mirror of
https://github.com/JuLi0n21/pwa-player.git
synced 2026-04-19 15:30:05 +00:00
update thingys
This commit is contained in:
1646
frontend/package-lock.json
generated
1646
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,10 @@
|
||||
"@types/node": "^22.9.3",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"npm-run-all2": "^7.0.1",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.1.7",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "^6.0.1",
|
||||
"vite-plugin-vue-devtools": "^7.6.5",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import SongItem from '../components/SongItem.vue'
|
||||
import type { Song } from '../script/types'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { type Song, mapApiToCollection } from '../script/types'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import CollectionListItem from '../components/CollectionListItem.vue'
|
||||
import { useAudio } from '@/composables/useAudio'
|
||||
@@ -13,24 +13,86 @@ const { musicApi } = useApi()
|
||||
|
||||
const songs = ref<Song[]>([])
|
||||
const name = ref('name')
|
||||
const limit = 50
|
||||
let offset = ref(0)
|
||||
let loading = ref(false)
|
||||
let hasMore = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
const fetchMoreSongs = async () => {
|
||||
if (loading.value || !hasMore.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await musicApi().musicBackendCollections(Array.isArray(route.params.id) ? route.params.id[0] : route.params.id) // Adjust method name if needed
|
||||
const response = await musicApi().musicBackendCollections(
|
||||
"",
|
||||
Array.isArray(route.params.id) ? route.params.id[0] : route.params.id,
|
||||
limit,
|
||||
offset.value
|
||||
)
|
||||
|
||||
const base = import.meta.env.VITE_MUSIC_API_URL || 'http://localhost:8080'
|
||||
const col = mapApiToCollection(response.data)
|
||||
|
||||
response.songs.forEach(song => {
|
||||
song.previewimage = `${base}/api/v1/images/${song.previewimage}`
|
||||
song.url = `${base}/api/v1/audio/${song.url}`
|
||||
})
|
||||
|
||||
name.value = response.name
|
||||
songs.value = response.songs
|
||||
if (offset.value === 0) {
|
||||
name.value = col.name
|
||||
songs.value = col.songs
|
||||
} else {
|
||||
songs.value.push(...col.songs)
|
||||
}
|
||||
|
||||
audioStore.setCollection(songs.value)
|
||||
|
||||
if (col.songs.length < limit) {
|
||||
hasMore.value = false
|
||||
}
|
||||
|
||||
offset.value += limit
|
||||
} catch (error) {
|
||||
console.error('Error fetching collection:', error)
|
||||
console.error('Error fetching songs:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onScroll = (e: Event) => {
|
||||
const target = e.target as HTMLElement
|
||||
const scrollTop = target.scrollTop
|
||||
const scrollHeight = target.scrollHeight
|
||||
const clientHeight = target.clientHeight
|
||||
|
||||
if (scrollTop + clientHeight >= scrollHeight * 0.9) {
|
||||
fetchMoreSongs()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchMoreSongs()
|
||||
const container = document.querySelector('.coll-container')
|
||||
container?.addEventListener('scroll', onScroll)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const container = document.querySelector('.coll-container')
|
||||
container?.removeEventListener('scroll', onScroll)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<main class="flex-1 text-center flex flex-col h-full overflow-y-hidden">
|
||||
<header v-show="true">
|
||||
<div class="wrapper">
|
||||
<nav class="flex justify-start my-2 mx-1 space-x-1 overflow-y-scroll flex-nowrap text-nowrap">
|
||||
<RouterLink class="p-1 rounded-full backdrop--light shadow-xl" to="/menu/collections"><i class="fa-solid fa-arrow-left"></i>
|
||||
</RouterLink>
|
||||
<p class="p-1 rounded-full backdrop--light shadow-xl">{{ name }}</p>
|
||||
</nav>
|
||||
<hr>
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
class="flex-1 flex-col overflow-y-scroll coll-container"
|
||||
>
|
||||
<SongItem v-for="(song, index) in songs" :key="index" :song="song" />
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
@@ -3,15 +3,11 @@ import type { CollectionPreview } from '@/script/types';
|
||||
|
||||
const props = defineProps<{ collection: CollectionPreview }>();
|
||||
|
||||
|
||||
function hi() {
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<RouterLink @click.navtive="hi" :to="'/collection/' + props.collection.index">
|
||||
<RouterLink :to="'/collection/' + props.collection.index +1">
|
||||
<div class=" border bordercolor rounded-lg flex">
|
||||
<img class="h-20 w-20 m-2 rounded-lg" :src="props.collection.previewimage" loading="lazy" />
|
||||
<div class="flex flex-col">
|
||||
|
||||
@@ -6,7 +6,7 @@ let userInstance: ReturnType<typeof createUser> | null = null;
|
||||
function createUser() {
|
||||
const user = ref<Me | null>(null);
|
||||
const baseUrl = ref(import.meta.env.VITE_BACKEND_URL || 'http://localhost:8080');
|
||||
const proxyUrl = ref(import.meta.env.VITE_PROXY_URL || 'http://localhost:8081');
|
||||
const proxyUrl = ref(import.meta.env.VITE_PROXY_URL || 'https://proxy.illegalesachen.download');
|
||||
|
||||
function saveUser(u: Me | null) {
|
||||
localStorage.setItem('activeUser', JSON.stringify(u));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Apiv1Song, v1CollectionPreview } from '@/generated';
|
||||
import type { Apiv1Song, v1CollectionPreview, v1Collection } from '@/generated';
|
||||
|
||||
export type Song = {
|
||||
hash: string;
|
||||
@@ -36,16 +36,36 @@ export type CollectionPreview = {
|
||||
previewimage: string;
|
||||
};
|
||||
|
||||
export type Collection = {
|
||||
name: string;
|
||||
items: number;
|
||||
songs: Song[];
|
||||
}
|
||||
|
||||
export function mapApiToCollection(coll : v1Collection): Collection {
|
||||
|
||||
return {
|
||||
name: coll.name,
|
||||
items: coll.items,
|
||||
songs: mapApiToSongs(coll.songs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function mapToCollectionPreview(
|
||||
apiCollection: v1CollectionPreview,
|
||||
index: number
|
||||
): CollectionPreview {
|
||||
const image = apiCollection.image;
|
||||
const imageIsMissing = !image || image === "404.png";
|
||||
|
||||
return {
|
||||
index,
|
||||
name: apiCollection.name,
|
||||
length: apiCollection.items,
|
||||
previewimage: `${basePath}/api/v1/image/${apiCollection.image}`,
|
||||
previewimage: imageIsMissing
|
||||
? "/404.gif"
|
||||
: `${basePath}/api/v1/image/${btoa(image).replace(/=+$/, '')}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ const fetchCollections = async () => {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
|
||||
const response = await api.musicBackendCollections(offset.value, limit.value);
|
||||
let songs = mapApiToCollectionPreview(response.data.songs)
|
||||
const response = await api.musicBackendSearchCollections("", limit.value, offset.value);
|
||||
let songs = mapApiToCollectionPreview(response.data.collections)
|
||||
collections.value = [...collections.value, ...songs];
|
||||
offset.value += limit.value;
|
||||
|
||||
@@ -46,8 +46,8 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="flex-1 text-center flex flex-col h-full overflow-y-scroll">
|
||||
<div class="flex flex-col overflow-scroll collection-container">
|
||||
<main class="flex-1 text-center flex flex-col h-full overflow-y-hidden">
|
||||
<div class="flex flex-col overflow-y-scroll collection-container">
|
||||
<CollectionListItem v-for="(collection, index) in collections" :key="index" :collection="collection" />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
|
||||
<main class="flex-1 flex-col overflow-hidden">
|
||||
<div class="flex-1 flex-col h-full overflow-y-scroll song-container">
|
||||
<main class="flex-1 flex-col">
|
||||
<div class="flex-1 flex-col h-full overflow-y-hidden song-container">
|
||||
<p class="p-1 rounded-full backdrop--light shadow-xl text-center">History</p>
|
||||
<p v-if="songs.length === 0">No songs found</p>
|
||||
<SongItem v-for="(song, index) in songs" :key="song.hash" :song="song" />
|
||||
|
||||
@@ -91,7 +91,7 @@ function reset() {
|
||||
<div>
|
||||
<p>{{ userStore.user.value.name }}</p>
|
||||
<p>{{ userStore.user.value.endpoint == "" ? 'Not Connected' : 'Connected' }}</p>
|
||||
<p>Sharing: <button @click="share" class="border bordercolor rounded-lg p-0.5">{{ userStore.user.value.share
|
||||
<p>Sharing: <button @click="userStore.user.value.share" class="border bordercolor rounded-lg p-0.5">{{ userStore.user.value.share
|
||||
}}</button></p>
|
||||
<button @click="getMe" class="border bordercolor rounded-lg p-0.5"> Refresh
|
||||
</button>
|
||||
|
||||
@@ -27,11 +27,6 @@ async function fetchActiveSearch(term: string) {
|
||||
|
||||
const songData = mapApiToSongs(response.data.songs)
|
||||
|
||||
songData.forEach((song: Song) => {
|
||||
song.previewimage = `${userStore.baseUrl.value}/api/v1/images/${song.previewimage}`
|
||||
song.url = `${userStore.baseUrl.value}/api/v1/audio/${song.url}`
|
||||
})
|
||||
|
||||
activesongs.value = songData
|
||||
|
||||
if (response.data.artist) artists.value = [response.data.artist]
|
||||
|
||||
@@ -340,16 +340,29 @@ func getRecent(db *sql.DB, limit, offset int) ([]Song, error) {
|
||||
}
|
||||
|
||||
func getSearch(db *sql.DB, q string, limit, offset int) (ActiveSearch, error) {
|
||||
rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap WHERE MD5Hash FROM Songs WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset)
|
||||
rows, err := db.Query(
|
||||
`
|
||||
SELECT
|
||||
BeatmapId,
|
||||
MD5Hash,
|
||||
Title,
|
||||
Artist,
|
||||
Creator,
|
||||
Folder,
|
||||
File,
|
||||
Audio,
|
||||
TotalTime
|
||||
FROM Beatmap WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?
|
||||
`, "%"+q+"%", "%"+q+"%", limit, offset)
|
||||
if err != nil {
|
||||
return ActiveSearch{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
_, err = scanSongs(rows)
|
||||
s, err := scanSongs(rows)
|
||||
if err != nil {
|
||||
return ActiveSearch{}, err
|
||||
}
|
||||
return ActiveSearch{}, nil
|
||||
return ActiveSearch{Songs: s}, nil
|
||||
}
|
||||
|
||||
func getArtists(db *sql.DB, q string, limit, offset int) ([]Artist, error) {
|
||||
@@ -471,13 +484,16 @@ func getCollectionByName(db *sql.DB, limit, offset int, name string) (Collection
|
||||
func getCollections(db *sql.DB, q string, limit, offset int) ([]CollectionPreview, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT
|
||||
c.Name, COUNT(b.MD5Hash), b.Folder, b.File
|
||||
c.Name,
|
||||
COUNT(b.MD5Hash) AS Count,
|
||||
MIN(b.Folder) AS Folder,
|
||||
MIN(b.File) AS File
|
||||
FROM Collection c
|
||||
Join Beatmap b ON c.MD5Hash = b.MD5Hash
|
||||
JOIN Beatmap b ON c.MD5Hash = b.MD5Hash
|
||||
WHERE c.Name LIKE ?
|
||||
GROUP BY c.NAME
|
||||
GROUP BY c.Name
|
||||
LIMIT ?
|
||||
OFFSET ?;`, "%"+q+"%", limit, offset)
|
||||
OFFSET ?`, "%"+q+"%", limit, offset)
|
||||
if err != nil {
|
||||
return []CollectionPreview{}, err
|
||||
}
|
||||
|
||||
@@ -110,7 +110,6 @@ func (s *Server) Recent(ctx context.Context, req *v1.RecentRequest) (*v1.RecentR
|
||||
|
||||
func (s *Server) Favorite(ctx context.Context, req *v1.FavoriteRequest) (*v1.FavoriteResponse, error) {
|
||||
|
||||
|
||||
limit := defaultLimit(int(req.Limit))
|
||||
offset := int(req.Offset)
|
||||
favorites, err := getFavorites(s.Db, req.Query, limit, offset)
|
||||
@@ -125,10 +124,9 @@ func (s *Server) Favorite(ctx context.Context, req *v1.FavoriteRequest) (*v1.Fav
|
||||
}
|
||||
|
||||
func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v1.CollectionResponse, error) {
|
||||
|
||||
|
||||
limit := defaultLimit(int(req.Limit))
|
||||
offset := int(req.Offset)
|
||||
|
||||
|
||||
name := req.Name
|
||||
if name != "" {
|
||||
@@ -143,12 +141,13 @@ func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v
|
||||
Name: c.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
fmt.Println(limit, offset, req.Index)
|
||||
c, err := getCollection(s.Db, limit, offset, int(req.Index))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to fetch collection with index: %d", req.Index))
|
||||
}
|
||||
fmt.Println(c)
|
||||
return &v1.CollectionResponse{
|
||||
Songs: toProtoSongs(c.Songs),
|
||||
Items: int32(c.Items),
|
||||
@@ -163,10 +162,8 @@ func (s *Server) Search(ctx context.Context, req *v1.SearchSharedRequest) (*v1.S
|
||||
return nil, status.Error(codes.InvalidArgument, "query cant be empty")
|
||||
}
|
||||
|
||||
|
||||
limit := defaultLimit(int(req.Limit))
|
||||
offset := int(req.Offset)
|
||||
|
||||
|
||||
search, err := getSearch(s.Db, q, limit, offset)
|
||||
if err != nil {
|
||||
@@ -182,15 +179,11 @@ func (s *Server) Search(ctx context.Context, req *v1.SearchSharedRequest) (*v1.S
|
||||
|
||||
func (s *Server) SearchCollections(ctx context.Context, req *v1.SearchCollectionRequest) (*v1.SearchCollectionResponse, error) {
|
||||
q := req.Query
|
||||
if q == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "query is required")
|
||||
}
|
||||
|
||||
|
||||
limit := defaultLimit(int(req.Limit))
|
||||
offset := int(req.Offset)
|
||||
|
||||
|
||||
fmt.Println(req)
|
||||
preview, err := getCollections(s.Db, q, limit, offset)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.23 AS builder
|
||||
FROM golang:1.24.3 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
Reference in New Issue
Block a user