import { computed, watch, useTemplateRef, shallowRef, inject, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue' import { useRouter, useRoute } from 'vue-router' import { isMobile } from '../utils' import { useSubsonicApi } from '../subsonicApi' import { useMainStore } from '../store/main' import { usePlaylistStore } from '../store/playlist' import { useCacheStore } from '../store/cache' import { pushReload } from '../reload' import { sleep } from '../utils' import { type ConfirmDialogExpose, ConfirmDialog } from '../components/ConfirmDialog.vine' import { AboutDialog } from '../view/About.vine' import { Logo } from './Logo.vine' import { EditPlaylistModal } from './EditPlaylistModal.vine' const qualityOptions = [ { text: 'OPUS 128k', value: { format: 'opus', bitrate: 128 } }, { text: 'OPUS 160k', value: { format: 'opus', bitrate: 160 } }, { text: 'Original', value: { format: 'raw', bitrate: 0 } }, ], themeColors = [ { name: 'Blue', value: '#178994' }, { name: 'Green', value: '#1db954' }, { name: 'Orange', value: '#ff8c00' }, { name: 'Purple', value: '#6f42c1' }, { name: 'Red', value: '#bf0000' } ], menuItems = [ { name: 'discover', title: 'Discover', icon: 'discover', to: { name: 'discover' }, }, { name: 'queue', title: 'Player Queue', icon: 'queue', to: { name: 'queue' }, }, { isDivider: true, title: 'Library', }, { name: 'menu', title: 'Menu', icon: 'more', toggleMenu: true, hideSidebar: true, }, { name: 'albums', title: 'Albums', icon: 'albums', to: { name: 'albums' }, hideNavBar: true, }, { name: 'artists', title: 'Artists', icon: 'artists', to: { name: 'artists' }, hideNavBar: true, }, { name: 'geners', title: 'Genres', icon: 'genres', to: { name: 'genres' }, hideNavBar: true, }, { name: 'playlists', title: 'Playlists', icon: 'playlist', to: { name: 'playlists' }, }, { name: 'favourites', title: 'Favourites', icon: 'heart-fill', to: { name: 'favourites', params: { section: 'tracks'}}, }, ] export const NavSidebar = () => { const router = useRouter(), route = useRoute(), subsonicApi = useSubsonicApi(), mainStore = useMainStore(), playlistStore = usePlaylistStore(), cacheStore = useCacheStore(), isScanning = shallowRef(false), showAbout = shallowRef(false), showModal = shallowRef(false), cacheSize = shallowRef(0), confirmDialog = shallowRef(null), playlists = computed(() => playlistStore.playlists?.slice(0, 10)), go = (item: any) => { if (item.toggleMenu) { mainStore.toggleMenu() } else { router.push(item.to) mainStore.hideMenu() } }, isActive = (item: any) => ( (item.match) ? item.match(route) : ( (!item.toggleMenu) ? !mainStore.menuVisible && (route.name === item.to.name) : mainStore.menuVisible ) ), openModal = () => { showModal.value = true }, closeModal = () => { showModal.value = false }, isActiveStreamQuality = (option) => { if (option.format !== mainStore.streamFormat) return false if (option.bitrate !== mainStore.streamBitrate) return false return true }, setStreamQuality = (option) => { mainStore.setStreamFormat(option.format) mainStore.setStreamBitrate(option.bitrate) subsonicApi.setStreamFormat(option.format, option.bitrate) }, createPlaylist = (name: string) => { playlistStore.create(name) closeModal() }, onDrop = async (playlistId: string, event: any) => { event.preventDefault() if (event.dataTransfer?.types.includes('application/x-track-id')) return playlistStore.addTracks(playlistId, [ event.dataTransfer.getData('application/x-track-id') ]) if (event.dataTransfer?.types.includes('application/x-album-id')) { const album = await subsonicApi.getAlbumDetails(event.dataTransfer.getData('application/x-album-id')) return playlistStore.addTracks( playlistId, album.tracks!.map(item => item.id) ) } }, onDragover = (event: DragEvent) => { const validTypes = [ 'application/x-track-id', 'application/x-album-id' ] if (!event.dataTransfer?.types.some(i => validTypes.includes(i))) return event.dataTransfer.dropEffect = 'copy' event.preventDefault() }, setTheme = (color: string) => { mainStore.setThemeColor(color) mainStore.applyThemeColor() }, updateCacheSize = async () => { cacheSize.value = await cacheStore.getCacheSizeGB() }, scan = async () => { if (isScanning.value) return mainStore.showLoader() isScanning.value = true try { let scanning = false await subsonicApi.scan() do { await sleep(1000) scanning = await subsonicApi.getScanStatus() } while (scanning) pushReload() router.replace({ name: route.name as string, params: { ...(route.params || {}) }, query: { ...(route.query || {}), t: Date.now().toString() }, }) } finally { mainStore.hideLoader() isScanning.value = false } }, clearCache = async () => { if (!confirmDialog.value) return const userConfirmed = await confirmDialog.value.open( 'Clear cache', 'Do you really want to clear cached content?' ) if (!userConfirmed) return try { mainStore.showLoader() const success = await cacheStore.clearAllAudioCache() if (success) { window.dispatchEvent(new CustomEvent('audioCacheClearedAll')) router.replace({ name: route.name as string, params: { ...(route.params || {}) }, query: { ...(route.query || {}), t: Date.now().toString() }, }) } await updateCacheSize() mainStore.hideLoader() } catch (err) { console.error('Error clearing cache:', err) } }, logout = async () => { if (!confirmDialog.value) return const userConfirmed = await confirmDialog.value.open( 'Logout', 'You are about to end the session. Continue?' ) if (userConfirmed) { mainStore.serverCredentials = null mainStore.serverInfo = null router.go(0) } } onMounted(() => { updateCacheSize() window.addEventListener('audioCached', updateCacheSize) window.addEventListener('audioCacheDeleted', updateCacheSize) window.addEventListener('audioCacheClearedAll', updateCacheSize) }) onBeforeUnmount(() => { window.removeEventListener('audioCached', updateCacheSize) window.removeEventListener('audioCacheDeleted', updateCacheSize) window.removeEventListener('audioCacheClearedAll', updateCacheSize) }) return vine` ` } export const NavBar = () => { const router = useRouter(), route = useRoute(), mainStore = useMainStore(), go = (item: any) => { if (item.toggleMenu) { mainStore.toggleMenu() } else { router.push(item.to) mainStore.hideMenu() } }, isActive = (item: any) => ( (item.match) ? item.match(route) : ( (item.toggleMenu) ? mainStore.menuVisible : !mainStore.menuVisible && (route.name === item?.to?.name) ) ) return vine` `; }