From 97521f19e5eb8e475fe8ebd7bd2564fb6a44ad99 Mon Sep 17 00:00:00 2001 From: ju09279 Date: Sun, 18 Aug 2024 02:20:46 +0200 Subject: [PATCH] search feature --- .gitignore | 5 + backend/Program.cs | 40 +- backend/SqliteDB.cs | 444 +++++++++++++++++++ backend/osudb.cs | 84 +--- backend/shitweb.csproj | 1 + backend/types.cs | 46 ++ frontend/src/components/ActiveSearchList.vue | 42 ++ frontend/src/stores/audioStore.ts | 4 +- frontend/src/stores/userStore.ts | 18 +- frontend/src/views/CollectionView.vue | 37 +- frontend/src/views/NowPlayingView.vue | 2 +- frontend/src/views/RecentView.vue | 60 +++ frontend/src/views/SearchView.vue | 88 +++- 13 files changed, 783 insertions(+), 88 deletions(-) create mode 100644 .gitignore create mode 100644 backend/SqliteDB.cs create mode 100644 frontend/src/components/ActiveSearchList.vue diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3f8631 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +################################################################################ +# Diese .gitignore-Datei wurde von Microsoft(R) Visual Studio automatisch erstellt. +################################################################################ + +/backend/Beatmaps.db diff --git a/backend/Program.cs b/backend/Program.cs index 26a861c..2312198 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; +using shitweb; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -39,12 +40,12 @@ app.MapGet("/api/v1/songs/recent", (int? limit, int? offset) => var limitValue = limit ?? 100; // default to 10 if not provided var offsetValue = offset ?? 0; // default to 0 if not provided - return Results.Json(Osudb.Instance.GetRecent(limitValue, offsetValue)); + return Results.Json(SqliteDB.GetByRecent(limitValue, offsetValue)); }); app.MapGet("/api/v1/songs/favorite", (int? limit, int? offset) => { - var limitValue = limit ?? 10; // default to 10 if not provided + var limitValue = limit ?? 100; // default to 10 if not provided var offsetValue = offset ?? 0; // default to 0 if not provided return Results.Ok(new { Limit = limitValue, Offset = offsetValue, Message = "List of favorite songs" }); @@ -56,13 +57,17 @@ app.MapGet("/api/v1/songs/{hash}", (string hash) => }); app.MapGet("/api/v1/collections/", async (int? limit, int? offset, [FromServices] IMemoryCache cache) => - { - const string cacheKey = "collections"; + { + + var limitValue = limit ?? 100; // default to 10 if not provided + var offsetValue = offset ?? 0; + + string cacheKey = $"collections_{offsetValue}_{limitValue}"; if (!cache.TryGetValue(cacheKey, out var collections)) { - collections = Osudb.Instance.GetCollections(); + collections = Osudb.Instance.GetCollections(limit: limitValue, offset: offsetValue); var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromDays(1)) @@ -105,6 +110,28 @@ app.MapGet("/api/v1/audio/{*fileName}", async (string fileName, HttpContext cont return Results.Stream(fileStream, contentType, enableRangeProcessing: true); }); +app.MapGet("/api/v1/search/active", async (string? q) => +{ + return Results.Ok(SqliteDB.activeSearch(q)); +}); + +app.MapGet("/api/v1/search/artist", async (string? q, int? limit, int? offset) => +{ + var limitv = limit ?? 100; + var offsetv = offset ?? 0; + + return Results.Ok(SqliteDB.GetArtistSearch(q, limitv, offsetv)); +}); + +app.MapGet("/api/v1/search/songs", async (string? q, int? limit, int? offset) => +{ + return Results.Ok(); +}); + +app.MapGet("/api/v1/search/collections", async (string? q, int? limit, int? offset) => +{ + return Results.Ok(); +}); app.MapGet("/api/v1/images/{*filename}", async (string filename, int? h, int? w) => { @@ -195,4 +222,5 @@ static ImageFormat GetImageFormat(string extension) }; } -app.Run(); +Osudb.Instance.ToString(); +app.Run(); \ No newline at end of file diff --git a/backend/SqliteDB.cs b/backend/SqliteDB.cs new file mode 100644 index 0000000..67dd38b --- /dev/null +++ b/backend/SqliteDB.cs @@ -0,0 +1,444 @@ +using OsuParsers.Beatmaps; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using System.Data.SQLite; +using System.Reflection.PortableExecutable; + +namespace shitweb +{ + public class SqliteDB + { + private static SqliteDB instance; + private const string filename = "Beatmaps.db"; + private const string dburl = $"Data Source={filename};Version=3;"; + + static SqliteDB() { } + + public static SqliteDB Instance() + { + if (instance == null) + { + + instance = new SqliteDB(); + } + return instance; + } + + public void setup(OsuParsers.Database.OsuDatabase osuDatabase) + { + + int count = 0; + + using (var connection = new SQLiteConnection(dburl)) + { + connection.Open(); + + string createBeatmaps = @" + CREATE TABLE IF NOT EXISTS Beatmap ( + BeatmapId INTEGER DEFAULT 0, + Artist TEXT DEFAULT '?????', + ArtistUnicode TEXT DEFAULT '?????', + Title TEXT DEFAULT '???????', + TitleUnicode TEXT DEFAULT '???????', + Creator TEXT DEFAULT '?????', + Difficulty TEXT DEFAULT '1', + AudioFileName TEXT DEFAULT 'unknown.mp3', + MD5Hash TEXT DEFAULT '00000000000000000000000000000000', + FileName TEXT DEFAULT 'unknown.osu', + RankedStatus TEXT DEFAULT Unknown, + LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00', + TotalTime INTEGER DEFAULT 0, + AudioPreviewTime INTEGER DEFAULT 0, + BeatmapSetId INTEGER DEFAULT -1, + Source TEXT DEFAULT '', + Tags TEXT DEFAULT '', + LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00', + FolderName TEXT DEFAULT 'Unknown Folder', + UNIQUE (Artist, Title, MD5Hash) + );"; + + using (var command = new SQLiteCommand(createBeatmaps, connection)) + { + command.ExecuteNonQuery(); + } + + string activeSearch = @"CREATE VIRTUAL TABLE IF NOT EXISTS BeatmapFTS USING fts4( + Title, + Artist, + );"; + + using (var command = new SQLiteCommand(activeSearch, connection)) + { + command.ExecuteNonQuery(); + } + + string triggerSearchupdate = @"CREATE TRIGGER IF NOT EXISTS Beatmap_Insert_Trigger + AFTER INSERT ON Beatmap + BEGIN + INSERT INTO BeatmapFTS (Title, Artist) + VALUES (NEW.Title, NEW.Artist); + END;"; + + using (var command = new SQLiteCommand(triggerSearchupdate, connection)) + { + command.ExecuteNonQuery(); + } + + string query = @"SELECT COUNT(rowid) as count FROM Beatmap"; + + using (var command = new SQLiteCommand(query, connection)) + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + count = reader.GetInt32(reader.GetOrdinal("count")); + } + } + } + } + + if (count < osuDatabase.BeatmapCount) + { + DateTime? LastMapInsert = null; + + using (var connection = new SQLiteConnection(dburl)) + { + connection.Open(); + + string query = @"SELECT MAX(LastModifiedTime) as Time FROM Beatmap"; ; + using (var command = new SQLiteCommand(query, connection)) + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + try + { + LastMapInsert = reader.GetDateTime(reader.GetOrdinal("Time")); + } + + catch (Exception e) + { + LastMapInsert = null; + } + } + } + } + + int i = 0; + int size = osuDatabase.BeatmapCount; + Console.CursorVisible = false; + foreach (var item in osuDatabase.Beatmaps) + { + if (LastMapInsert == null || item.LastModifiedTime > LastMapInsert) + { + insertBeatmap(item); + i++; + Console.CursorTop -= 1; + Console.Write($"Inserted {i}/{size}"); + } + } + + } + } + + + + } + + public static List GetByRecent(int limit, int offset) + { + var songs = new List(); + + using (var connection = new SQLiteConnection(dburl)) + { + connection.Open(); + + string query = @" + SELECT + MD5Hash, + Title, + Artist, + TotalTime, + Creator, + FileName, + FolderName, + AudioFileName + FROM + Beatmap + GROUP BY + BeatmapSetId + ORDER BY + LastModifiedTime DESC + LIMIT @Limit + OFFSET @Offset + "; + + using (var command = new SQLiteCommand(query, connection)) + { + command.Parameters.AddWithValue("@Limit", limit); + command.Parameters.AddWithValue("@Offset", offset); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + string folder = reader.GetString(reader.GetOrdinal("FolderName")); + string file = reader.GetString(reader.GetOrdinal("FileName")); + string audio = reader.GetString(reader.GetOrdinal("AudioFileName")); + + var img = Osudb.getBG(folder, file); + Song song = new Song( + hash: reader.GetString(reader.GetOrdinal("MD5Hash")), + name: reader.GetString(reader.GetOrdinal("Title")), + artist: reader.GetString(reader.GetOrdinal("Artist")), + length: reader.GetInt32(reader.GetOrdinal("TotalTime")), + url: $"{folder}/{audio}", + previewimage: img, + mapper: reader.GetString(reader.GetOrdinal("Creator")) + ); + + songs.Add(song); + } + } + } + } + return songs; + } + + public void insertBeatmap(OsuParsers.Database.Objects.DbBeatmap beatmap) + { + + using (var connection = new SQLiteConnection(dburl)) + { + + connection.Open(); + + string insertBeatmap = @" + INSERT INTO Beatmap ( + BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator, Difficulty, + AudioFileName, MD5Hash, FileName, RankedStatus, LastModifiedTime, TotalTime, + AudioPreviewTime, BeatmapSetId, Source, Tags, LastPlayed, FolderName + ) VALUES ( + @BeatmapId, @Artist, @ArtistUnicode, @Title, @TitleUnicode, @Creator, @Difficulty, + @AudioFileName, @MD5Hash, @FileName, @RankedStatus, @LastModifiedTime, @TotalTime, + @AudioPreviewTime, @BeatmapSetId, @Source, @Tags, @LastPlayed, @FolderName + );"; + using (var command = new SQLiteCommand(insertBeatmap, connection)) + { + command.Parameters.AddWithValue("@BeatmapSetId", beatmap.BeatmapSetId); + command.Parameters.AddWithValue("@BeatmapId", beatmap.BeatmapId); + command.Parameters.AddWithValue("@Artist", beatmap.Artist); + command.Parameters.AddWithValue("@ArtistUnicode", beatmap.ArtistUnicode); + command.Parameters.AddWithValue("@Title", beatmap.Title); + command.Parameters.AddWithValue("@TitleUnicode", beatmap.TitleUnicode); + command.Parameters.AddWithValue("@Creator", beatmap.Creator); + command.Parameters.AddWithValue("@Difficulty", beatmap.Difficulty); + command.Parameters.AddWithValue("@AudioFileName", beatmap.AudioFileName); + command.Parameters.AddWithValue("@MD5Hash", beatmap.MD5Hash); + command.Parameters.AddWithValue("@FileName", beatmap.FileName); + command.Parameters.AddWithValue("@RankedStatus", beatmap.RankedStatus); + command.Parameters.AddWithValue("@LastModifiedTime", beatmap.LastModifiedTime); + command.Parameters.AddWithValue("@TotalTime", beatmap.TotalTime); + command.Parameters.AddWithValue("@AudioPreviewTime", beatmap.AudioPreviewTime); + command.Parameters.AddWithValue("@Source", beatmap.Source); + command.Parameters.AddWithValue("@Tags", beatmap.Tags); + command.Parameters.AddWithValue("@LastPlayed", beatmap.LastPlayed); + command.Parameters.AddWithValue("@FolderName", beatmap.FolderName); + + int rows = command.ExecuteNonQuery(); + Console.WriteLine(rows); + } + } + } + + public static Song GetSongByHash(string hash) + { + + using (var connection = new SQLiteConnection(dburl)) + { + connection.Open(); + + string query = @" + SELECT + MD5Hash, + Title, + Artist, + TotalTime, + Creator, + FileName, + FolderName, + AudioFileName + FROM Beatmap + WHERE MD5Hash = @Hash; + "; + + using (var command = new SQLiteCommand(query, connection)) + { + command.Parameters.AddWithValue("@Hash", hash); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + + string folder = reader.GetString(reader.GetOrdinal("FolderName")); + string file = reader.GetString(reader.GetOrdinal("FileName")); + string audio = reader.GetString(reader.GetOrdinal("AudioFileName")); + + var img = Osudb.getBG(folder, file); + Song song = new Song( + hash: reader.GetString(reader.GetOrdinal("MD5Hash")), + name: reader.GetString(reader.GetOrdinal("Title")), + artist: reader.GetString(reader.GetOrdinal("Artist")), + length: reader.GetInt32(reader.GetOrdinal("TotalTime")), + url: $"{folder}/{audio}", + previewimage: img, + mapper: reader.GetString(reader.GetOrdinal("Creator")) + ); + + return song; + } + } + } + } + return null; + } + + public static ActiveSearch activeSearch(string query) { + ActiveSearch search = new ActiveSearch(); + + using (var connection = new SQLiteConnection(dburl)) + { + string q = @"SELECT + MD5Hash, + Title, + Artist, + TotalTime, + Creator, + FileName, + FolderName, + AudioFileName + FROM Beatmap + WHERE Title LIKE @query + OR Artist LIKE @query + OR Tags LIKE @query + Group By Title + LIMIT 15"; + + connection.Open(); + + using (var command = new SQLiteCommand(q, connection)) + { + command.Parameters.AddWithValue("@query", "%" + query + "%"); + + using (var reader = command.ExecuteReader()) { + + while (reader.Read()) { + + string folder = reader.GetString(reader.GetOrdinal("FolderName")); + string file = reader.GetString(reader.GetOrdinal("FileName")); + string audio = reader.GetString(reader.GetOrdinal("AudioFileName")); + + var img = Osudb.getBG(folder, file); + Song song = new Song( + hash: reader.GetString(reader.GetOrdinal("MD5Hash")), + name: reader.GetString(reader.GetOrdinal("Title")), + artist: reader.GetString(reader.GetOrdinal("Artist")), + length: reader.GetInt32(reader.GetOrdinal("TotalTime")), + url: $"{folder}/{audio}", + previewimage: img, + mapper: reader.GetString(reader.GetOrdinal("Creator")) + ); + + search.Songs.Add(song); + } + } + } + + string q2 = @"SELECT + Artist + FROM Beatmap + WHERE Artist LIKE @query + Group By Artist + LIMIT 5"; + + using (var command = new SQLiteCommand(q2, connection)) + { + command.Parameters.AddWithValue("@query", query + "%"); + + using (var reader = command.ExecuteReader()) + { + + while (reader.Read()) + { + + search.Artist.Add(reader.GetString(reader.GetOrdinal("Artist"))); + + } + } + } + } + + return search; + } + + public static List GetArtistSearch(string query, int limit, int offset) { + List songs = new List(); + + query = $"%{query}%"; + using (var connection = new SQLiteConnection(dburl)) + { + string q = @"SELECT + MD5Hash, + Title, + Artist, + TotalTime, + Creator, + FileName, + FolderName, + AudioFileName + FROM Beatmap + WHERE Artist LIKE @query + Group By Title + LIMIT @Limit + OFFSET @Offset"; + + connection.Open(); + + using (var command = new SQLiteCommand(q, connection)) + { + command.Parameters.AddWithValue("@query", query); + command.Parameters.AddWithValue("@Limit", limit); + command.Parameters.AddWithValue("@Offset", offset); + + using (var reader = command.ExecuteReader()) + { + + while (reader.Read()) + { + string folder = reader.GetString(reader.GetOrdinal("FolderName")); + string file = reader.GetString(reader.GetOrdinal("FileName")); + string audio = reader.GetString(reader.GetOrdinal("AudioFileName")); + + var img = Osudb.getBG(folder, file); + Song song = new Song( + hash: reader.GetString(reader.GetOrdinal("MD5Hash")), + name: reader.GetString(reader.GetOrdinal("Title")), + artist: reader.GetString(reader.GetOrdinal("Artist")), + length: reader.GetInt32(reader.GetOrdinal("TotalTime")), + url: $"{folder}/{audio}", + previewimage: img, + mapper: reader.GetString(reader.GetOrdinal("Creator")) + ); + + songs.Add(song); + } + } + } + } + + return songs; + } + } +} diff --git a/backend/osudb.cs b/backend/osudb.cs index 2e165c8..c6cdfc1 100644 --- a/backend/osudb.cs +++ b/backend/osudb.cs @@ -4,6 +4,7 @@ using Microsoft.Win32; using OsuParsers.Beatmaps; using OsuParsers.Database; using OsuParsers.Database.Objects; +using shitweb; using System.Collections; using System.Net; using System.Text.RegularExpressions; @@ -13,7 +14,6 @@ public class Osudb private static Osudb instance = null; private static readonly object padlock = new object(); public static string osufolder { get; private set; } - public static OsuDatabase osuDatabase { get; private set; } public static CollectionDatabase CollectionDb { get; private set; } static Osudb() @@ -37,6 +37,7 @@ public class Osudb static void Parse(string filepath) { + OsuDatabase osuDatabase = null; string file = "/osu!.db"; if (File.Exists(filepath + file)) { @@ -45,6 +46,8 @@ public class Osudb Console.WriteLine($"Parsing {file}"); osuDatabase = OsuParsers.Decoders.DatabaseDecoder.DecodeOsu($"{filepath}{file}"); Console.WriteLine($"Parsed {file}"); + + fileStream.Close(); } } @@ -58,6 +61,10 @@ public class Osudb Console.WriteLine($"Parsed {file}"); } } + + SqliteDB.Instance().setup(osuDatabase); + osuDatabase = null; + GC.Collect(); } public static Osudb Instance @@ -75,15 +82,6 @@ public class Osudb } } - public DbBeatmap GetBeatmapbyHash(string Hash) - { - if (Hash == null || osuDatabase == null) - { - return null; - } - return osuDatabase.Beatmaps.FirstOrDefault(beatmap => beatmap.MD5Hash == Hash); - } - public static OsuParsers.Database.Objects.Collection GetCollectionbyName(string name) { return CollectionDb.Collections.FirstOrDefault(collection => collection.Name == name); @@ -101,87 +99,37 @@ public class Osudb if (collection == null) { return null; } List songs = new List(); - var activeId = 0; + var activeId = ""; collection.MD5Hashes.ForEach(hash => { - var beatmap = GetBeatmapbyHash(hash); + var beatmap = SqliteDB.GetSongByHash(hash); if (beatmap == null) { return; } - if (activeId == beatmap.BeatmapSetId) { return; } - activeId = beatmap.BeatmapSetId; - //todo - string img = getBG(beatmap.FolderName, beatmap.FileName); - - songs.Add(new Song(hash: beatmap.MD5Hash, name: beatmap.Title, artist: beatmap.Artist, length: beatmap.TotalTime, url: $"{beatmap.FolderName}/{beatmap.AudioFileName}" , previewimage: img, mapper: beatmap.Creator)); + songs.Add(beatmap); }); return new Collection(collection.Name, songs.Count, songs); } - public List GetCollections() + public List GetCollections(int limit, int offset) { List collections = new List(); - for (int i = 0; i < CollectionDb.Collections.Count; i++) { + for (int i = offset; i < CollectionDb.Collections.Count - 1 && i < offset + limit; i++) { var collection = CollectionDb.Collections[i]; - var beatmap = GetBeatmapbyHash(collection.MD5Hashes.FirstOrDefault()); + var beatmap = SqliteDB.GetSongByHash(collection.MD5Hashes.FirstOrDefault()); - //todo - string img = getBG(beatmap.FolderName, beatmap.FileName); - - collections.Add(new CollectionPreview(index: i, name: collection.Name, previewimage: img, length: collection.Count)); + collections.Add(new CollectionPreview(index: i, name: collection.Name, previewimage: beatmap.previewimage, length: collection.Count)); }; return collections; } - public List GetRecent(int limit, int offset) - { - var recent = new List(); - if(limit > 100 && limit < 0) { - limit = 100; - } - - var size = osuDatabase.Beatmaps.Count -1; - for (int i = size - offset; i > size - offset - limit; i--) - { - var beatmap = osuDatabase.Beatmaps.ElementAt(i); - if (beatmap == null) { - continue; - } - - string img = getBG(beatmap.FolderName, beatmap.FileName); - - recent.Add(new Song( - name: beatmap.FileName, - hash: beatmap.MD5Hash, - artist: beatmap.Artist, - length: beatmap.TotalTime, - url: $"{beatmap.FolderName}/{beatmap.AudioFileName}", - previewimage: img, - mapper: beatmap.Creator)); - } - - return recent; - } - - public List GetFavorites() - { - var recent = new List(); - /* - osuDatabase.Beatmaps.ForEach(beatmap => - { - Console.WriteLine(beatmap.LastModifiedTime); - }); - */ - return null; - } - - private static string getBG(string songfolder, string diff) + public static string getBG(string songfolder, string diff) { string folderpath = Path.Combine(songfolder, diff); string filepath = Path.Combine(osufolder, "Songs", folderpath); diff --git a/backend/shitweb.csproj b/backend/shitweb.csproj index 07f4bfa..84f95c5 100644 --- a/backend/shitweb.csproj +++ b/backend/shitweb.csproj @@ -8,6 +8,7 @@ + diff --git a/backend/types.cs b/backend/types.cs index d4718fc..af8ccb6 100644 --- a/backend/types.cs +++ b/backend/types.cs @@ -1,3 +1,7 @@ +using OsuParsers.Database.Objects; +using OsuParsers.Enums.Database; +using OsuParsers.Enums; + public class Song{ public string hash {get; set;} public string name {get; set;} @@ -37,4 +41,46 @@ public class Collection{ this.name = name; this.length = length; this.songs = songs; } +} + +public class ActiveSearch{ + public List Artist { get; set; } = new List(); + public List Songs { get; set; } = new List(); +} + +public class Beatmap +{ + public string Artist { get; set; } + public string ArtistUnicode { get; set; } + public string Title { get; set; } + public string TitleUnicode { get; set; } + public string Creator { get; set; } + public string Difficulty { get; set; } + public string AudioFileName { get; set; } + public string MD5Hash { get; set; } + public string FileName { get; set; } + public RankedStatus RankedStatus { get; set; } + public DateTime LastModifiedTime { get; set; } + public int TotalTime { get; set; } + public int AudioPreviewTime { get; set; } + public int BeatmapId { get; set; } + public int BeatmapSetId { get; set; } + public string Source { get; set; } + public string Tags { get; set; } + public DateTime LastPlayed { get; set; } + public string FolderName { get; set; } +} + +public class BeatmapSet { + public int BeatmapSetId { get; set; } + public string FolderName { get; set; } + public string Creator { get; set; } + public DateTime LastModifiedTime { get; set; } + public List Beatmaps { get; private set; } = new List(); + + public void AddBeatmap(Beatmap beatmap) + { + beatmap.BeatmapSetId = this.BeatmapSetId; + Beatmaps.Add(beatmap); + } } \ No newline at end of file diff --git a/frontend/src/components/ActiveSearchList.vue b/frontend/src/components/ActiveSearchList.vue new file mode 100644 index 0000000..d2ef334 --- /dev/null +++ b/frontend/src/components/ActiveSearchList.vue @@ -0,0 +1,42 @@ + + + \ No newline at end of file diff --git a/frontend/src/stores/audioStore.ts b/frontend/src/stores/audioStore.ts index f9eee6c..552796d 100644 --- a/frontend/src/stores/audioStore.ts +++ b/frontend/src/stores/audioStore.ts @@ -70,6 +70,7 @@ export const useAudioStore = defineStore('audioStore', () => { audio.pause() audio.currentTime = 0; audio.play() + return; } if (shuffle.value) { @@ -77,6 +78,7 @@ export const useAudioStore = defineStore('audioStore', () => { setSong(activeCollection.value[Math.floor(activeCollection.value.length * Math.random())]) audio.play() + return; } toggleNext() @@ -89,7 +91,7 @@ export const useAudioStore = defineStore('audioStore', () => { var audio = document.getElementById("audio-player") as HTMLAudioElement; - audio.currentTime = Math.round((audioslider.value / 100) * audio.duration) + audio.currentTime = Math.round((Number(audioslider.value) / 100) * audio.duration) } function togglePrev() { diff --git a/frontend/src/stores/userStore.ts b/frontend/src/stores/userStore.ts index 5c97220..ad2e3e1 100644 --- a/frontend/src/stores/userStore.ts +++ b/frontend/src/stores/userStore.ts @@ -60,9 +60,9 @@ export const useUserStore = defineStore('userStore', () => { } - async function fetchCollections(): Promise { - const cacheKey = 'collections_cache'; - const url = `${baseUrl.value}api/v1/collections/`; + async function fetchCollections(offset: number, limit: number): Promise { + const cacheKey = `collections_cache_${offset}_${limit}`; + const url = `${baseUrl.value}api/v1/collections/?offset=${offset}&limit=${limit}`; return fetchWithCache(cacheKey, url); } @@ -84,7 +84,17 @@ export const useUserStore = defineStore('userStore', () => { return fetchWithCache(cacheKey, url); } + async function fetchActiveSearch(query: string): Promise<{}> { + const cacheKey = `collections_activeSearch_${query}`; + const url = `${baseUrl.value}api/v1/search/active?q=${query}`; + return fetchWithCache(cacheKey, url); + } + async function fetchSearchArtist(query: string): Promise { + const cacheKey = `collections_artist_${query}`; + const url = `${baseUrl.value}api/v1/search/artist?q=${query}`; + return fetchWithCache(cacheKey, url); + } - return { fetchSong, fetchCollections, fetchCollection, fetchRecent, fetchFavorites, userId, baseUrl } + return { fetchSong, fetchActiveSearch, fetchSearchArtist, fetchCollections, fetchCollection, fetchRecent, fetchFavorites, userId, baseUrl } }) diff --git a/frontend/src/views/CollectionView.vue b/frontend/src/views/CollectionView.vue index 86c5bf1..762591a 100644 --- a/frontend/src/views/CollectionView.vue +++ b/frontend/src/views/CollectionView.vue @@ -7,20 +7,47 @@ import CollectionListItem from '../components/CollectionListItem.vue' const userStore = useUserStore(); const collections = ref([]); +const limit = ref(10); +const offset = ref(0); +const isLoading = ref(false); -onMounted(async () => { - const data = await userStore.fetchCollections(); +const fetchCollections = async () => { + if (isLoading.value) return; + isLoading.value = true; + + const data = await userStore.fetchCollections(offset.value, limit.value); data.forEach(song => { song.previewimage = `${userStore.baseUrl}api/v1/images/${song.previewimage}?h=80&w=80`; - }) - collections.value = data; + }); + collections.value = [...collections.value, ...data]; + offset.value += limit.value; + + isLoading.value = false; +}; + +onMounted(async () => { + await fetchCollections(); + + const container = document.querySelector('.collection-container'); + if (container) { + container.addEventListener('scroll', async () => { + const scrollTop = container.scrollTop; + const scrollHeight = container.scrollHeight; + const clientHeight = container.clientHeight; + + if (scrollTop + clientHeight >= scrollHeight * 0.9 && !isLoading.value) { + await fetchCollections(); + } + }); + } }); +