feat(artist-view): ArtistPostsTab — PostCard infinite-scroll list, artist_id pinned, no filter bar; mirrors PostsView mechanics
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="fc-artist-posts">
|
||||
<v-alert v-if="store.error" type="error" variant="tonal" closable class="mb-3">
|
||||
{{ String(store.error) }}
|
||||
</v-alert>
|
||||
|
||||
<div v-if="store.loading && store.items.length === 0" class="fc-artist-posts__loading">
|
||||
<v-progress-circular indeterminate color="accent" size="36" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="store.items.length === 0 && store.done" class="fc-artist-posts__empty">
|
||||
<p>No posts for this artist yet. Switch to
|
||||
<a href="#" @click.prevent="$emit('switch-tab', 'gallery')">Gallery</a>
|
||||
to see imported images, or visit
|
||||
<RouterLink to="/subscriptions">Subscriptions</RouterLink>
|
||||
to start capturing posts.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<PostCard v-for="p in store.items" :key="p.id" :post="p" />
|
||||
<div ref="sentinel" class="fc-artist-posts__sentinel">
|
||||
<v-progress-circular v-if="store.loading" indeterminate color="accent" size="24" />
|
||||
<span v-else-if="store.done" class="fc-artist-posts__end">End of stream</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
import { usePostsStore } from '../../stores/posts.js'
|
||||
import PostCard from '../posts/PostCard.vue'
|
||||
|
||||
const props = defineProps({
|
||||
artistId: { type: Number, required: true },
|
||||
})
|
||||
|
||||
defineEmits(['switch-tab'])
|
||||
|
||||
const store = usePostsStore()
|
||||
const sentinel = ref(null)
|
||||
let observer = null
|
||||
|
||||
async function reload () {
|
||||
await store.loadInitial({ artist_id: props.artistId, platform: null })
|
||||
}
|
||||
|
||||
watch(() => props.artistId, reload)
|
||||
|
||||
onMounted(async () => {
|
||||
await reload()
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
if (entries.some(e => e.isIntersecting)) {
|
||||
store.loadMore()
|
||||
}
|
||||
}, { rootMargin: '400px 0px' })
|
||||
if (sentinel.value) observer.observe(sentinel.value)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer) observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fc-artist-posts {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.fc-artist-posts__loading,
|
||||
.fc-artist-posts__empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
}
|
||||
.fc-artist-posts__sentinel {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 1.5rem 0;
|
||||
min-height: 2rem;
|
||||
}
|
||||
.fc-artist-posts__end {
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user