albums
This commit is contained in:
123
src/views/AlbumsView.vue
Normal file
123
src/views/AlbumsView.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAlbumStore } from '../stores/albums'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
import Dialog from 'primevue/dialog'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Textarea from 'primevue/textarea'
|
||||
import Button from 'primevue/button'
|
||||
import Image from 'primevue/image'
|
||||
const router = useRouter()
|
||||
const albumStore = useAlbumStore()
|
||||
const { albums, loading } = storeToRefs(albumStore)
|
||||
|
||||
const showCreateDialog = ref(false)
|
||||
const newAlbum = ref({ name: '', description: '' })
|
||||
const submitting = ref(false)
|
||||
const API_URL = import.meta.env.VITE_API_URL
|
||||
|
||||
onMounted(async () => {
|
||||
await albumStore.fetchAlbums()
|
||||
})
|
||||
|
||||
const goToDetail = (id) => {
|
||||
router.push({ name: 'album-detail', params: { id } })
|
||||
}
|
||||
|
||||
const createAlbum = async () => {
|
||||
if (!newAlbum.value.name) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await albumStore.createAlbum(newAlbum.value)
|
||||
showCreateDialog.value = false
|
||||
newAlbum.value = { name: '', description: '' }
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full p-8 overflow-y-auto text-slate-100">
|
||||
<!-- Top Bar -->
|
||||
<header class="flex justify-between items-end mb-8">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold m-0">Albums</h1>
|
||||
<p class="mt-2 mb-0 text-slate-400">Organize your generations</p>
|
||||
</div>
|
||||
<Button label="Create Album" icon="pi pi-plus" @click="showCreateDialog = true"
|
||||
class="p-button-outlined p-button-secondary !text-white !border-white/20 hover:!bg-white/10" />
|
||||
</header>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading && albums.length === 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div v-for="i in 6" :key="i" class="glass-panel rounded-2xl p-6 flex flex-col gap-4">
|
||||
<Skeleton height="12rem" class="w-full rounded-xl" />
|
||||
<Skeleton width="60%" height="1.5rem" />
|
||||
<Skeleton width="40%" height="1rem" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="albums.length === 0" class="flex flex-col items-center justify-center h-64 text-slate-400">
|
||||
<i class="pi pi-folder-open text-6xl mb-4 opacity-50"></i>
|
||||
<p class="text-xl">No albums found</p>
|
||||
<Button label="Create your first album" class="mt-4 p-button-text" @click="showCreateDialog = true" />
|
||||
</div>
|
||||
|
||||
<!-- Albums Grid -->
|
||||
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div v-for="album in albums" :key="album.id"
|
||||
class="glass-panel rounded-2xl p-6 flex flex-col gap-4 transition-all duration-300 cursor-pointer border border-white/5 hover:-translate-y-1 hover:bg-white/5 hover:border-white/10 group"
|
||||
@click="goToDetail(album.id)">
|
||||
|
||||
<!-- Album Cover Placeholder -->
|
||||
<div v-if="album.cover_asset_id" class="aspect-video w-full bg-slate-800 rounded-xl flex items-center justify-center overflow-hidden border border-white/5">
|
||||
<Image :src="API_URL + '/assets/' + album.cover_asset_id + '?thumbnail=true'" preview class="w-full h-full object-cover" imageClass="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div v-else class="aspect-video w-full bg-slate-800 rounded-xl flex items-center justify-center overflow-hidden border border-white/5">
|
||||
<i class="pi pi-images text-4xl text-slate-600 group-hover:text-slate-400 transition-colors"></i>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="m-0 mb-1 text-xl font-semibold truncate">{{ album.name }}</h3>
|
||||
<p class="m-0 text-sm text-slate-400 line-clamp-2 min-h-[2.5em]">
|
||||
{{ album.description || 'No description' }}
|
||||
</p>
|
||||
<div class="mt-3 flex items-center text-xs text-slate-500">
|
||||
<i class="pi pi-image mr-1"></i>
|
||||
<span>{{ album.generation_ids?.length || 0 }} items</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Album Dialog -->
|
||||
<Dialog v-model:visible="showCreateDialog" modal header="Create New Album" :style="{ width: '500px' }"
|
||||
:breakpoints="{ '960px': '75vw', '641px': '90vw' }" class="p-dialog-custom">
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="name" class="font-semibold">Name</label>
|
||||
<InputText id="name" v-model="newAlbum.name" class="w-full" placeholder="My Awesome Album" autofocus />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="description" class="font-semibold">Description</label>
|
||||
<Textarea id="description" v-model="newAlbum.description" rows="3" class="w-full" placeholder="Optional description..." />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="Cancel" icon="pi pi-times" @click="showCreateDialog = false" text />
|
||||
<Button label="Create" icon="pi pi-check" @click="createAlbum" :loading="submitting" autofocus />
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user