mirror of
https://github.com/chenasraf/nextcloud-jukebox.git
synced 2026-05-18 01:39:00 +00:00
feat: page loaders
This commit is contained in:
@@ -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)]);
|
||||
}
|
||||
}
|
||||
|
||||
25
src/App.vue
25
src/App.vue
@@ -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
61
src/components/Page.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user