feat: page loaders

This commit is contained in:
2025-06-08 02:02:25 +03:00
parent 892520ce2d
commit c6a88992b0
5 changed files with 113 additions and 42 deletions

View File

@@ -145,7 +145,6 @@ class ApiController extends OCSController {
$albums[$key]['tracks'][] = $track->jsonSerialize();
}
// Return as array, not associative map
return new JSONResponse(['albums' => array_values($albums)]);
}
}

View File

@@ -49,7 +49,12 @@
<template #footer> <!-- Add footer controls if needed --> </template>
</NcAppNavigation>
<NcAppContent id="jukebox-main">
<div id="jukebox-router"> <router-view /> </div>
<div id="jukebox-router">
<div v-if="isRouterLoading" class="router-loading">
<NcLoadingIcon :size="64" />
</div>
<router-view v-else />
</div>
<!-- Media Player -->
<footer class="jukebox-player">
<div class="controls">
@@ -104,12 +109,15 @@
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation'
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
import NcAppNavigationSearch from '@nextcloud/vue/components/NcAppNavigationSearch'
import NcAppContent from '@nextcloud/vue/components/NcAppContent'
import NcContent from '@nextcloud/vue/components/NcContent'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import QueuePopup from '@/components/media/QueuePopup.vue'
@@ -137,6 +145,7 @@
NcAppNavigation,
NcAppNavigationItem,
NcAppNavigationSearch,
NcLoadingIcon,
NcButton,
QueuePopup,
SkipPrevious,
@@ -159,6 +168,12 @@
}
},
setup() {
const router = useRouter()
const isRouterLoading = ref(true)
router.beforeEach(() => (isRouterLoading.value = true))
router.afterEach(() => (isRouterLoading.value = false))
const showQueue = ref(false)
const toggleQueue = () => {
@@ -191,6 +206,7 @@
onPlayFromQueue,
formattedCurrentTime,
formattedDuration,
isRouterLoading,
}
},
})
@@ -261,4 +277,11 @@
text-align: center;
color: var(--color-text-light);
}
.router-loading {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
</style>

61
src/components/Page.vue Normal file
View File

@@ -0,0 +1,61 @@
<template>
<div class="page">
<PageTitle>
<slot name="title" />
</PageTitle>
<div v-if="loading" class="loader-wrapper">
<NcLoadingIcon :size="64" />
</div>
<main v-else class="page-content">
<slot />
</main>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import PageTitle from '@/components/PageTitle.vue'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
export default defineComponent({
name: 'Page',
components: { PageTitle, NcLoadingIcon },
props: {
loading: {
type: Boolean,
default: false,
},
},
})
</script>
<style scoped lang="scss">
.page {
display: flex;
flex-direction: column;
height: 100%;
}
.page-content {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
.loader-wrapper {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
padding: 2rem;
}
.loader {
font-weight: bold;
font-size: 1.1rem;
opacity: 0.8;
}
</style>

View File

@@ -1,11 +1,12 @@
<template>
<div class="albums-view">
<PageTitle>
<Page :loading="isLoading">
<template #title>
Albums
</PageTitle>
</template>
<AlbumListItem v-for="album in albums" :key="album.album + '|' + album.albumArtist" :album="album"
@play="handlePlay" />
</div>
</Page>
</template>
<script lang="ts">
@@ -14,8 +15,7 @@ import { axios } from '@/axios'
import { type Media } from '@/models/media'
import AlbumListItem from '@/components/media/AlbumListItem.vue'
import PageTitle from '@/components/PageTitle.vue'
import Page from '@/components/Page.vue'
import playback from '@/composables/usePlayback'
export interface Album {
@@ -28,9 +28,10 @@ export interface Album {
export default defineComponent({
name: 'AlbumsView',
components: { AlbumListItem, PageTitle },
components: { AlbumListItem, Page },
setup() {
const albums = ref<Album[]>([])
const isLoading = ref(true)
const { overwriteQueue } = playback
onMounted(async () => {
@@ -39,6 +40,8 @@ export default defineComponent({
albums.value = res.data.albums
} catch (err) {
console.error('Failed to load albums:', err)
} finally {
isLoading.value = false
}
})
@@ -46,26 +49,22 @@ export default defineComponent({
for (const album of albums.value) {
const index = album.tracks.findIndex(t => t.id === track.id)
if (index !== -1) {
console.debug('[AlbumsView] Overwriting queue with album:', album.album, 'starting at track', index)
overwriteQueue([...album.tracks], index)
return
}
}
console.warn('Track not found in current albums:', track)
console.warn('Track not found in albums:', track)
}
return {
albums,
isLoading,
handlePlay,
}
},
})
</script>
<style lang="scss">
.albums-view {
display: flex;
flex-direction: column;
height: 100%;
}
<style scoped lang="scss">
/* Page component handles layout, no need for separate .albums-view styles */
</style>

View File

@@ -1,10 +1,11 @@
<template>
<div class="tracks-view">
<PageTitle>
Track List
</PageTitle>
<Page :loading="isLoading">
<template #title>
Tracks
</template>
<MediaListItem v-for="track in tracks" :key="track.id" :media="track" media-type="track" @play="handlePlay" />
</div>
</Page>
</template>
<script lang="ts">
@@ -13,16 +14,16 @@ import { axios } from '@/axios'
import { type Media } from '@/models/media'
import MediaListItem from '@/components/media/MediaListItem.vue'
import PageTitle from '@/components/PageTitle.vue'
import Page from '@/components/Page.vue'
import playback from '@/composables/usePlayback'
export default defineComponent({
name: 'TracksView',
components: { MediaListItem, PageTitle },
components: { MediaListItem, Page },
setup() {
const tracks = ref([])
const { play, overwriteQueue, playIndex } = playback
const tracks = ref<Media[]>([])
const isLoading = ref(true)
const { overwriteQueue } = playback
onMounted(async () => {
try {
@@ -30,35 +31,23 @@ export default defineComponent({
tracks.value = res.data.tracks
} catch (err) {
console.error('Failed to load tracks:', err)
} finally {
isLoading.value = false
}
})
const handlePlay = (track: Media) => {
const index = tracks.value.findIndex(t => t.id === track.id)
if (index !== -1) {
console.debug('[TracksView] Overwriting queue with starting index:', index)
overwriteQueue([...tracks.value], index)
} else {
console.warn('Track not found in current view list:', track)
}
}
return {
tracks,
isLoading,
handlePlay,
}
},
})
</script>
<style lang="scss">
.tracks-view {
display: flex;
flex-direction: column;
height: 100%;
.track-list {
overflow-y: auto;
flex: 1;
}
}
</style>