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",
|
"@types/node": "^22.9.3",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
"npm-run-all2": "^7.0.1",
|
"npm-run-all2": "^7.0.1",
|
||||||
|
"postcss": "^8.5.3",
|
||||||
|
"tailwindcss": "^4.1.7",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^6.0.1",
|
"vite": "^6.0.1",
|
||||||
"vite-plugin-vue-devtools": "^7.6.5",
|
"vite-plugin-vue-devtools": "^7.6.5",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SongItem from '../components/SongItem.vue'
|
import SongItem from '../components/SongItem.vue'
|
||||||
import type { Song } from '../script/types'
|
import { type Song, mapApiToCollection } from '../script/types'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import CollectionListItem from '../components/CollectionListItem.vue'
|
import CollectionListItem from '../components/CollectionListItem.vue'
|
||||||
import { useAudio } from '@/composables/useAudio'
|
import { useAudio } from '@/composables/useAudio'
|
||||||
@@ -13,24 +13,86 @@ const { musicApi } = useApi()
|
|||||||
|
|
||||||
const songs = ref<Song[]>([])
|
const songs = ref<Song[]>([])
|
||||||
const name = ref('name')
|
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 {
|
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 => {
|
if (offset.value === 0) {
|
||||||
song.previewimage = `${base}/api/v1/images/${song.previewimage}`
|
name.value = col.name
|
||||||
song.url = `${base}/api/v1/audio/${song.url}`
|
songs.value = col.songs
|
||||||
})
|
} else {
|
||||||
|
songs.value.push(...col.songs)
|
||||||
name.value = response.name
|
}
|
||||||
songs.value = response.songs
|
|
||||||
|
|
||||||
audioStore.setCollection(songs.value)
|
audioStore.setCollection(songs.value)
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching collection:', error)
|
if (col.songs.length < limit) {
|
||||||
|
hasMore.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset.value += limit
|
||||||
|
} catch (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>
|
</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 }>();
|
const props = defineProps<{ collection: CollectionPreview }>();
|
||||||
|
|
||||||
|
|
||||||
function hi() {
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<RouterLink @click.navtive="hi" :to="'/collection/' + props.collection.index">
|
<RouterLink :to="'/collection/' + props.collection.index +1">
|
||||||
<div class=" border bordercolor rounded-lg flex">
|
<div class=" border bordercolor rounded-lg flex">
|
||||||
<img class="h-20 w-20 m-2 rounded-lg" :src="props.collection.previewimage" loading="lazy" />
|
<img class="h-20 w-20 m-2 rounded-lg" :src="props.collection.previewimage" loading="lazy" />
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ let userInstance: ReturnType<typeof createUser> | null = null;
|
|||||||
function createUser() {
|
function createUser() {
|
||||||
const user = ref<Me | null>(null);
|
const user = ref<Me | null>(null);
|
||||||
const baseUrl = ref(import.meta.env.VITE_BACKEND_URL || 'http://localhost:8080');
|
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) {
|
function saveUser(u: Me | null) {
|
||||||
localStorage.setItem('activeUser', JSON.stringify(u));
|
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 = {
|
export type Song = {
|
||||||
hash: string;
|
hash: string;
|
||||||
@@ -36,16 +36,36 @@ export type CollectionPreview = {
|
|||||||
previewimage: string;
|
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(
|
export function mapToCollectionPreview(
|
||||||
apiCollection: v1CollectionPreview,
|
apiCollection: v1CollectionPreview,
|
||||||
index: number
|
index: number
|
||||||
): CollectionPreview {
|
): CollectionPreview {
|
||||||
|
const image = apiCollection.image;
|
||||||
|
const imageIsMissing = !image || image === "404.png";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index,
|
index,
|
||||||
name: apiCollection.name,
|
name: apiCollection.name,
|
||||||
length: apiCollection.items,
|
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;
|
if (isLoading.value) return;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
const response = await api.musicBackendCollections(offset.value, limit.value);
|
const response = await api.musicBackendSearchCollections("", limit.value, offset.value);
|
||||||
let songs = mapApiToCollectionPreview(response.data.songs)
|
let songs = mapApiToCollectionPreview(response.data.collections)
|
||||||
collections.value = [...collections.value, ...songs];
|
collections.value = [...collections.value, ...songs];
|
||||||
offset.value += limit.value;
|
offset.value += limit.value;
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="flex-1 text-center flex flex-col h-full overflow-y-scroll">
|
<main class="flex-1 text-center flex flex-col h-full overflow-y-hidden">
|
||||||
<div class="flex flex-col overflow-scroll collection-container">
|
<div class="flex flex-col overflow-y-scroll collection-container">
|
||||||
<CollectionListItem v-for="(collection, index) in collections" :key="index" :collection="collection" />
|
<CollectionListItem v-for="(collection, index) in collections" :key="index" :collection="collection" />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<main class="flex-1 flex-col overflow-hidden">
|
<main class="flex-1 flex-col">
|
||||||
<div class="flex-1 flex-col h-full overflow-y-scroll song-container">
|
<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 class="p-1 rounded-full backdrop--light shadow-xl text-center">History</p>
|
||||||
<p v-if="songs.length === 0">No songs found</p>
|
<p v-if="songs.length === 0">No songs found</p>
|
||||||
<SongItem v-for="(song, index) in songs" :key="song.hash" :song="song" />
|
<SongItem v-for="(song, index) in songs" :key="song.hash" :song="song" />
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ function reset() {
|
|||||||
<div>
|
<div>
|
||||||
<p>{{ userStore.user.value.name }}</p>
|
<p>{{ userStore.user.value.name }}</p>
|
||||||
<p>{{ userStore.user.value.endpoint == "" ? 'Not Connected' : 'Connected' }}</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></p>
|
||||||
<button @click="getMe" class="border bordercolor rounded-lg p-0.5"> Refresh
|
<button @click="getMe" class="border bordercolor rounded-lg p-0.5"> Refresh
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ async function fetchActiveSearch(term: string) {
|
|||||||
|
|
||||||
const songData = mapApiToSongs(response.data.songs)
|
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
|
activesongs.value = songData
|
||||||
|
|
||||||
if (response.data.artist) artists.value = [response.data.artist]
|
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) {
|
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 {
|
if err != nil {
|
||||||
return ActiveSearch{}, err
|
return ActiveSearch{}, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
_, err = scanSongs(rows)
|
s, err := scanSongs(rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ActiveSearch{}, err
|
return ActiveSearch{}, err
|
||||||
}
|
}
|
||||||
return ActiveSearch{}, nil
|
return ActiveSearch{Songs: s}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getArtists(db *sql.DB, q string, limit, offset int) ([]Artist, error) {
|
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) {
|
func getCollections(db *sql.DB, q string, limit, offset int) ([]CollectionPreview, error) {
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT
|
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
|
FROM Collection c
|
||||||
Join Beatmap b ON c.MD5Hash = b.MD5Hash
|
JOIN Beatmap b ON c.MD5Hash = b.MD5Hash
|
||||||
WHERE c.Name LIKE ?
|
WHERE c.Name LIKE ?
|
||||||
GROUP BY c.NAME
|
GROUP BY c.Name
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
OFFSET ?;`, "%"+q+"%", limit, offset)
|
OFFSET ?`, "%"+q+"%", limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []CollectionPreview{}, err
|
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) {
|
func (s *Server) Favorite(ctx context.Context, req *v1.FavoriteRequest) (*v1.FavoriteResponse, error) {
|
||||||
|
|
||||||
|
|
||||||
limit := defaultLimit(int(req.Limit))
|
limit := defaultLimit(int(req.Limit))
|
||||||
offset := int(req.Offset)
|
offset := int(req.Offset)
|
||||||
favorites, err := getFavorites(s.Db, req.Query, limit, offset)
|
favorites, err := getFavorites(s.Db, req.Query, limit, offset)
|
||||||
@@ -129,7 +128,6 @@ func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v
|
|||||||
limit := defaultLimit(int(req.Limit))
|
limit := defaultLimit(int(req.Limit))
|
||||||
offset := int(req.Offset)
|
offset := int(req.Offset)
|
||||||
|
|
||||||
|
|
||||||
name := req.Name
|
name := req.Name
|
||||||
if name != "" {
|
if name != "" {
|
||||||
c, err := getCollectionByName(s.Db, limit, offset, name)
|
c, err := getCollectionByName(s.Db, limit, offset, name)
|
||||||
@@ -143,12 +141,13 @@ func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v
|
|||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
fmt.Println(limit, offset, req.Index)
|
||||||
c, err := getCollection(s.Db, limit, offset, int(req.Index))
|
c, err := getCollection(s.Db, limit, offset, int(req.Index))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to fetch collection with index: %d", req.Index))
|
return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to fetch collection with index: %d", req.Index))
|
||||||
}
|
}
|
||||||
|
fmt.Println(c)
|
||||||
return &v1.CollectionResponse{
|
return &v1.CollectionResponse{
|
||||||
Songs: toProtoSongs(c.Songs),
|
Songs: toProtoSongs(c.Songs),
|
||||||
Items: int32(c.Items),
|
Items: int32(c.Items),
|
||||||
@@ -163,11 +162,9 @@ func (s *Server) Search(ctx context.Context, req *v1.SearchSharedRequest) (*v1.S
|
|||||||
return nil, status.Error(codes.InvalidArgument, "query cant be empty")
|
return nil, status.Error(codes.InvalidArgument, "query cant be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
limit := defaultLimit(int(req.Limit))
|
limit := defaultLimit(int(req.Limit))
|
||||||
offset := int(req.Offset)
|
offset := int(req.Offset)
|
||||||
|
|
||||||
|
|
||||||
search, err := getSearch(s.Db, q, limit, offset)
|
search, err := getSearch(s.Db, q, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -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) {
|
func (s *Server) SearchCollections(ctx context.Context, req *v1.SearchCollectionRequest) (*v1.SearchCollectionResponse, error) {
|
||||||
q := req.Query
|
q := req.Query
|
||||||
if q == "" {
|
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "query is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
limit := defaultLimit(int(req.Limit))
|
limit := defaultLimit(int(req.Limit))
|
||||||
offset := int(req.Offset)
|
offset := int(req.Offset)
|
||||||
|
|
||||||
|
fmt.Println(req)
|
||||||
preview, err := getCollections(s.Db, q, limit, offset)
|
preview, err := getCollections(s.Db, q, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.23 AS builder
|
FROM golang:1.24.3 AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user