+
+
+
diff --git a/src-angular/app/components/library/library-table/library-table.component.ts b/src-angular/app/components/library/library-table/library-table.component.ts
index 6637ba1..6cde246 100644
--- a/src-angular/app/components/library/library-table/library-table.component.ts
+++ b/src-angular/app/components/library/library-table/library-table.component.ts
@@ -1,9 +1,11 @@
-import { Component, OnInit, OnDestroy } from '@angular/core'
-import { SelectionService } from 'src-angular/app/core/services/selection.service'
-import { ChartData } from 'src-shared/interfaces/search.interface'
-import { SettingsService } from '../../../core/services/settings.service'
+import { Component, HostListener, OnDestroy, OnInit } from '@angular/core'
+
import { Subscription } from 'rxjs'
import { LibraryService } from 'src-angular/app/core/services/library.service'
+import { SelectionService } from 'src-angular/app/core/services/selection.service'
+import { ChartData } from 'src-shared/interfaces/search.interface'
+
+import { SettingsService } from '../../../core/services/settings.service'
type SortColumn = 'name' | 'artist' | 'album' | 'genre' | 'year' | 'charter' | 'length' | 'modifiedTime' | null
@@ -20,6 +22,8 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
allRowsSelected: boolean = false
subscriptions: Subscription[] = []
selectedSongs: ChartData[] = []
+ isLoading: boolean = false
+ hasSearched: boolean = false
constructor(
public libraryService: LibraryService,
@@ -56,6 +60,7 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
}
filterSongs(): void {
+ this.hasSearched = true
this.libraryService.getChartsBySearchTerm(this.searchTerm)
}
@@ -97,6 +102,10 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
}
}
+ rowIsSelected(song: ChartData): boolean {
+ return this.selectedSongs.some(selectedSong => selectedSong.md5 === song.md5)
+ }
+
trackByFn(index: number): number {
return index
}
@@ -111,6 +120,22 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
}
}
+ async loadMoreSongs(): Promise
{
+ if (this.isLoading) return
+
+ this.isLoading = true
+ await this.libraryService.loadMoreSongs()
+ this.isLoading = false
+ }
+
+ @HostListener('scroll', ['$event'])
+ onScroll(event: Event): void {
+ const target = event.target as HTMLElement
+ if (target.scrollHeight - target.scrollTop - target.clientHeight < 100) {
+ this.loadMoreSongs()
+ }
+ }
+
ngOnDestroy(): void {
this.subscriptions.forEach(subscription => subscription.unsubscribe())
}
diff --git a/src-angular/app/core/services/library.service.ts b/src-angular/app/core/services/library.service.ts
index ef21466..1aaaeab 100644
--- a/src-angular/app/core/services/library.service.ts
+++ b/src-angular/app/core/services/library.service.ts
@@ -1,9 +1,13 @@
import { Injectable, Injector } from '@angular/core'
+
import { BehaviorSubject } from 'rxjs'
import { ChartData } from 'src-shared/interfaces/search.interface'
+
import { DownloadService } from './download.service'
import { StorageService } from './storage.service'
+export const LIBRARY_TABLE_PAGESIZE = 50
+
@Injectable({
providedIn: 'root',
})
@@ -15,9 +19,11 @@ export class LibraryService {
selectedSongs$ = this._selectedSongs.asObservable()
private _downloadService: DownloadService | null = null
+ private currentPage = 1
+ private isLastPage = false
constructor(private injector: Injector, private storageService: StorageService) {
- this.storageService.getChartsBySearchTerm().then(library => {
+ this.storageService.getChartsBySearchTerm(undefined, 1, 50).then(library => {
if (library) {
this._tracks.next(library)
}
@@ -64,12 +70,14 @@ export class LibraryService {
}
addToSelectedSongs(song: ChartData) {
- const updatedSelectedSongs = [...this._selectedSongs.value, song]
- this._selectedSongs.next(updatedSelectedSongs)
+ if (!this._selectedSongs.value.some(selectedSong => selectedSong.md5 === song.md5)) {
+ const updatedSelectedSongs = [...this._selectedSongs.value, song]
+ this._selectedSongs.next(updatedSelectedSongs)
+ }
}
removeFromSelectedSongs(song: ChartData) {
- const updatedSelectedSongs = this._selectedSongs.value.filter(selectedSong => selectedSong !== song)
+ const updatedSelectedSongs = this._selectedSongs.value.filter(selectedSong => selectedSong.md5 !== song.md5)
this._selectedSongs.next(updatedSelectedSongs)
}
@@ -95,10 +103,35 @@ export class LibraryService {
this._tracks.next(library)
}
- async getChartsBySearchTerm(searchTerm?: string): Promise {
- const library = await this.storageService.getChartsBySearchTerm(searchTerm)
+ async loadMoreSongs(): Promise {
+ if (this.isLastPage)
+ return
+
+ this.currentPage++
+ const moreSongs = await this.getChartsBySearchTerm()
+
+ if (moreSongs.length < LIBRARY_TABLE_PAGESIZE) {
+ this.isLastPage = true
+ }
+
+ const uniqueSongs = moreSongs.filter(
+ song => !this._tracks.value.some(track => track.md5 === song.md5)
+ )
+
+ this._tracks.next([...this._tracks.value, ...uniqueSongs])
+ }
+
+ async getChartsBySearchTerm(searchTerm?: string): Promise {
+ if (searchTerm !== undefined) {
+ this.currentPage = 1
+ this._tracks.next([])
+ this.isLastPage = false
+ }
+
+ const library = await this.storageService.getChartsBySearchTerm(searchTerm, this.currentPage, LIBRARY_TABLE_PAGESIZE)
+
+ this._tracks.next([...this._tracks.value, ...library])
- this._tracks.next(library)
return library
}
}
diff --git a/src-angular/app/core/services/storage.service.ts b/src-angular/app/core/services/storage.service.ts
index 1800ecd..dce3985 100644
--- a/src-angular/app/core/services/storage.service.ts
+++ b/src-angular/app/core/services/storage.service.ts
@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'
-import { ChartData } from 'src-shared/interfaces/search.interface'
+
+import { ChartData, LibrarySearch } from 'src-shared/interfaces/search.interface'
@Injectable({
providedIn: 'root',
@@ -17,8 +18,14 @@ export class StorageService {
return window.electron.invoke.removeCharts(charts)
}
- async getChartsBySearchTerm(searchTerm?: string): Promise {
- return window.electron.invoke.getChartsBySearchTerm(searchTerm)
+ async getChartsBySearchTerm(searchTerm?: string, page?: number, pageSize?: number): Promise {
+ const librarySearch = {
+ searchTerm: searchTerm,
+ page: page,
+ pageSize: pageSize,
+ } as LibrarySearch
+
+ return window.electron.invoke.getChartsBySearchTerm(librarySearch)
}
async removeAllCharts(): Promise {
diff --git a/src-electron/database/databaseService.ts b/src-electron/database/databaseService.ts
index 90c0623..d6319b9 100644
--- a/src-electron/database/databaseService.ts
+++ b/src-electron/database/databaseService.ts
@@ -1,7 +1,8 @@
-import { ChartData } from 'src-shared/interfaces/search.interface.js'
+import { ChartData, LibrarySearch } from 'src-shared/interfaces/search.interface.js'
+import { Like } from 'typeorm'
+
import { dataSource } from './dataSource.js'
import { Chart } from './entities/Chart.js'
-import { Like } from 'typeorm'
export class DatabaseService {
async insertChart(chartData: ChartData): Promise {
@@ -12,7 +13,6 @@ export class DatabaseService {
const chartRepository = dataSource.getRepository(Chart)
- // if one already exist dont create
const existingChart = await chartRepository.findOneBy({ md5: chartData.md5 })
if (existingChart) {
@@ -61,7 +61,6 @@ export class DatabaseService {
const chartRepository = dataSource.getRepository(Chart)
- // delete the array of charts provided using querybulilder
charts.forEach(async chart => {
console.log('removing chart:', chart.name)
await chartRepository.delete({ md5: chart.md5 })
@@ -72,7 +71,7 @@ export class DatabaseService {
}
}
- async getChartsBySearchTerm(searchTerm?: string): Promise {
+ async getChartsBySearchTerm(search: LibrarySearch): Promise {
try {
if (!dataSource.isInitialized) {
await dataSource.initialize()
@@ -82,22 +81,27 @@ export class DatabaseService {
let charts: Chart[]
- if (searchTerm) {
- const likeSearchTerm = `%${searchTerm}%`
+ if (search.searchTerm?.trim()) {
+ const likeSearchTerm = Like(`%${search.searchTerm}%`)
+
charts = await chartRepository.find({
where: [
- { name: Like(likeSearchTerm) },
- { album: Like(likeSearchTerm) },
- { artist: Like(likeSearchTerm) },
- { genre: Like(likeSearchTerm) },
- { year: Like(likeSearchTerm) },
- { charter: Like(likeSearchTerm) },
+ { name: likeSearchTerm },
+ { album: likeSearchTerm },
+ { artist: likeSearchTerm },
+ { genre: likeSearchTerm },
+ { year: likeSearchTerm },
+ { charter: likeSearchTerm },
],
+ skip: (search.page - 1) * search.pageSize,
+ take: search.pageSize,
})
} else {
- charts = await chartRepository.find()
+ charts = await chartRepository.find({
+ skip: (search.page - 1) * search.pageSize,
+ take: search.pageSize,
+ })
}
-
return charts as unknown as ChartData[]
} catch (error) {
console.error('Error fetching charts by search term:', error)
@@ -119,7 +123,6 @@ export class DatabaseService {
throw error
}
}
-
}
export const databaseService = new DatabaseService()
diff --git a/src-electron/ipc/LibraryHandler.ipc.ts b/src-electron/ipc/LibraryHandler.ipc.ts
index 72b223f..8268de7 100644
--- a/src-electron/ipc/LibraryHandler.ipc.ts
+++ b/src-electron/ipc/LibraryHandler.ipc.ts
@@ -1,5 +1,6 @@
+import { ChartData, LibrarySearch } from 'src-shared/interfaces/search.interface.js'
+
import { databaseService } from '../database/databaseService.js'
-import { ChartData } from 'src-shared/interfaces/search.interface.js'
export async function addChart(chartData: ChartData): Promise {
try {
@@ -37,9 +38,9 @@ export async function removeAllCharts(): Promise {
}
}
-export async function getChartsBySearchTerm(searchTerm?: string): Promise {
+export async function getChartsBySearchTerm(search: LibrarySearch): Promise {
try {
- return await databaseService.getChartsBySearchTerm(searchTerm)
+ return await databaseService.getChartsBySearchTerm(search)
} catch (error) {
console.error('Error in getChartsHandler:', error)
throw error
diff --git a/src-shared/interfaces/ipc.interface.ts b/src-shared/interfaces/ipc.interface.ts
index 317e871..c611546 100644
--- a/src-shared/interfaces/ipc.interface.ts
+++ b/src-shared/interfaces/ipc.interface.ts
@@ -3,9 +3,9 @@ import { UpdateInfo } from 'electron-updater'
import { Settings } from '../Settings.js'
import { Download, DownloadProgress } from './download.interface.js'
+import { ChartData, LibrarySearch } from './search.interface.js'
import { ThemeColors } from './theme.interface.js'
import { UpdateProgress } from './update.interface.js'
-import { ChartData } from './search.interface.js'
export interface ContextBridgeApi {
invoke: IpcInvokeHandlers
@@ -64,7 +64,7 @@ export interface IpcInvokeEvents {
output: void
}
getChartsBySearchTerm: {
- input?: string
+ input?: LibrarySearch
output: ChartData[]
}
}
diff --git a/src-shared/interfaces/search.interface.ts b/src-shared/interfaces/search.interface.ts
index f35e1b7..18a4ead 100644
--- a/src-shared/interfaces/search.interface.ts
+++ b/src-shared/interfaces/search.interface.ts
@@ -117,6 +117,12 @@ export interface FolderIssue {
description: string
}
+export interface LibrarySearch {
+ searchTerm: string
+ page: number
+ pageSize: number
+}
+
export type ChartData = SearchResult['data'][number]
export interface SearchResult {
found: number