fix alligment, remove scrollbars, clean up layout to support desktop properly

This commit is contained in:
2025-05-25 03:14:30 +02:00
parent 8e5adaec96
commit 6058a96258
12 changed files with 80 additions and 51 deletions

View File

@@ -3,6 +3,7 @@ import { RouterLink, RouterView } from 'vue-router'
import NowPlaying from '@/components/NowPlaying.vue' import NowPlaying from '@/components/NowPlaying.vue'
import NowPlayingView from '@/views/NowPlayingView.vue' import NowPlayingView from '@/views/NowPlayingView.vue'
import MenuView from '@/views/MenuView.vue' import MenuView from '@/views/MenuView.vue'
import HistoryView from '@/views/HistoryView.vue'
import Footer from '@/components/Footer.vue' import Footer from '@/components/Footer.vue'
import { ref, onMounted, watch, onUnmounted } from 'vue' import { ref, onMounted, watch, onUnmounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
@@ -37,8 +38,6 @@ function loadColors() {
document.documentElement.style.setProperty('--information-color', localStorage.getItem('infoColor') || '#ec4899'); document.documentElement.style.setProperty('--information-color', localStorage.getItem('infoColor') || '#ec4899');
document.documentElement.style.setProperty('--border-color', localStorage.getItem('borderColor') || '#ec4899'); document.documentElement.style.setProperty('--border-color', localStorage.getItem('borderColor') || '#ec4899');
console.log(localStorage.getItem('bgColor'));
} }
loadColors(); loadColors();
@@ -71,17 +70,17 @@ onUnmounted(() => {
</div> </div>
<div v-else class="flex flex-col h-screen max-h-screen wrapper info text-xl"> <div v-else class="flex flex-col h-screen max-h-screen wrapper info text-xl">
<main class="flex flex-1 w-full h-full overflow-y-scroll"> <main class="flex flex-1 w-full h-full overflow-y-hidden">
<aside class="w-1/12 bg-primary p-4"> <aside class="w-1/12 bg-primary p-4 overflow-y-scroll">
<p>Sidebar content</p> <HistoryView />
</aside> </aside>
<section class="flex-1 overflow-y-scroll p-4"> <section class="flex-1 overflow-y-hidden">
<RouterView /> <RouterView />
</section> </section>
<section class="w-1/5 p-4"> <section class="w-1/5 overflow-y-scroll flex flex-col">
<NowPlayingView /> <NowPlayingView />
</section> </section>
</main> </main>

View File

@@ -16,7 +16,7 @@ onMounted(() => {
<template> <template>
<div> <div>
<hr> <hr>
<div class="relative wrapper p-1 grow action"> <div class="relative wrapper p-1 action">
<img :src="encodeURI(bgimg + '?h=150&w=400')" class="w-full absolute top-0 left-0 right-0 h-full" <img :src="encodeURI(bgimg + '?h=150&w=400')" class="w-full absolute top-0 left-0 right-0 h-full"
:style="{ 'filter': 'blur(2px)', 'opacity': '0.5' }" alt="Background Image" /> :style="{ 'filter': 'blur(2px)', 'opacity': '0.5' }" alt="Background Image" />

View File

@@ -13,7 +13,6 @@ const audioStore = useAudio();
function updateSong() { function updateSong() {
let updated = props.song; let updated = props.song;
console.log("updating song:", updated)
audioStore.setSong(updated) audioStore.setSong(updated)
} }
</script> </script>
@@ -21,19 +20,19 @@ function updateSong() {
<template> <template>
<div @click="updateSong" :style="{ borderColor: border }" class="m-1 border bordercolor rounded-lg flex"> <div @click="updateSong" :style="{ borderColor: border }" class="m-1 md:text-xl border bordercolor rounded-lg flex">
<img class="h-14 w-14 m-1 rounded-lg" <img class="h-14 w-14 md:w-24 md:h-24 m-1 rounded-lg"
:src="encodeURI(props.song?.previewimage ? props.song?.previewimage + '?h=56&w=56' : '/default-bg.png')" :src="encodeURI(props.song?.previewimage ? props.song?.previewimage + '?h=56&w=56' : '/default-bg.png')"
loading="lazy" /> loading="lazy" />
<div class="flex flex-col overflow-hidden"> <div class="flex flex-col overflow-hidden">
<p :style="{ color: info }" class="text-nowrap text-ellipsis overflow-hidden text-base info"> <p :style="{ color: info }" class="text-nowrap text-ellipsis overflow-hidden text-base info">
<slot name="songName">{{ props.song?.name ?? 'Title' }}</slot> <slot name="songName">{{ props.song?.name ? props.song?.name : 'Unknown Title' }}</slot>
</p> </p>
<h5 :style="{ color: action }" class="action text-sm text-nowrap text-ellipsis overflow-hidden text-base"> <h5 :style="{ color: action }" class="action text-sm text-nowrap text-ellipsis overflow-hidden text-base">
<slot name="artist">{{ props.song?.artist ?? 'Artist' }}</slot> <slot name="artist">{{ props.song?.artist ? props.song.artist : 'Unknown Artist' }}</slot>
</h5> </h5>
<h5 :style="{ color: action }" class="action text-sm"> <h5 :style="{ color: action }" class="action text-sm">
<slot name="length">{{ Math.floor(props.song?.length ?? 0 / 60000) }}:{{ Math.floor((props.song?.length ?? 0 / <slot name="length">{{ Math.floor(props.song?.length / 60000 ?? 0) }}:{{ Math.floor((props.song?.length ?? 0 /
1000) 1000)
% 60).toString().padStart(2, '0') }}</slot> % 60).toString().padStart(2, '0') }}</slot>
</h5> </h5>

View File

@@ -22,6 +22,7 @@ function createAudio() {
repeat: ref(false), repeat: ref(false),
activeSongs: ref<Song[] | null>([]), activeSongs: ref<Song[] | null>([]),
currentSong: ref<Song | null>(null), currentSong: ref<Song | null>(null),
recentlyPlayed: ref(new Map<string, Song>()),
} }
function saveToLocalStorage(key: string, data: any) { function saveToLocalStorage(key: string, data: any) {
@@ -34,10 +35,16 @@ function createAudio() {
} }
function setSong(song: Song | null) { function setSong(song: Song | null) {
console.log(song)
if (!song) return if (!song) return
state.currentSong.value = song state.currentSong.value = song
const map = state.recentlyPlayed.value;
saveToLocalStorage('lastPlayedSong', song) saveToLocalStorage('lastPlayedSong', song)
if (map.has(song.hash)) {
map.delete(song.hash);
}
map.set(song.hash, song);
audioElement.pause() audioElement.pause()
audioElement.src = song.url audioElement.src = song.url
@@ -66,6 +73,11 @@ function toggleNext() {
if (!state.activeSongs.value || !state.currentSong.value) return; if (!state.activeSongs.value || !state.currentSong.value) return;
const songs = state.activeSongs.value; const songs = state.activeSongs.value;
if(state.shuffle.value){
setSong(songs[Math.floor(Math.random() * songs.length)]);
}
const currentHash = state.currentSong.value.hash; const currentHash = state.currentSong.value.hash;
const currentIndex = songs.findIndex(song => song.hash === currentHash); const currentIndex = songs.findIndex(song => song.hash === currentHash);
@@ -99,8 +111,13 @@ function togglePrevious() {
if (state.repeat.value) { if (state.repeat.value) {
audioElement.currentTime = 0 audioElement.currentTime = 0
audioElement.play() audioElement.play()
return
} }
toggleNext();
} }
} }
function formatTime(seconds: number): string { function formatTime(seconds: number): string {
@@ -109,11 +126,8 @@ function togglePrevious() {
return `${min}:${sec}` return `${min}:${sec}`
} }
function updateTime() { function updateTime(value: number) {
const slider = document.getElementById('audio-slider') as HTMLInputElement if (value) audioElement.currentTime = (Number(value) / 100) * audioElement.duration
if (slider) {
audioElement.currentTime = (Number(slider.value) / 100) * audioElement.duration
}
} }
async function loadInitialSong() { async function loadInitialSong() {

View File

@@ -19,9 +19,7 @@ const fetchCollections = async () => {
isLoading.value = true; isLoading.value = true;
const response = await api.musicBackendCollections(offset.value, limit.value); const response = await api.musicBackendCollections(offset.value, limit.value);
console.log(response.data.songs.length)
let songs = mapApiToCollectionPreview(response.data.songs) let songs = mapApiToCollectionPreview(response.data.songs)
console.log(songs)
collections.value = [...collections.value, ...songs]; collections.value = [...collections.value, ...songs];
offset.value += limit.value; offset.value += limit.value;
@@ -48,7 +46,7 @@ onMounted(async () => {
</script> </script>
<template> <template>
<main class="flex-1 text-center flex flex-col h-full overflow-scroll"> <main class="flex-1 text-center flex flex-col h-full overflow-y-scroll">
<div class="flex flex-col overflow-scroll collection-container"> <div class="flex flex-col overflow-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>

View File

@@ -3,7 +3,7 @@ import SongItem from '../components/SongItem.vue'
</script> </script>
<template> <template>
<main class="flex-1 flex-col overflow-scroll"> <main class="flex-1 flex-col overflow-y-scroll">
<p>Coming Soon...</p> <p>Coming Soon...</p>
</main> </main>
</template> </template>

View File

@@ -0,0 +1,21 @@
<template>
<main class="flex-1 flex-col overflow-hidden">
<div class="flex-1 flex-col h-full overflow-y-scroll 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" />
</div>
</main>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import SongItem from '../components/SongItem.vue'
import { useAudio } from '@/composables/useAudio';
const audioStore = useAudio();
const songs = computed(() => Array.from(audioStore.recentlyPlayed.value?.values()).reverse() || [])
</script>

View File

@@ -32,8 +32,6 @@ function save(bg: string | null, main: string | null, info: string | null, borde
localStorage.setItem('actionColor', main ?? actionColor.value); localStorage.setItem('actionColor', main ?? actionColor.value);
localStorage.setItem('infoColor', info ?? infoColor.value); localStorage.setItem('infoColor', info ?? infoColor.value);
localStorage.setItem('borderColor', border ?? borderColor.value); localStorage.setItem('borderColor', border ?? borderColor.value);
console.log("bg", bgColor.value, "action:", actionColor.value, "info", infoColor.value, "border", borderColor.value)
} }
async function getMe() { async function getMe() {
@@ -44,12 +42,10 @@ async function getMe() {
console.log("redirect detected"); console.log("redirect detected");
} }
console.log(data)
if (data.id === null || data.id === undefined || Object.keys(data).length === 0) { if (data.id === null || data.id === undefined || Object.keys(data).length === 0) {
return return
} }
console.log("active user: ", data.name)
userStore.setUser(data); userStore.setUser(data);
userStore.baseUrl.value(data.endpoint); userStore.baseUrl.value(data.endpoint);
@@ -86,7 +82,7 @@ function reset() {
</div> </div>
</header> </header>
<main class="flex-1 flex flex-col overflow-scroll"> <main class="flex-1 flex flex-col h-full overflow-y-scroll">
<input @change="update" type="text" id="url-input" :value="userStore.baseUrl.value" disabled /> <input @change="update" type="text" id="url-input" :value="userStore.baseUrl.value" disabled />
<br> <br>
<button v-if="!userStore.user.value" @click="getMe" class="border bordercolor rounded-lg p-0.5">{{ loginStatus }}</button> <button v-if="!userStore.user.value" @click="getMe" class="border bordercolor rounded-lg p-0.5">{{ loginStatus }}</button>
@@ -128,10 +124,9 @@ function reset() {
class="appearance-none w-8 h-8 border-2 p-0 overflow-hidden cursor-pointer"> class="appearance-none w-8 h-8 border-2 p-0 overflow-hidden cursor-pointer">
</div> </div>
</div> </div>
<div class="w-full p-2"> <div class="w-full p-2">
<p>Current</p> <p>Current</p>
<SongItem :song="audioStore.currentSong" /> <SongItem :song="audioStore.currentSong.value" />
</div> </div>
<div class="w-full p-2 bg-black"> <div class="w-full p-2 bg-black">
@@ -139,14 +134,14 @@ function reset() {
class="border rounded-lg p-0.5" @click="save('#000000', '#5e2d8f', '#57db5d', '#b3002d')">Choose class="border rounded-lg p-0.5" @click="save('#000000', '#5e2d8f', '#57db5d', '#b3002d')">Choose
</button> </button>
</p> </p>
<SongItem :song="audioStore.currentSong" :border="'#b3002d'" :action="'#5e2d8f'" :info="'#57db5d'" /> <SongItem :song="audioStore.currentSong.value" :border="'#b3002d'" :action="'#5e2d8f'" :info="'#57db5d'" />
</div> </div>
<div class="w-full p-2" style="background-color: #1c1719"> <div class="w-full p-2" style="background-color: #1c1719">
<p class="flex-1 flex justify-between" style=" color : #ec4889">Default<button style="border-color : #ec4889" <p class="flex-1 flex justify-between" style=" color : #ec4889">Default<button style="border-color : #ec4889"
class="border rounded-lg p-0.5" @click="save('#1c1719', '#eab308', '#ec4889', '#ec4889')">Choose class="border rounded-lg p-0.5" @click="save('#1c1719', '#eab308', '#ec4889', '#ec4889')">Choose
</button> </button>
</p> </p>
<SongItem :song="audioStore.currentSong" :border="'#ec4889'" :info="'#ec4889'" :action="'#eab308'" /> <SongItem :song="audioStore.currentSong.value" :border="'#ec4889'" :info="'#ec4889'" :action="'#eab308'" />
</div> </div>
<div class="w-full p-2" style="background-color: #ff4c4c"> <div class="w-full p-2" style="background-color: #ff4c4c">
@@ -155,7 +150,7 @@ function reset() {
<button style="border-color: #ffffff" class="border rounded-lg p-0.5" <button style="border-color: #ffffff" class="border rounded-lg p-0.5"
@click="save('#ff4c4c', '#ffcc00', '#ffffff', '#ffffff')">Choose</button> @click="save('#ff4c4c', '#ffcc00', '#ffffff', '#ffffff')">Choose</button>
</p> </p>
<SongItem :song="audioStore.currentSong" :border="'#ffffff'" :info="'#ffffff'" :action="'#ffcc00'" /> <SongItem :song="audioStore.currentSong.value" :border="'#ffffff'" :info="'#ffffff'" :action="'#ffcc00'" />
</div> </div>
<div class="w-full p-2" style="background-color: #003d00"> <div class="w-full p-2" style="background-color: #003d00">
@@ -164,7 +159,7 @@ function reset() {
<button style="border-color: #e0f8d8" class="border rounded-lg p-0.5" <button style="border-color: #e0f8d8" class="border rounded-lg p-0.5"
@click="save('#003d00', '#a8d5a2', '#e0f8d8', '#e0f8d8')">Choose</button> @click="save('#003d00', '#a8d5a2', '#e0f8d8', '#e0f8d8')">Choose</button>
</p> </p>
<SongItem :song="audioStore.currentSong" :border="'#e0f8d8'" :info="'#e0f8d8'" :action="'#a8d5a2'" /> <SongItem :song="audioStore.currentSong.value" :border="'#e0f8d8'" :info="'#e0f8d8'" :action="'#a8d5a2'" />
</div> </div>
<div class="w-full p-2" style="background-color: #00274d"> <div class="w-full p-2" style="background-color: #00274d">
@@ -173,7 +168,7 @@ function reset() {
<button style="border-color: #00ffff" class="border rounded-lg p-0.5" <button style="border-color: #00ffff" class="border rounded-lg p-0.5"
@click="save('#00274d', '#0099ff', '#00ffff', '#00ffff')">Choose</button> @click="save('#00274d', '#0099ff', '#00ffff', '#00ffff')">Choose</button>
</p> </p>
<SongItem :song="audioStore.currentSong" :border="'#00ffff'" :info="'#00ffff'" :action="'#0099ff'" /> <SongItem :song="audioStore.currentSong.value" :border="'#00ffff'" :info="'#00ffff'" :action="'#0099ff'" />
</div> </div>
</main> </main>

View File

@@ -10,10 +10,10 @@ function isActive(path: string) {
</script> </script>
<template> <template>
<main class="flex-1 flex flex-col h-full overflow-y-scroll"> <main class="flex-1 flex flex-col h-full overflow-y-scroll md:overflow-hidden">
<header v-show="true"> <header v-show="true">
<div class="wrapper"> <div class="wrapper">
<nav class="flex justify-start my-2 mx-1 space-x-1 overflow-x-scroll flex-nowrap text-nowrap"> <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="/"><i class="fa-solid fa-arrow-left"></i> <RouterLink class="p-1 rounded-full backdrop--light shadow-xl" to="/"><i class="fa-solid fa-arrow-left"></i>
</RouterLink> </RouterLink>
<RouterLink :class="`p-1 rounded-full backdrop--light shadow-xl ${isActive('/')}`" to="/menu/recent">Recently <RouterLink :class="`p-1 rounded-full backdrop--light shadow-xl ${isActive('/')}`" to="/menu/recent">Recently

View File

@@ -74,8 +74,9 @@ const bgimg = computed(() => audioStore.currentSong.value?.previewimage || '/def
<input <input
class="w-full appearance-none h-2 rounded-full bg-yellow-200 bg-opacity-20 accent-yellow-600 outline-none" class="w-full appearance-none h-2 rounded-full bg-yellow-200 bg-opacity-20 accent-yellow-600 outline-none"
type="range" type="range"
@change="audioStore.updateTime" @input="event => audioStore.updateTime(Number(event.target.value))"
:max="100" :max="100"
step="0.001"
:value="audioStore.percentDone.value" :value="audioStore.percentDone.value"
/> />
</div> </div>

View File

@@ -2,7 +2,7 @@
import SongItem from '../components/SongItem.vue' import SongItem from '../components/SongItem.vue'
import { type Song, type CollectionPreview, mapApiToSongs } from '../script/types' import { type Song, type CollectionPreview, mapApiToSongs } from '../script/types'
import { ref, onMounted } from 'vue' import { ref, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useAudio } from '@/composables/useAudio'; import { useAudio } from '@/composables/useAudio';
import { useUser } from '@/composables/useUser'; import { useUser } from '@/composables/useUser';
@@ -16,6 +16,7 @@ const api = musicApi()
const songs = ref<Song[]>([]); const songs = ref<Song[]>([]);
const name = ref('name'); const name = ref('name');
const containerRef = ref<HTMLElement | null>(null);
const limit = ref(100); const limit = ref(100);
const offset = ref(0); const offset = ref(0);
@@ -46,7 +47,9 @@ const fetchRecent = async () => {
onMounted(async () => { onMounted(async () => {
await fetchRecent(); await fetchRecent();
const container = document.querySelector('.song-container'); await nextTick();
const container = containerRef.value;
if (container) { if (container) {
container.addEventListener('scroll', async () => { container.addEventListener('scroll', async () => {
const scrollTop = container.scrollTop; const scrollTop = container.scrollTop;
@@ -64,11 +67,10 @@ onMounted(async () => {
</script> </script>
<template> <template>
<div
<main class="flex-1 flex-col overflow-scroll"> ref="containerRef"
<div class="flex-1 flex-col h-full overflow-scroll song-container"> class="flex-1 flex-col overflow-y-scroll song-container"
>
<SongItem v-for="(song, index) in songs" :key="index" :song="song" /> <SongItem v-for="(song, index) in songs" :key="index" :song="song" />
</div> </div>
</main>
</template> </template>

View File

@@ -108,19 +108,19 @@ watch(searchInput, async (val) => {
</div> </div>
</header> </header>
<main class="flex flex-col flex-1 w-full h-full overflow-scroll"> <main class="flex flex-col flex-1 w-full h-full">
<div class="relative"> <div class="relative">
<input <input
v-model="searchInput" v-model="searchInput"
placeholder="Type to Search..." placeholder="Type to Search..."
class="flex-1 max-h-12 search border bordercolor accent-pink-800 bg-yellow-300 bg-opacity-20 rounded-lg m-2 p-2" class="w-full flex-1 max-h-12 search border bordercolor accent-pink-800 bg-yellow-300 bg-opacity-20 rounded-lg p-2"
/> />
<div class="absolute h-16 right-4 flex flex-col justify-center cursor-pointer" @click="emptySearch"> <div class="absolute top-4 right-4 flex flex-col justify-center cursor-pointer" @click="emptySearch">
<i class="far fa-times-circle opacity-50"></i> <i class="far fa-times-circle opacity-50"></i>
</div> </div>
</div> </div>
<div class="relative flex flex-col w-full h-full overflow-scroll"> <div class="relative flex flex-col w-full h-full overflow-y-scroll">
<div v-if="showSearch" class="absolute w-full text-center search-recommendations z-20"> <div v-if="showSearch" class="absolute w-full text-center search-recommendations z-20">
<ActiveSearchList :songs="activesongs" :artist="artists" :search="searchTerm" /> <ActiveSearchList :songs="activesongs" :artist="artists" :search="searchTerm" />
</div> </div>