Simplification de la web UI en reposant davantage sur le serveur.

This commit is contained in:
2025-12-05 23:37:21 +01:00
parent 4d79c6d883
commit daef8d6150
18 changed files with 700 additions and 758 deletions

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRenderersStore } from '@/stores/renderers'
import { ref } from 'vue'
import { useRenderers } from '@/composables/useRenderers'
import { Play, ListPlus, ChevronRight } from 'lucide-vue-next'
defineProps<{
@@ -14,12 +14,10 @@ const emit = defineEmits<{
addToQueue: [rendererId: string]
}>()
const renderersStore = useRenderersStore()
const { onlineRenderers } = useRenderers()
const showMenu = ref(false)
const showRendererSubmenu = ref<'play' | 'queue' | null>(null)
const onlineRenderers = computed(() => renderersStore.onlineRenderers)
function handleAction(action: 'play' | 'queue', rendererId: string) {
showMenu.value = false
showRendererSubmenu.value = null

View File

@@ -1,15 +1,13 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRenderersStore } from '@/stores/renderers'
import { computed, toRef } from 'vue'
import { useRenderer } from '@/composables/useRenderers'
import { Music } from 'lucide-vue-next'
const props = defineProps<{
rendererId: string
}>()
const renderersStore = useRenderersStore()
const state = computed(() => renderersStore.getStateById(props.rendererId))
const { state } = useRenderer(toRef(props, 'rendererId'))
const metadata = computed(() => state.value?.current_track)
// Calcul du pourcentage de progression

View File

@@ -1,9 +1,8 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useMediaServersStore } from '@/stores/mediaServers'
import { useRenderersStore } from '@/stores/renderers'
import { useMediaServers } from '@/composables/useMediaServers'
import { useRenderers } from '@/composables/useRenderers'
import { useUIStore } from '@/stores/ui'
import type { BreadcrumbItem } from './Breadcrumb.vue'
import Breadcrumb from './Breadcrumb.vue'
import ContainerItem from './ContainerItem.vue'
import MediaItem from './MediaItem.vue'
@@ -14,8 +13,21 @@ const props = defineProps<{
containerId: string
}>()
const mediaServersStore = useMediaServersStore()
const renderersStore = useRenderersStore()
const {
getBrowseCached,
browseContainer,
currentPath: breadcrumbPath,
loading,
error
} = useMediaServers()
const {
playContent,
addToQueue,
attachAndPlayPlaylist,
attachPlaylist,
fetchQueue
} = useRenderers()
const uiStore = useUIStore()
// Flags pour gérer le rechargement automatique avec debounce et cooldown
@@ -25,7 +37,7 @@ const lastRefreshTime = ref<number>(0)
const REFRESH_COOLDOWN_MS = 5000 // Ne pas recharger plus d'une fois toutes les 5 secondes
const browseData = computed(() =>
mediaServersStore.getBrowseCached(props.serverId, props.containerId)
getBrowseCached(props.serverId, props.containerId)
)
const containers = computed(() =>
@@ -36,17 +48,12 @@ const items = computed(() =>
browseData.value?.entries.filter((e) => !e.is_container) || []
)
const loading = computed(() => mediaServersStore.loading)
const error = computed(() => mediaServersStore.error)
const breadcrumbPath = computed<BreadcrumbItem[]>(() => mediaServersStore.currentPath)
// Charger le container au montage et quand containerId change
watch(
() => props.containerId,
async (newContainerId) => {
if (newContainerId) {
await mediaServersStore.browseContainer(props.serverId, newContainerId)
await browseContainer(props.serverId, newContainerId)
}
},
{ immediate: true }
@@ -85,7 +92,7 @@ watch(
`[MediaBrowser] Cache invalidé pour ${props.serverId}/${props.containerId}, rechargement après debounce...`
)
isRefreshing.value = true
await mediaServersStore.browseContainer(props.serverId, props.containerId)
await browseContainer(props.serverId, props.containerId, false)
lastRefreshTime.value = Date.now() // Enregistrer le moment du rechargement
isRefreshing.value = false
refreshTimeoutId.value = null
@@ -110,7 +117,7 @@ function handleBrowseContainer(containerId: string) {
// Actions handlers pour les containers (playlists/albums)
async function handlePlayContainer(containerId: string, rendererId: string) {
try {
await renderersStore.attachAndPlayPlaylist(rendererId, props.serverId, containerId)
await attachAndPlayPlaylist(rendererId, props.serverId, containerId)
uiStore.notifySuccess('Lecture de la playlist démarrée !')
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'
@@ -121,8 +128,8 @@ async function handlePlayContainer(containerId: string, rendererId: string) {
async function handleQueueContainer(containerId: string, rendererId: string) {
try {
// Attacher la playlist (sans démarrer la lecture)
await renderersStore.attachPlaylist(rendererId, props.serverId, containerId)
await renderersStore.fetchQueue(rendererId)
await attachPlaylist(rendererId, props.serverId, containerId)
await fetchQueue(rendererId, true)
uiStore.notifySuccess('Playlist attachée à la queue !')
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'
@@ -133,7 +140,7 @@ async function handleQueueContainer(containerId: string, rendererId: string) {
// Actions handlers pour les items (tracks)
async function handlePlayItem(itemId: string, rendererId: string) {
try {
await renderersStore.playContent(rendererId, props.serverId, itemId)
await playContent(rendererId, props.serverId, itemId)
uiStore.notifySuccess('Lecture démarrée !')
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'
@@ -143,7 +150,7 @@ async function handlePlayItem(itemId: string, rendererId: string) {
async function handleQueueItem(itemId: string, rendererId: string) {
try {
await renderersStore.addToQueue(rendererId, props.serverId, itemId)
await addToQueue(rendererId, props.serverId, itemId)
uiStore.notifySuccess('Ajouté à la queue !')
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'
@@ -170,7 +177,7 @@ async function handleQueueItem(itemId: string, rendererId: string) {
<!-- Error state -->
<div v-else-if="error" class="browser-error">
<p class="error-message">{{ error }}</p>
<button class="btn btn-secondary" @click="mediaServersStore.browseContainer(serverId, containerId)">
<button class="btn btn-secondary" @click="browseContainer(serverId, containerId, false)">
Réessayer
</button>
</div>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRenderersStore } from '@/stores/renderers'
import { computed, toRef } from 'vue'
import { useRenderer, useRenderers } from '@/composables/useRenderers'
import { useUIStore } from '@/stores/ui'
import { Link, Unlink } from 'lucide-vue-next'
@@ -8,15 +8,15 @@ const props = defineProps<{
rendererId: string
}>()
const renderersStore = useRenderersStore()
const { binding } = useRenderer(toRef(props, 'rendererId'))
const { detachPlaylist } = useRenderers()
const uiStore = useUIStore()
const binding = computed(() => renderersStore.getBindingById(props.rendererId))
const isAttached = computed(() => !!binding.value)
async function handleDetach() {
try {
await renderersStore.detachPlaylist(props.rendererId)
await detachPlaylist(props.rendererId)
uiStore.notifySuccess('Playlist détachée avec succès')
} catch (error) {
uiStore.notifyError(`Impossible de détacher la playlist: ${error instanceof Error ? error.message : 'Erreur inconnue'}`)

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, ref, watch, nextTick } from 'vue'
import { useRenderersStore } from '@/stores/renderers'
import { computed, ref, watch, nextTick, toRef } from 'vue'
import { useRenderer } from '@/composables/useRenderers'
import QueueItem from './QueueItem.vue'
import { Link } from 'lucide-vue-next'
@@ -8,10 +8,7 @@ const props = defineProps<{
rendererId: string
}>()
const renderersStore = useRenderersStore()
const queue = computed(() => renderersStore.getQueueById(props.rendererId))
const binding = computed(() => renderersStore.getBindingById(props.rendererId))
const { queue, binding } = useRenderer(toRef(props, 'rendererId'))
const isAttached = computed(() => !!binding.value)

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRenderersStore } from '@/stores/renderers'
import { computed, toRef } from 'vue'
import { useRenderer, useRenderers } from '@/composables/useRenderers'
import { useUIStore } from '@/stores/ui'
import { Play, Pause, Square, SkipForward } from 'lucide-vue-next'
@@ -8,20 +8,17 @@ const props = defineProps<{
rendererId: string
}>()
const renderersStore = useRenderersStore()
const { state } = useRenderer(toRef(props, 'rendererId'))
const { resumeOrPlayFromQueue, pause, stop, next } = useRenderers()
const uiStore = useUIStore()
const state = computed(() => renderersStore.getStateById(props.rendererId))
const isPlaying = computed(() => state.value?.transport_state === 'PLAYING')
const isPaused = computed(() => state.value?.transport_state === 'PAUSED')
const isStopped = computed(() => state.value?.transport_state === 'STOPPED' || state.value?.transport_state === 'NO_MEDIA')
async function handlePlay() {
try {
// Utilise resumeOrPlayFromQueue qui choisit intelligemment entre:
// - Resume (si PAUSED)
// - Play from queue (si STOPPED/NO_MEDIA avec queue non vide)
await renderersStore.resumeOrPlayFromQueue(props.rendererId)
await resumeOrPlayFromQueue(props.rendererId)
} catch (error) {
uiStore.notifyError(`Impossible de démarrer la lecture: ${error instanceof Error ? error.message : 'Erreur inconnue'}`)
}
@@ -29,7 +26,7 @@ async function handlePlay() {
async function handlePause() {
try {
await renderersStore.pause(props.rendererId)
await pause(props.rendererId)
} catch (error) {
uiStore.notifyError(`Impossible de mettre en pause: ${error instanceof Error ? error.message : 'Erreur inconnue'}`)
}
@@ -37,7 +34,7 @@ async function handlePause() {
async function handleStop() {
try {
await renderersStore.stop(props.rendererId)
await stop(props.rendererId)
} catch (error) {
uiStore.notifyError(`Impossible d'arrêter la lecture: ${error instanceof Error ? error.message : 'Erreur inconnue'}`)
}
@@ -45,7 +42,7 @@ async function handleStop() {
async function handleNext() {
try {
await renderersStore.next(props.rendererId)
await next(props.rendererId)
} catch (error) {
uiStore.notifyError(`Impossible de passer au morceau suivant: ${error instanceof Error ? error.message : 'Erreur inconnue'}`)
}

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRenderersStore } from '@/stores/renderers'
import { ref, watch, toRef } from 'vue'
import { useRenderer, useRenderers } from '@/composables/useRenderers'
import { useUIStore } from '@/stores/ui'
import { Volume2, VolumeX } from 'lucide-vue-next'
@@ -8,10 +8,10 @@ const props = defineProps<{
rendererId: string
}>()
const renderersStore = useRenderersStore()
const { state } = useRenderer(toRef(props, 'rendererId'))
const { setVolume, toggleMute } = useRenderers()
const uiStore = useUIStore()
const state = computed(() => renderersStore.getStateById(props.rendererId))
const localVolume = ref(state.value?.volume ?? 50)
// Synchroniser localVolume avec le state
@@ -34,7 +34,7 @@ function handleVolumeChange(event: Event) {
debounceTimer = window.setTimeout(async () => {
try {
await renderersStore.setVolume(props.rendererId, localVolume.value)
await setVolume(props.rendererId, localVolume.value)
} catch (error) {
uiStore.notifyError(`Impossible de régler le volume: ${error instanceof Error ? error.message : 'Erreur inconnue'}`)
}
@@ -44,7 +44,7 @@ function handleVolumeChange(event: Event) {
async function handleToggleMute() {
try {
await renderersStore.toggleMute(props.rendererId)
await toggleMute(props.rendererId)
} catch (error) {
uiStore.notifyError(`Impossible de basculer le mode muet: ${error instanceof Error ? error.message : 'Erreur inconnue'}`)
}