chore: code style and some minor fixes

This commit is contained in:
Phan An 2022-12-02 17:17:37 +01:00
parent e3c7d51ad5
commit 4b8ae1a78e
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
146 changed files with 642 additions and 634 deletions

View file

@ -45,6 +45,8 @@
"vue/valid-v-on": 0,
"vue/no-side-effects-in-computed-properties": 0,
"vue/max-attributes-per-line": 0,
"vue/no-v-html": 0
"vue/no-v-html": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/multi-word-component-names": 0
}
}

View file

@ -1,26 +1,26 @@
<template>
<Overlay ref="overlay"/>
<DialogBox ref="dialog"/>
<MessageToaster ref="toaster"/>
<GlobalEventListeners/>
<OfflineNotification v-if="offline"/>
<Overlay ref="overlay" />
<DialogBox ref="dialog" />
<MessageToaster ref="toaster" />
<GlobalEventListeners />
<OfflineNotification v-if="offline" />
<div v-if="authenticated" id="main" @dragend="onDragEnd" @dragover="onDragOver" @drop="onDrop">
<Hotkeys/>
<MainWrapper/>
<AppFooter/>
<SupportKoel/>
<SongContextMenu/>
<AlbumContextMenu/>
<ArtistContextMenu/>
<PlaylistContextMenu/>
<PlaylistFolderContextMenu/>
<CreateNewPlaylistContextMenu/>
<DropZone v-show="showDropZone"/>
<Hotkeys />
<MainWrapper />
<AppFooter />
<SupportKoel />
<SongContextMenu />
<AlbumContextMenu />
<ArtistContextMenu />
<PlaylistContextMenu />
<PlaylistFolderContextMenu />
<CreateNewPlaylistContextMenu />
<DropZone v-show="showDropZone" />
</div>
<div v-else class="login-wrapper">
<LoginForm @loggedin="onUserLoggedIn"/>
<LoginForm @loggedin="onUserLoggedIn" />
</div>
</template>
@ -92,7 +92,7 @@ onMounted(async () => {
})
const init = async () => {
overlay.value.show({ message: 'Just a little patience…' })
overlay.value!.show({ message: 'Just a little patience…' })
try {
await commonStore.init()
@ -108,7 +108,7 @@ const init = async () => {
})
await socketService.init() && socketListener.listen()
overlay.value.hide()
overlay.value!.hide()
} catch (err) {
authenticated.value = false
throw err

View file

@ -7,6 +7,6 @@ export default (faker: Faker): AlbumInfo => ({
summary: faker.lorem.sentence(),
full: faker.lorem.sentences(4)
},
tracks: factory<AlbumTrack[]>('album-track', 8),
tracks: factory<AlbumTrack>('album-track', 8),
url: faker.internet.url()
})

View file

@ -3,5 +3,5 @@ import factory from 'factoria'
export default (faker: Faker): SmartPlaylistRuleGroup => ({
id: faker.datatype.number(),
rules: factory<SmartPlaylistRule[]>('smart-playlist-rule', 3)
rules: factory<SmartPlaylistRule>('smart-playlist-rule', 3)
})

View file

@ -5,22 +5,22 @@ import MessageToaster from '@/components/ui/MessageToaster.vue'
import DialogBox from '@/components/ui/DialogBox.vue'
import Overlay from '@/components/ui/Overlay.vue'
export const MessageToasterStub: Ref<InstanceType<typeof MessageToaster>> = ref({
export const MessageToasterStub = ref({
info: noop,
success: noop,
warning: noop,
error: noop
})
}) as unknown as Ref<InstanceType<typeof MessageToaster>>
export const DialogBoxStub: Ref<InstanceType<typeof DialogBox>> = ref({
export const DialogBoxStub = ref({
info: noop,
success: noop,
warning: noop,
error: noop,
confirm: noop
})
}) as unknown as Ref<InstanceType<typeof DialogBox>>
export const OverlayStub: Ref<InstanceType<typeof Overlay>> = ref({
export const OverlayStub = ref({
show: noop,
hide: noop
})
}) as unknown as Ref<InstanceType<typeof Overlay>>

View file

@ -18,7 +18,6 @@
<a
:title="`Shuffle all songs in the album ${album.name}`"
class="shuffle-album"
href
role="button"
@click.prevent="shuffle"
>
@ -28,7 +27,6 @@
v-if="allowDownload"
:title="`Download all songs in the album ${album.name}`"
class="download-album"
href
role="button"
@click.prevent="download"
>

View file

@ -3,11 +3,11 @@
<template v-if="album">
<li @click="play">Play All</li>
<li @click="shuffle">Shuffle All</li>
<li class="separator"></li>
<li class="separator" />
<li v-if="isStandardAlbum" @click="viewAlbumDetails">Go to Album</li>
<li v-if="isStandardArtist" @click="viewArtistDetails">Go to Artist</li>
<template v-if="isStandardAlbum && allowDownload">
<li class="separator"></li>
<li class="separator" />
<li @click="download">Download</li>
</template>
</template>
@ -22,7 +22,7 @@ import { useContextMenu, useRouter } from '@/composables'
import { eventBus } from '@/utils'
const { go } = useRouter()
const { context, base, ContextMenuBase, open, trigger } = useContextMenu()
const { base, ContextMenuBase, open, trigger } = useContextMenu()
const album = ref<Album>()
const allowDownload = toRef(commonStore.state, 'allow_download')
@ -49,6 +49,6 @@ const download = () => trigger(() => downloadService.fromAlbum(album.value!))
eventBus.on('ALBUM_CONTEXT_MENU_REQUESTED', async (e, _album) => {
album.value = _album
await open(e.pageY, e.pageX, { album })
await open(e.pageY, e.pageX)
})
</script>

View file

@ -3,24 +3,24 @@
<h1 v-if="mode === 'aside'" class="name">
<span>{{ album.name }}</span>
<button :title="`Play all songs in ${album.name}`" class="control" type="button" @click.prevent="play">
<icon :icon="faCirclePlay" size="xl"/>
<icon :icon="faCirclePlay" size="xl" />
</button>
</h1>
<main>
<AlbumThumbnail v-if="mode === 'aside'" :entity="album"/>
<AlbumThumbnail v-if="mode === 'aside'" :entity="album" />
<template v-if="info">
<div v-if="info.wiki?.summary" class="wiki">
<div v-if="showSummary" class="summary" data-testid="summary" v-html="info.wiki.summary"/>
<div v-if="showFull" class="full" data-testid="full" v-html="info.wiki.full"/>
<div v-if="showSummary" class="summary" data-testid="summary" v-html="info.wiki.summary" />
<div v-if="showFull" class="full" data-testid="full" v-html="info.wiki.full" />
<button v-if="showSummary" class="more" @click.prevent="showingFullWiki = true">
Full Wiki
</button>
</div>
<TrackList v-if="info.tracks?.length" :album="album" :tracks="info.tracks" data-testid="album-info-tracks"/>
<TrackList v-if="info.tracks?.length" :album="album" :tracks="info.tracks" data-testid="album-info-tracks" />
<footer>
Data &copy;

View file

@ -4,7 +4,7 @@
<ul class="tracks">
<li v-for="(track, index) in tracks" :key="index" data-testid="album-track-item">
<TrackListItem :album="album" :track="track"/>
<TrackListItem :album="album" :track="track" />
</li>
</ul>
</section>
@ -22,6 +22,7 @@ const { album, tracks } = toRefs(props)
const songs = ref<Song[]>([])
// @ts-ignore
provide(SongsKey, songs)
onMounted(async () => songs.value = await songStore.fetchForAlbum(album.value))

View file

@ -7,7 +7,7 @@
@click="play"
>
<span class="title">{{ track.title }}</span>
<AppleMusicButton v-if="useAppleMusic && !matchedSong" :url="iTunesUrl"/>
<AppleMusicButton v-if="useAppleMusic && !matchedSong" :url="iTunesUrl" />
<span class="length">{{ fmtLength }}</span>
</div>
</template>

View file

@ -4,7 +4,7 @@ exports[`renders 1`] = `
<article class="item full" draggable="true" tabindex="0" title="IV by Led Zeppelin" data-v-f01bdc56=""><br data-testid="thumbnail" entity="[object Object]" data-v-f01bdc56="">
<footer data-v-f01bdc56="">
<div class="name" data-v-f01bdc56=""><a href="#/album/42" class="text-normal" data-testid="name">IV</a><a href="#/artist/17">Led Zeppelin</a></div>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs in the album IV" class="shuffle-album" href="" role="button"> Shuffle </a><a title="Download all songs in the album IV" class="download-album" href="" role="button"> Download </a></p>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs in the album IV" class="shuffle-album" role="button"> Shuffle </a><a title="Download all songs in the album IV" class="download-album" role="button"> Download </a></p>
</footer>
</article>
`;

View file

@ -15,7 +15,6 @@
<a
:title="`Shuffle all songs by ${artist.name}`"
class="shuffle-artist"
href
role="button"
@click.prevent="shuffle"
>
@ -25,7 +24,6 @@
v-if="allowDownload"
:title="`Download all songs by ${artist.name}`"
class="download-artist"
href
role="button"
@click.prevent="download"
>

View file

@ -4,11 +4,11 @@
<li @click="play">Play All</li>
<li @click="shuffle">Shuffle All</li>
<template v-if="isStandardArtist">
<li class="separator"></li>
<li class="separator" />
<li @click="viewArtistDetails">Go to Artist</li>
</template>
<template v-if="isStandardArtist && allowDownload">
<li class="separator"></li>
<li class="separator" />
<li @click="download">Download</li>
</template>
</template>
@ -23,7 +23,7 @@ import { useContextMenu, useRouter } from '@/composables'
import { eventBus } from '@/utils'
const { go } = useRouter()
const { context, base, ContextMenuBase, open, trigger } = useContextMenu()
const { base, ContextMenuBase, open, trigger } = useContextMenu()
const artist = ref<Artist>()
const allowDownload = toRef(commonStore.state, 'allow_download')
@ -48,6 +48,6 @@ const download = () => trigger(() => downloadService.fromArtist(artist.value!))
eventBus.on('ARTIST_CONTEXT_MENU_REQUESTED', async (e, _artist) => {
artist.value = _artist
await open(e.pageY, e.pageX, { _artist })
await open(e.pageY, e.pageX)
})
</script>

View file

@ -3,17 +3,17 @@
<h1 v-if="mode === 'aside'" class="name">
<span>{{ artist.name }}</span>
<button :title="`Play all songs by ${artist.name}`" class="control" type="button" @click.prevent="play">
<icon :icon="faCirclePlay" size="xl"/>
<icon :icon="faCirclePlay" size="xl" />
</button>
</h1>
<main>
<ArtistThumbnail v-if="mode === 'aside'" :entity="artist"/>
<ArtistThumbnail v-if="mode === 'aside'" :entity="artist" />
<template v-if="info">
<div v-if="info.bio?.summary" class="bio">
<div v-if="showSummary" class="summary" data-testid="summary" v-html="info.bio.summary"/>
<div v-if="showFull" class="full" data-testid="full" v-html="info.bio.full"/>
<div v-if="showSummary" class="summary" data-testid="summary" v-html="info.bio.summary" />
<div v-if="showFull" class="full" data-testid="full" v-html="info.bio.full" />
<button v-if="showSummary" class="more" @click.prevent="showingFullBio = true">
Full Bio

View file

@ -4,7 +4,7 @@ exports[`renders 1`] = `
<article class="item full" draggable="true" tabindex="0" title="Led Zeppelin" data-v-f01bdc56=""><br data-testid="thumbnail" entity="[object Object]" data-v-f01bdc56="">
<footer data-v-f01bdc56="">
<div class="name" data-v-f01bdc56=""><a href="#/artist/42" class="text-normal" data-testid="name">Led Zeppelin</a></div>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs by Led Zeppelin" class="shuffle-artist" href="" role="button"> Shuffle </a><a title="Download all songs by Led Zeppelin" class="download-artist" href="" role="button"> Download </a></p>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs by Led Zeppelin" class="shuffle-artist" role="button"> Shuffle </a><a title="Download all songs by Led Zeppelin" class="download-artist" role="button"> Download </a></p>
</footer>
</article>
`;

View file

@ -1,11 +1,11 @@
import { screen } from '@testing-library/vue'
import { expect, it } from 'vitest'
import { expect, it, Mock } from 'vitest'
import { userStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import LoginFrom from './LoginForm.vue'
new class extends UnitTestCase {
private async submitForm (loginMock: SpyInstanceFn) {
private async submitForm (loginMock: Mock) {
const rendered = this.render(LoginFrom)
await this.type(screen.getByPlaceholderText('Email Address'), 'john@doe.com')

View file

@ -1,15 +1,15 @@
<template>
<dialog ref="dialog" class="text-primary bg-primary" @cancel.prevent>
<Component :is="modalNameToComponentMap[activeModalName]" v-if="activeModalName" @close="close"/>
<Component :is="modalNameToComponentMap[activeModalName]" v-if="activeModalName" @close="close" />
</dialog>
</template>
<script lang="ts" setup>
import { ComponentPublicInstance, defineAsyncComponent, ref, watch } from 'vue'
import { defineAsyncComponent, ref, watch } from 'vue'
import { arrayify, eventBus, provideReadonly } from '@/utils'
import { ModalContextKey } from '@/symbols'
const modalNameToComponentMap: Record<string, ComponentPublicInstance> = {
const modalNameToComponentMap = {
'create-playlist-form': defineAsyncComponent(() => import('@/components/playlist/CreatePlaylistForm.vue')),
'edit-playlist-form': defineAsyncComponent(() => import('@/components/playlist/EditPlaylistForm.vue')),
'create-smart-playlist-form': defineAsyncComponent(() => import('@/components/playlist/smart-playlist/CreateSmartPlaylistForm.vue')),

View file

@ -3,7 +3,7 @@
A very thin wrapper around Plyr, extracted as a standalone component for easier styling and to work better with HMR.
-->
<div class="plyr">
<audio controls crossorigin="anonymous"></audio>
<audio controls crossorigin="anonymous" />
</div>
</template>

View file

@ -8,7 +8,7 @@
href="/#/visualizer"
title="Show the visualizer"
>
<icon :icon="faBolt"/>
<icon :icon="faBolt" />
</a>
<button
@ -20,10 +20,10 @@
type="button"
@click.prevent="showEqualizer"
>
<icon :icon="faSliders"/>
<icon :icon="faSliders" />
</button>
<Volume/>
<Volume />
</div>
</div>
</template>

View file

@ -4,14 +4,14 @@ import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { CurrentSongKey } from '@/symbols'
import { playbackService } from '@/services'
import FooterPlaybackControls from './FooterPlaybackControls.vue'
import { screen } from '@testing-library/vue'
import FooterPlaybackControls from './FooterPlaybackControls.vue'
new class extends UnitTestCase {
private renderComponent (song?: Song | null) {
if (song === undefined) {
song = factory<Song>('song', {
id: 42,
id: '00000000-0000-0000-0000-000000000000',
title: 'Fahrstuhl to Heaven',
artist_name: 'Led Zeppelin',
artist_id: 3,

View file

@ -1,20 +1,20 @@
<template>
<div class="playback-controls" data-testid="footer-middle-pane">
<div class="buttons">
<LikeButton v-if="song" :song="song" class="like-btn"/>
<button type="button" v-else/> <!-- a placeholder to maintain the flex layout -->
<LikeButton v-if="song" :song="song" class="like-btn" />
<button v-else type="button" /> <!-- a placeholder to maintain the flex layout -->
<button type="button" title="Play previous song" @click.prevent="playPrev">
<icon :icon="faStepBackward"/>
<icon :icon="faStepBackward" />
</button>
<PlayButton/>
<PlayButton />
<button type="button" title="Play next song" @click.prevent="playNext">
<icon :icon="faStepForward"/>
<icon :icon="faStepForward" />
</button>
<RepeatModeSwitch class="repeat-mode-btn"/>
<RepeatModeSwitch class="repeat-mode-btn" />
</div>
</div>
</template>

View file

@ -1,9 +1,9 @@
import { expect, it } from 'vitest'
import { ref } from 'vue'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import FooterSongInfo from './FooterSongInfo.vue'
import { ref } from 'vue'
import { CurrentSongKey } from '@/symbols'
import FooterSongInfo from './FooterSongInfo.vue'
new class extends UnitTestCase {
protected test () {
@ -21,7 +21,7 @@ new class extends UnitTestCase {
expect(this.render(FooterSongInfo, {
global: {
provide: {
[CurrentSongKey]: ref(song)
[<symbol>CurrentSongKey]: ref(song)
}
}
}).html()).toMatchSnapshot()

View file

@ -1,7 +1,7 @@
<template>
<div class="song-info" :class="{ playing: song?.playback_state === 'Playing' }">
<span :style="{ backgroundImage: `url('${cover}')` }" class="album-thumb"/>
<div class="meta" v-if="song">
<span :style="{ backgroundImage: `url('${cover}')` }" class="album-thumb" />
<div v-if="song" class="meta">
<h3 class="title">{{ song.title }}</h3>
<a :href="`/#/artist/${song.artist_id}`" class="artist">{{ song.artist_name }}</a>
</div>

View file

@ -1,11 +1,11 @@
<template>
<footer id="mainFooter" @contextmenu.prevent="requestContextMenu">
<AudioPlayer/>
<AudioPlayer />
<div class="wrapper">
<SongInfo/>
<PlaybackControls/>
<ExtraControls/>
<SongInfo />
<PlaybackControls />
<ExtraControls />
</div>
</footer>
</template>

View file

@ -2,8 +2,8 @@
<div id="extraPanel" :class="{ 'showing-pane': activeTab }">
<div class="controls">
<div class="top">
<SidebarMenuToggleButton class="burger"/>
<ExtraPanelTabHeader v-if="song" v-model="activeTab"/>
<SidebarMenuToggleButton class="burger" />
<ExtraPanelTabHeader v-if="song" v-model="activeTab" />
</div>
<div class="bottom">
@ -13,19 +13,19 @@
type="button"
@click.prevent="openAboutKoelModal"
>
<icon :icon="faInfoCircle"/>
<span v-if="shouldNotifyNewVersion" class="new-version-notification"/>
<icon :icon="faInfoCircle" />
<span v-if="shouldNotifyNewVersion" class="new-version-notification" />
</button>
<button v-koel-tooltip.left title="Log out" type="button" @click.prevent="logout">
<icon :icon="faArrowRightFromBracket"/>
<icon :icon="faArrowRightFromBracket" />
</button>
<ProfileAvatar @click="onProfileLinkClick"/>
<ProfileAvatar @click="onProfileLinkClick" />
</div>
</div>
<div class="panes" v-if="song" v-show="activeTab">
<div v-if="song" v-show="activeTab" class="panes">
<div
v-show="activeTab === 'Lyrics'"
id="extraPanelLyrics"
@ -33,7 +33,7 @@
role="tabpanel"
tabindex="0"
>
<LyricsPane :song="song"/>
<LyricsPane :song="song" />
</div>
<div
@ -43,7 +43,7 @@
role="tabpanel"
tabindex="0"
>
<ArtistInfo v-if="artist" :artist="artist" mode="aside"/>
<ArtistInfo v-if="artist" :artist="artist" mode="aside" />
<span v-else>Loading</span>
</div>
@ -54,19 +54,19 @@
role="tabpanel"
tabindex="0"
>
<AlbumInfo v-if="album" :album="album" mode="aside"/>
<AlbumInfo v-if="album" :album="album" mode="aside" />
<span v-else>Loading</span>
</div>
<div
v-show="activeTab === 'YouTube'"
data-testid="extra-panel-youtube"
id="extraPanelYouTube"
data-testid="extra-panel-youtube"
aria-labelledby="extraTabYouTube"
role="tabpanel"
tabindex="0"
>
<YouTubeVideoList v-if="useYouTube && song" :song="song"/>
<YouTubeVideoList v-if="useYouTube && song" :song="song" />
</div>
</div>
</div>
@ -97,16 +97,16 @@ const { shouldNotifyNewVersion } = useNewVersionNotification()
const song = requireInjection(CurrentSongKey, ref(null))
const activeTab = ref<ExtraPanelTab | null>(null)
const artist = ref<Artist | null>(null)
const album = ref<Album | null>(null)
const artist = ref<Artist>()
const album = ref<Album>()
watch(song, song => song && fetchSongInfo(song))
watch(activeTab, tab => (preferenceStore.activeExtraPanelTab = tab))
const fetchSongInfo = async (_song: Song) => {
song.value = _song
artist.value = null
album.value = null
artist.value = undefined
album.value = undefined
try {
artist.value = await artistStore.resolve(_song.artist_id)

View file

@ -5,30 +5,30 @@
lists), so we use v-show.
For those that don't need to maintain their own UI state, we use v-if and enjoy some code-splitting juice.
-->
<VisualizerScreen v-if="screen === 'Visualizer'"/>
<AlbumArtOverlay v-if="showAlbumArtOverlay && currentSong" :album="currentSong?.album_id"/>
<VisualizerScreen v-if="screen === 'Visualizer'" />
<AlbumArtOverlay v-if="showAlbumArtOverlay && currentSong" :album="currentSong?.album_id" />
<HomeScreen v-show="screen === 'Home'"/>
<QueueScreen v-show="screen === 'Queue'"/>
<AllSongsScreen v-show="screen === 'Songs'"/>
<AlbumListScreen v-show="screen === 'Albums'"/>
<ArtistListScreen v-show="screen === 'Artists'"/>
<PlaylistScreen v-show="screen === 'Playlist'"/>
<FavoritesScreen v-show="screen === 'Favorites'"/>
<RecentlyPlayedScreen v-show="screen === 'RecentlyPlayed'"/>
<UploadScreen v-show="screen === 'Upload'"/>
<SearchExcerptsScreen v-show="screen === 'Search.Excerpt'"/>
<GenreScreen v-show="screen === 'Genre'"/>
<HomeScreen v-show="screen === 'Home'" />
<QueueScreen v-show="screen === 'Queue'" />
<AllSongsScreen v-show="screen === 'Songs'" />
<AlbumListScreen v-show="screen === 'Albums'" />
<ArtistListScreen v-show="screen === 'Artists'" />
<PlaylistScreen v-show="screen === 'Playlist'" />
<FavoritesScreen v-show="screen === 'Favorites'" />
<RecentlyPlayedScreen v-show="screen === 'RecentlyPlayed'" />
<UploadScreen v-show="screen === 'Upload'" />
<SearchExcerptsScreen v-show="screen === 'Search.Excerpt'" />
<GenreScreen v-show="screen === 'Genre'" />
<GenreListScreen v-if="screen === 'Genres'"/>
<SearchSongResultsScreen v-if="screen === 'Search.Songs'"/>
<AlbumScreen v-if="screen === 'Album'"/>
<ArtistScreen v-if="screen === 'Artist'"/>
<SettingsScreen v-if="screen === 'Settings'"/>
<ProfileScreen v-if="screen === 'Profile'"/>
<UserListScreen v-if="screen === 'Users'"/>
<YoutubeScreen v-if="useYouTube" v-show="screen === 'YouTube'"/>
<NotFoundScreen v-if="screen === '404'"/>
<GenreListScreen v-if="screen === 'Genres'" />
<SearchSongResultsScreen v-if="screen === 'Search.Songs'" />
<AlbumScreen v-if="screen === 'Album'" />
<ArtistScreen v-if="screen === 'Artist'" />
<SettingsScreen v-if="screen === 'Settings'" />
<ProfileScreen v-if="screen === 'Profile'" />
<UserListScreen v-if="screen === 'Users'" />
<YoutubeScreen v-if="useYouTube" v-show="screen === 'YouTube'" />
<NotFoundScreen v-if="screen === '404'" />
</section>
</template>

View file

@ -1,13 +1,13 @@
<template>
<nav id="sidebar" :class="{ showing: mobileShowing }" class="side side-nav" v-koel-clickaway="closeIfMobile">
<SearchForm/>
<nav id="sidebar" v-koel-clickaway="closeIfMobile" :class="{ showing: mobileShowing }" class="side side-nav">
<SearchForm />
<section class="music">
<h1>Your Music</h1>
<ul class="menu">
<li>
<a :class="['home', activeScreen === 'Home' ? 'active' : '']" href="#/home">
<icon :icon="faHome" fixed-width/>
<icon :icon="faHome" fixed-width />
Home
</a>
</li>
@ -18,44 +18,44 @@
@drop="onQueueDrop"
>
<a :class="['queue', activeScreen === 'Queue' ? 'active' : '']" href="#/queue">
<icon :icon="faListOl" fixed-width/>
<icon :icon="faListOl" fixed-width />
Current Queue
</a>
</li>
<li>
<a :class="['songs', activeScreen === 'Songs' ? 'active' : '']" href="#/songs">
<icon :icon="faMusic" fixed-width/>
<icon :icon="faMusic" fixed-width />
All Songs
</a>
</li>
<li>
<a :class="['albums', activeScreen === 'Albums' ? 'active' : '']" href="#/albums">
<icon :icon="faCompactDisc" fixed-width/>
<icon :icon="faCompactDisc" fixed-width />
Albums
</a>
</li>
<li>
<a :class="['artists', activeScreen === 'Artists' ? 'active' : '']" href="#/artists">
<icon :icon="faMicrophone" fixed-width/>
<icon :icon="faMicrophone" fixed-width />
Artists
</a>
</li>
<li>
<a :class="['genres', activeScreen === 'Genres' ? 'active' : '']" href="#/genres">
<icon :icon="faTags" fixed-width/>
<icon :icon="faTags" fixed-width />
Genres
</a>
</li>
<li v-if="useYouTube">
<a :class="['youtube', activeScreen === 'YouTube' ? 'active' : '']" href="#/youtube">
<icon :icon="faYoutube" fixed-width/>
<icon :icon="faYoutube" fixed-width />
YouTube Video
</a>
</li>
</ul>
</section>
<PlaylistList/>
<PlaylistList />
<section v-if="isAdmin" class="manage">
<h1>Manage</h1>
@ -63,19 +63,19 @@
<ul class="menu">
<li>
<a :class="['settings', activeScreen === 'Settings' ? 'active' : '']" href="#/settings">
<icon :icon="faTools" fixed-width/>
<icon :icon="faTools" fixed-width />
Settings
</a>
</li>
<li>
<a :class="['upload', activeScreen === 'Upload' ? 'active' : '']" href="#/upload">
<icon :icon="faUpload" fixed-width/>
<icon :icon="faUpload" fixed-width />
Upload
</a>
</li>
<li>
<a :class="['users', activeScreen === 'Users' ? 'active' : '']" href="#/users">
<icon :icon="faUsers" fixed-width/>
<icon :icon="faUsers" fixed-width />
Users
</a>
</li>

View file

@ -1,16 +1,16 @@
<template>
<div id="mainWrapper">
<Sidebar/>
<MainContent/>
<ExtraPanel/>
<ModalWrapper/>
<SideBar />
<MainContent />
<ExtraPanel />
<ModalWrapper />
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue'
import Sidebar from '@/components/layout/main-wrapper/Sidebar.vue'
import SideBar from '@/components/layout/main-wrapper/SideBar.vue'
import MainContent from '@/components/layout/main-wrapper/MainContent.vue'
import ExtraPanel from '@/components/layout/main-wrapper/ExtraPanel.vue'

View file

@ -18,7 +18,8 @@
<a href="https://github.com/phanan" rel="noopener" target="_blank">Phan An</a>
and quite a few
<a href="https://github.com/koel/core/graphs/contributors" rel="noopener" target="_blank">awesome</a>&nbsp;<a
href="https://github.com/koel/koel/graphs/contributors" rel="noopener" target="_blank">contributors</a>.
href="https://github.com/koel/koel/graphs/contributors" rel="noopener" target="_blank"
>contributors</a>.
</p>
<div v-if="credits" class="credit-wrapper" data-testid="demo-credits">
@ -30,7 +31,7 @@
</ul>
</div>
<SponsorList/>
<SponsorList />
<p>
Loving Koel? Please consider supporting its development via

View file

@ -7,7 +7,7 @@
<a href="https://opencollective.com/koel" rel="noopener" target="_blank">OpenCollective</a>.
</p>
<button type="button" @click.prevent="close">Hide</button>
<span class="sep"></span>
<span class="sep" />
<button type="button" @click.prevent="stopBugging">
Don't bug me again
</button>

View file

@ -7,7 +7,7 @@ import CreateNewPlaylistContextMenu from './CreateNewPlaylistContextMenu.vue'
new class extends UnitTestCase {
private async renderComponent () {
await this.render(CreateNewPlaylistContextMenu)
this.render(CreateNewPlaylistContextMenu)
eventBus.emit('CREATE_NEW_PLAYLIST_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 } as MouseEvent)
await this.tick(2)
}

View file

@ -11,7 +11,7 @@ new class extends UnitTestCase {
const storeMock = this.mock(playlistFolderStore, 'store')
.mockResolvedValue(factory<PlaylistFolder>('playlist-folder'))
await this.render(CreatePlaylistFolderForm)
this.render(CreatePlaylistFolderForm)
await this.type(screen.getByPlaceholderText('Folder name'), 'My folder')
await this.user.click(screen.getByRole('button', { name: 'Save' }))

View file

@ -20,8 +20,8 @@
<label class="folder">
Folder
<select v-model="folderId">
<option :value="null"></option>
<option v-for="folder in folders" :value="folder.id">{{ folder.name }}</option>
<option :value="null" />
<option v-for="folder in folders" :key="folder.id" :value="folder.id">{{ folder.name }}</option>
</select>
</label>
</div>

View file

@ -21,8 +21,8 @@
<label class="folder">
Folder
<select v-model="folderId">
<option :value="null"></option>
<option v-for="folder in folders" :value="folder.id">{{ folder.name }}</option>
<option :value="null" />
<option v-for="folder in folders" :key="folder.id" :value="folder.id">{{ folder.name }}</option>
</select>
</label>
</div>

View file

@ -7,8 +7,8 @@ import PlaylistContextMenu from './PlaylistContextMenu.vue'
new class extends UnitTestCase {
private async renderComponent (playlist: Playlist) {
await this.render(PlaylistContextMenu)
eventBus.emit('PLAYLIST_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 }, playlist)
this.render(PlaylistContextMenu)
eventBus.emit('PLAYLIST_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 } as MouseEvent, playlist)
await this.tick(2)
}

View file

@ -1,7 +1,7 @@
<template>
<ContextMenuBase ref="base">
<li :data-testid="`playlist-context-menu-edit-${playlist.id}`" @click="editPlaylist">Edit</li>
<li :data-testid="`playlist-context-menu-delete-${playlist.id}`" @click="deletePlaylist">Delete</li>
<li @click="editPlaylist">Edit</li>
<li @click="deletePlaylist">Delete</li>
</ContextMenuBase>
</template>
@ -10,14 +10,14 @@ import { ref } from 'vue'
import { eventBus } from '@/utils'
import { useContextMenu } from '@/composables'
const { context, base, ContextMenuBase, open, trigger } = useContextMenu()
const { base, ContextMenuBase, open, trigger } = useContextMenu()
const playlist = ref<Playlist>()
const editPlaylist = () => trigger(() => eventBus.emit('MODAL_SHOW_EDIT_PLAYLIST_FORM', playlist.value))
const deletePlaylist = () => trigger(() => eventBus.emit('PLAYLIST_DELETE', playlist.value))
const editPlaylist = () => trigger(() => eventBus.emit('MODAL_SHOW_EDIT_PLAYLIST_FORM', playlist.value!))
const deletePlaylist = () => trigger(() => eventBus.emit('PLAYLIST_DELETE', playlist.value!))
eventBus.on('PLAYLIST_CONTEXT_MENU_REQUESTED', async (event, _playlist) => {
playlist.value = _playlist
await open(event.pageY, event.pageX, { playlist })
await open(event.pageY, event.pageX)
})
</script>

View file

@ -9,8 +9,8 @@ import PlaylistFolderContextMenu from './PlaylistFolderContextMenu.vue'
new class extends UnitTestCase {
private async renderComponent (folder: PlaylistFolder) {
await this.render(PlaylistFolderContextMenu)
eventBus.emit('PLAYLIST_FOLDER_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 }, folder)
this.render(PlaylistFolderContextMenu)
eventBus.emit('PLAYLIST_FOLDER_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 } as MouseEvent, folder)
await this.tick(2)
}

View file

@ -4,11 +4,11 @@
<template v-if="playable">
<li @click="play">Play All</li>
<li @click="shuffle">Shuffle All</li>
<li class="separator"/>
<li class="separator" />
</template>
<li @click="createPlaylist">Create Playlist</li>
<li @click="createSmartPlaylist">Create Smart Playlist</li>
<li class="separator"/>
<li class="separator" />
<li @click="rename">Rename</li>
<li @click="destroy">Delete</li>
</template>
@ -23,7 +23,7 @@ import { playbackService } from '@/services'
import { useContextMenu, useRouter } from '@/composables'
const { go } = useRouter()
const { context, base, ContextMenuBase, open, trigger } = useContextMenu()
const { base, ContextMenuBase, open, trigger } = useContextMenu()
const folder = ref<PlaylistFolder>()
@ -40,13 +40,13 @@ const shuffle = () => trigger(async () => {
go('queue')
})
const createPlaylist = () => trigger(() => eventBus.emit('MODAL_SHOW_CREATE_PLAYLIST_FORM', folder.value))
const createSmartPlaylist = () => trigger(() => eventBus.emit('MODAL_SHOW_CREATE_SMART_PLAYLIST_FORM', folder.value))
const rename = () => trigger(() => eventBus.emit('MODAL_SHOW_EDIT_PLAYLIST_FOLDER_FORM', folder.value))
const destroy = () => trigger(() => eventBus.emit('PLAYLIST_FOLDER_DELETE', folder.value))
const createPlaylist = () => trigger(() => eventBus.emit('MODAL_SHOW_CREATE_PLAYLIST_FORM', folder.value!))
const createSmartPlaylist = () => trigger(() => eventBus.emit('MODAL_SHOW_CREATE_SMART_PLAYLIST_FORM', folder.value!))
const rename = () => trigger(() => eventBus.emit('MODAL_SHOW_EDIT_PLAYLIST_FOLDER_FORM', folder.value!))
const destroy = () => trigger(() => eventBus.emit('PLAYLIST_FOLDER_DELETE', folder.value!))
eventBus.on('PLAYLIST_FOLDER_CONTEXT_MENU_REQUESTED', async (e, _folder) => {
folder.value = _folder
await open(e.pageY, e.pageX, { folder })
await open(e.pageY, e.pageX)
})
</script>

View file

@ -2,18 +2,18 @@
<li
class="playlist-folder"
:class="{ droppable }"
tabindex="0"
@dragleave="onDragLeave"
@dragover="onDragOver"
@drop="onDrop"
tabindex="0"
>
<a @click.prevent="toggle" @contextmenu.prevent="onContextMenu">
<icon :icon="opened ? faFolderOpen : faFolder" fixed-width/>
<icon :icon="opened ? faFolderOpen : faFolder" fixed-width />
{{ folder.name }}
</a>
<ul v-if="playlistsInFolder.length" v-show="opened">
<PlaylistSidebarItem v-for="playlist in playlistsInFolder" :key="playlist.id" :list="playlist" class="sub-item"/>
<PlaylistSidebarItem v-for="playlist in playlistsInFolder" :key="playlist.id" :list="playlist" class="sub-item" />
</ul>
<div

View file

@ -13,10 +13,10 @@
</h1>
<ul>
<PlaylistSidebarItem :list="{ name: 'Favorites', songs: favorites }"/>
<PlaylistSidebarItem :list="{ name: 'Recently Played', songs: [] }"/>
<PlaylistFolderSidebarItem v-for="folder in folders" :key="folder.id" :folder="folder"/>
<PlaylistSidebarItem v-for="playlist in orphanPlaylists" :key="playlist.id" :list="playlist"/>
<PlaylistSidebarItem :list="{ name: 'Favorites', songs: favorites }" />
<PlaylistSidebarItem :list="{ name: 'Recently Played', songs: [] }" />
<PlaylistFolderSidebarItem v-for="folder in folders" :key="folder.id" :folder="folder" />
<PlaylistSidebarItem v-for="playlist in orphanPlaylists" :key="playlist.id" :list="playlist" />
</ul>
</section>
</template>

View file

@ -14,8 +14,8 @@
<label class="folder">
Folder
<select v-model="folderId">
<option :value="null"></option>
<option v-for="folder in folders" :value="folder.id">{{ folder.name }}</option>
<option :value="null" />
<option v-for="folder in folders" :key="folder.id" :value="folder.id">{{ folder.name }}</option>
</select>
</label>
</div>
@ -25,11 +25,11 @@
v-for="(group, index) in collectedRuleGroups"
:key="group.id"
:group="group"
:isFirstGroup="index === 0"
:is-first-group="index === 0"
@input="onGroupChanged"
/>
<Btn class="btn-add-group" green small title="Add a new group" uppercase @click.prevent="addGroup">
<icon :icon="faPlus"/>
<icon :icon="faPlus" />
Group
</Btn>
</div>

View file

@ -20,8 +20,8 @@
<label class="folder">
Folder
<select v-model="mutablePlaylist.folder_id">
<option :value="null"></option>
<option v-for="folder in folders" :value="folder.id">{{ folder.name }}</option>
<option :value="null" />
<option v-for="folder in folders" :key="folder.id" :value="folder.id">{{ folder.name }}</option>
</select>
</label>
</div>
@ -31,11 +31,11 @@
v-for="(group, index) in mutablePlaylist.rules"
:key="group.id"
:group="group"
:isFirstGroup="index === 0"
:is-first-group="index === 0"
@input="onGroupChanged"
/>
<Btn class="btn-add-group" green small title="Add a new group" uppercase @click.prevent="addGroup">
<icon :icon="faPlus"/>
<icon :icon="faPlus" />
</Btn>
</div>
</main>
@ -63,9 +63,7 @@ const playlist = useModal().getFromContext<Playlist>('playlist')
const folders = toRef(playlistFolderStore.state, 'folders')
let mutablePlaylist: Playlist
watch(playlist, () => (mutablePlaylist = reactive(cloneDeep(playlist))), { immediate: true })
const mutablePlaylist = reactive(cloneDeep(playlist))
const isPristine = () => isEqual(mutablePlaylist.rules, playlist.rules)
&& mutablePlaylist.name.trim() === playlist.name

View file

@ -1,6 +1,6 @@
<template>
<div class="smart-playlist-form">
<slot/>
<slot />
</div>
</template>

View file

@ -1,11 +1,11 @@
<template>
<div class="row" data-testid="smart-playlist-rule-row">
<Btn class="remove-rule" red title="Remove this rule" @click.prevent="removeRule">
<icon :icon="faTrashCan"/>
<icon :icon="faTrashCan" />
</Btn>
<select v-model="selectedModel" name="model[]">
<option v-for="model in models" :key="model.name" :value="model">{{ model.label }}</option>
<option v-for="m in models" :key="m.name" :value="m">{{ model.label }}</option>
</select>
<select v-model="selectedOperator" name="operator[]">
@ -17,9 +17,9 @@
v-for="input in availableInputs"
:key="input.id"
v-model="input.value"
:type="selectedOperator?.type || selectedModel?.type"
:type="(selectedOperator?.type || selectedModel?.type)!"
:value="input.value"
@update:modelValue="onInput"
@update:model-value="onInput"
/>
<span v-if="valueSuffix" class="suffix">{{ valueSuffix }}</span>
@ -39,7 +39,7 @@ const RuleInput = defineAsyncComponent(() => import('@/components/playlist/smart
const props = defineProps<{ rule: SmartPlaylistRule }>()
const { rule } = toRefs(props)
const mutatedRule = Object.assign({}, rule.value)
const mutatedRule = Object.assign({}, rule.value) as SmartPlaylistRule
const selectedModel = ref<SmartPlaylistModel>()
const selectedOperator = ref<SmartPlaylistOperator>()
@ -104,8 +104,8 @@ const emit = defineEmits<{
const onInput = () => {
emit('input', {
id: mutatedRule.id,
model: selectedModel.value,
operator: selectedOperator.value?.operator,
model: selectedModel.value!,
operator: selectedOperator.value?.operator!,
value: availableInputs.value.map(input => input.value)
})
}

View file

@ -10,15 +10,15 @@
</div>
<Rule
v-for="rule in mutatedGroup.rules"
:key="rule.id"
:rule="rule"
@input="onRuleChanged"
@remove="removeRule(rule)"
v-for="rule in mutatedGroup.rules"
/>
<Btn @click.prevent="addRule" class="btn-add-rule" green small uppercase>
<icon :icon="faPlus"/>
<Btn class="btn-add-rule" green small uppercase @click.prevent="addRule">
<icon :icon="faPlus" />
Rule
</Btn>
</div>
@ -44,7 +44,7 @@ const notifyParentForUpdate = () => emit('input', mutatedGroup)
const addRule = () => mutatedGroup.rules.push(playlistStore.createEmptySmartPlaylistRule())
const onRuleChanged = (data: SmartPlaylistRule) => {
Object.assign(mutatedGroup.rules.find(r => r.id === data.id), data)
Object.assign(mutatedGroup.rules.find(r => r.id === data.id)!, data)
notifyParentForUpdate()
}

View file

@ -6,7 +6,7 @@
import { computed, toRefs } from 'vue'
import inputTypes from '@/config/smart-playlist/inputTypes'
const props = withDefaults(defineProps<{ type?: keyof typeof inputTypes, value?: any }>(), { value: undefined })
const props = withDefaults(defineProps<{ type: keyof typeof inputTypes, value?: any }>(), { value: undefined })
const { type } = toRefs(props)
const emit = defineEmits<{ (e: 'update:modelValue', value: any): void }>()

View file

@ -21,7 +21,7 @@
</p>
<div class="buttons">
<Btn class="connect" @click.prevent="connect">
<icon :icon="faLastfm"/>
<icon :icon="faLastfm" />
{{ connected ? 'Reconnect' : 'Connect' }}
</Btn>

View file

@ -1,26 +1,26 @@
<template>
<div>
<div class="form-row" v-if="!isPhone">
<div v-if="!isPhone" class="form-row">
<label>
<CheckBox name="notify" v-model="preferences.notify"/>
<CheckBox v-model="preferences.notify" name="notify" />
Show Now Playing song notification
</label>
</div>
<div class="form-row" v-if="!isPhone">
<div v-if="!isPhone" class="form-row">
<label>
<CheckBox name="confirm_closing" v-model="preferences.confirmClosing"/>
<CheckBox v-model="preferences.confirmClosing" name="confirm_closing" />
Confirm before closing Koel
</label>
</div>
<div class="form-row" v-if="isPhone">
<div v-if="isPhone" class="form-row">
<label>
<CheckBox name="transcode_on_mobile" v-model="preferences.transcodeOnMobile"/>
<CheckBox v-model="preferences.transcodeOnMobile" name="transcode_on_mobile" />
Convert and play media at 128kbps on mobile
</label>
</div>
<div class="form-row">
<label>
<CheckBox name="show_album_art_overlay" v-model="preferences.showAlbumArtOverlay"/>
<CheckBox v-model="preferences.showAlbumArtOverlay" name="show_album_art_overlay" />
Show a translucent, blurred overlay of the current albums art
</label>
</div>

View file

@ -40,14 +40,14 @@
type="password"
>
<span class="password-rules help">
Min. 10 characters. Should be a mix of characters, numbers, and symbols.
</span>
Min. 10 characters. Should be a mix of characters, numbers, and symbols.
</span>
</label>
</div>
<div class="form-row">
<Btn class="btn-submit" type="submit">Save</Btn>
<span v-if="isDemo" class="demo-notice">
<span v-if="isDemo()" class="demo-notice">
Changes will not be saved in the demo version.
</span>
</div>

View file

@ -18,6 +18,8 @@ import { slugToTitle } from '@/utils'
const props = defineProps<{ theme: Theme }>()
const { theme } = toRefs(props)
const emit = defineEmits<{ (e: 'selected', theme: Theme): void }>()
const name = theme.value.name ? theme.value.name : slugToTitle(theme.value.id)
const thumbnailStyles: Record<string, string> = {

View file

@ -3,7 +3,7 @@
<h1>Theme</h1>
<ul class="themes">
<li v-for="theme in themes" :key="theme.id" data-testid="theme-card">
<ThemeCard :key="theme.id" :theme="theme" @selected="setTheme"/>
<ThemeCard :key="theme.id" :theme="theme" @selected="setTheme" />
</li>
</ul>
</section>

View file

@ -2,8 +2,8 @@
<section id="albumsWrapper">
<ScreenHeader layout="collapsed">
Albums
<template v-slot:controls>
<ViewModeSwitch v-model="viewMode"/>
<template #controls>
<ViewModeSwitch v-model="viewMode" />
</template>
</ScreenHeader>
@ -15,11 +15,11 @@
@scroll="scrolling"
>
<template v-if="showSkeletons">
<AlbumCardSkeleton v-for="i in 10" :key="i" :layout="itemLayout"/>
<AlbumCardSkeleton v-for="i in 10" :key="i" :layout="itemLayout" />
</template>
<template v-else>
<AlbumCard v-for="album in albums" :key="album.id" :album="album" :layout="itemLayout"/>
<ToTopButton/>
<AlbumCard v-for="album in albums" :key="album.id" :album="album" :layout="itemLayout" />
<ToTopButton />
</template>
</div>
</section>

View file

@ -1,16 +1,16 @@
<template>
<section id="albumWrapper">
<ScreenHeaderSkeleton v-if="loading"/>
<section v-if="album" id="albumWrapper">
<ScreenHeaderSkeleton v-if="loading" />
<ScreenHeader v-if="!loading && album" :layout="songs.length === 0 ? 'collapsed' : headerLayout">
{{ album.name }}
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:thumbnail>
<AlbumThumbnail :entity="album"/>
<template #thumbnail>
<AlbumThumbnail :entity="album" />
</template>
<template v-slot:meta>
<template #meta>
<a v-if="isNormalArtist" :href="`#/artist/${album.artist_id}`" class="artist">{{ album.artist_name }}</a>
<span v-else class="nope">{{ album.artist_name }}</span>
<span>{{ pluralize(songs, 'song') }}</span>
@ -19,7 +19,6 @@
<a
v-if="allowDownload"
class="download"
href
role="button"
title="Download all songs in album"
@click.prevent="download"
@ -28,11 +27,11 @@
</a>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="songs.length && (!isPhone || showingControls)"
@playAll="playAll"
@playSelected="playSelected"
@play-all="playAll"
@play-selected="playSelected"
/>
</template>
</ScreenHeader>
@ -41,20 +40,20 @@
<template #header>
<label :class="{ active: activeTab === 'Songs' }">
Songs
<input type="radio" name="tab" value="Songs" v-model="activeTab"/>
<input v-model="activeTab" type="radio" name="tab" value="Songs">
</label>
<label :class="{ active: activeTab === 'OtherAlbums' }">
Other Albums
<input type="radio" name="tab" value="OtherAlbums" v-model="activeTab"/>
<input v-model="activeTab" type="radio" name="tab" value="OtherAlbums">
</label>
<label :class="{ active: activeTab === 'Info' }" v-if="useLastfm">
<label v-if="useLastfm" :class="{ active: activeTab === 'Info' }">
Information
<input type="radio" name="tab" value="Info" v-model="activeTab"/>
<input v-model="activeTab" type="radio" name="tab" value="Info">
</label>
</template>
<div v-show="activeTab === 'Songs'">
<SongListSkeleton v-if="loading"/>
<SongListSkeleton v-if="loading" />
<SongList
v-else
ref="songList"
@ -67,21 +66,21 @@
<div v-show="activeTab === 'OtherAlbums'" class="albums-pane" data-testid="albums-pane">
<template v-if="otherAlbums">
<ul v-if="otherAlbums.length" class="as-list">
<li v-for="album in otherAlbums" :key="album.id">
<AlbumCard :album="album" layout="compact"/>
<li v-for="a in otherAlbums" :key="a.id">
<AlbumCard :album="a" layout="compact" />
</li>
</ul>
<p v-else class="text-secondary">No other albums by {{ album.artist_name }} found in the library.</p>
</template>
<ul v-else class="as-list">
<li v-for="i in 12" :key="i">
<AlbumCardSkeleton layout="compact"/>
<AlbumCardSkeleton layout="compact" />
</li>
</ul>
</div>
<div v-show="activeTab === 'Info'" class="info-pane" v-if="useLastfm && album">
<AlbumInfo :album="album" mode="full"/>
<div v-show="activeTab === 'Info'" v-if="useLastfm && album" class="info-pane">
<AlbumInfo :album="album" mode="full" />
</div>
</ScreenTabs>
</section>
@ -180,7 +179,7 @@ onMounted(async () => (albumId.value = parseInt(getRouteParam('id')!)))
onRouteChanged(route => route.screen === 'Album' && (albumId.value = parseInt(getRouteParam('id')!)))
// if the current album has been deleted, go back to the list
eventBus.on('SONGS_UPDATED', () => albumStore.byId(albumId.value) || go('albums'))
eventBus.on('SONGS_UPDATED', () => albumStore.byId(albumId.value!) || go('albums'))
</script>
<style lang="scss" scoped>

View file

@ -2,27 +2,27 @@
<section id="songsWrapper">
<ScreenHeader :layout="headerLayout">
All Songs
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:thumbnail>
<ThumbnailStack :thumbnails="thumbnails"/>
<template #thumbnail>
<ThumbnailStack :thumbnails="thumbnails" />
</template>
<template v-if="totalSongCount" v-slot:meta>
<template v-if="totalSongCount" #meta>
<span>{{ pluralize(totalSongCount, 'song') }}</span>
<span>{{ totalDuration }}</span>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="totalSongCount && (!isPhone || showingControls)"
@playAll="playAll"
@playSelected="playSelected"
@play-all="playAll"
@playselected="playSelected"
/>
</template>
</ScreenHeader>
<SongListSkeleton v-if="showSkeletons"/>
<SongListSkeleton v-if="showSkeletons" />
<SongList
v-else
ref="songList"

View file

@ -2,8 +2,8 @@
<section id="artistsWrapper">
<ScreenHeader layout="collapsed">
Artists
<template v-slot:controls>
<ViewModeSwitch v-model="viewMode"/>
<template #controls>
<ViewModeSwitch v-model="viewMode" />
</template>
</ScreenHeader>
@ -15,11 +15,11 @@
@scroll="scrolling"
>
<template v-if="showSkeletons">
<ArtistCardSkeleton v-for="i in 10" :key="i" :layout="itemLayout"/>
<ArtistCardSkeleton v-for="i in 10" :key="i" :layout="itemLayout" />
</template>
<template v-else>
<ArtistCard v-for="artist in artists" :key="artist.id" :artist="artist" :layout="itemLayout"/>
<ToTopButton/>
<ArtistCard v-for="artist in artists" :key="artist.id" :artist="artist" :layout="itemLayout" />
<ToTopButton />
</template>
</div>
</section>

View file

@ -1,16 +1,16 @@
<template>
<section id="artistWrapper">
<ScreenHeaderSkeleton v-if="loading"/>
<ScreenHeaderSkeleton v-if="loading" />
<ScreenHeader v-if="!loading && artist" :layout="songs.length === 0 ? 'collapsed' : headerLayout">
{{ artist.name }}
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:thumbnail>
<ArtistThumbnail :entity="artist"/>
<template #thumbnail>
<ArtistThumbnail :entity="artist" />
</template>
<template v-slot:meta>
<template #meta>
<span>{{ pluralize(albumCount, 'album') }}</span>
<span>{{ pluralize(songs, 'song') }}</span>
<span>{{ duration }}</span>
@ -18,7 +18,6 @@
<a
v-if="allowDownload"
class="download"
href
role="button"
title="Download all songs by this artist"
@click.prevent="download"
@ -27,11 +26,11 @@
</a>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="songs.length && (!isPhone || showingControls)"
@playAll="playAll"
@playSelected="playSelected"
@play-all="playAll"
@play-selected="playSelected"
/>
</template>
</ScreenHeader>
@ -40,20 +39,20 @@
<template #header>
<label :class="{ active: activeTab === 'Songs' }">
Songs
<input type="radio" name="tab" value="Songs" v-model="activeTab"/>
<input v-model="activeTab" type="radio" name="tab" value="Songs">
</label>
<label :class="{ active: activeTab === 'Albums' }">
Albums
<input type="radio" name="tab" value="Albums" v-model="activeTab"/>
<input v-model="activeTab" type="radio" name="tab" value="Albums">
</label>
<label :class="{ active: activeTab === 'Info' }" v-if="useLastfm">
<label v-if="useLastfm" :class="{ active: activeTab === 'Info' }">
Information
<input type="radio" name="tab" value="Info" v-model="activeTab"/>
<input v-model="activeTab" type="radio" name="tab" value="Info">
</label>
</template>
<div v-show="activeTab === 'Songs'" class="songs-pane">
<SongListSkeleton v-if="loading"/>
<SongListSkeleton v-if="loading" />
<SongList
v-else
ref="songList"
@ -66,18 +65,18 @@
<div v-show="activeTab === 'Albums'" class="albums-pane">
<ul v-if="albums" class="as-list">
<li v-for="album in albums" :key="album.id">
<AlbumCard :album="album" layout="compact"/>
<AlbumCard :album="album" layout="compact" />
</li>
</ul>
<ul v-else class="as-list">
<li v-for="i in 12" :key="i">
<AlbumCardSkeleton layout="compact"/>
<AlbumCardSkeleton layout="compact" />
</li>
</ul>
</div>
<div v-show="activeTab === 'Info'" class="info-pane" v-if="useLastfm && artist">
<ArtistInfo :artist="artist" mode="full"/>
<div v-show="activeTab === 'Info'" v-if="useLastfm && artist" class="info-pane">
<ArtistInfo :artist="artist" mode="full" />
</div>
</ScreenTabs>
</section>

View file

@ -2,20 +2,19 @@
<section id="favoritesWrapper">
<ScreenHeader :layout="songs.length === 0 ? 'collapsed' : headerLayout">
Songs You Love
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:thumbnail>
<ThumbnailStack :thumbnails="thumbnails"/>
<template #thumbnail>
<ThumbnailStack :thumbnails="thumbnails" />
</template>
<template v-slot:meta v-if="songs.length">
<template v-if="songs.length" #meta>
<span>{{ pluralize(songs, 'song') }}</span>
<span>{{ duration }}</span>
<a
v-if="allowDownload"
class="download"
href
role="button"
title="Download all songs in playlist"
@click.prevent="download"
@ -24,16 +23,16 @@
</a>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="songs.length && (!isPhone || showingControls)"
@playAll="playAll"
@playSelected="playSelected"
@play-all="playAll"
@play-selected="playSelected"
/>
</template>
</ScreenHeader>
<SongListSkeleton v-if="loading"/>
<SongListSkeleton v-if="loading" />
<SongList
v-if="songs.length"
ref="songList"
@ -44,13 +43,13 @@
/>
<ScreenEmptyState v-else>
<template v-slot:icon>
<icon :icon="faHeartBroken"/>
<template #icon>
<icon :icon="faHeartBroken" />
</template>
No favorites yet.
<span class="secondary d-block">
Click the&nbsp;
<icon :icon="faHeart"/>&nbsp;
<icon :icon="faHeart" />&nbsp;
icon to mark a song as favorite.
</span>
</ScreenEmptyState>

View file

@ -1,10 +1,10 @@
<template>
<section id="genresWrapper">
<ScreenHeader layout="compact">
<ScreenHeader layout="collapsed">
Genres
</ScreenHeader>
<div class="main-scroll-wrap">
<ul class="genres" v-if="genres">
<ul v-if="genres" class="genres">
<li v-for="genre in genres" :key="genre.name" :class="`level-${getLevel(genre)}`">
<a
:href="`/#/genres/${encodeURIComponent(genre.name)}`"
@ -15,9 +15,9 @@
</a>
</li>
</ul>
<ul class="genres" v-else>
<ul v-else class="genres">
<li v-for="i in 20" :key="i">
<GenreItemSkeleton/>
<GenreItemSkeleton />
</li>
</ul>
</div>

View file

@ -1,25 +1,25 @@
<template>
<section id="genreWrapper">
<ScreenHeader :layout="headerLayout" v-if="genre">
Genre: <span class="text-thin">{{ decodeURIComponent(name) }}</span>
<ControlsToggle v-if="songs.length" v-model="showingControls"/>
<ScreenHeader v-if="genre" :layout="headerLayout">
Genre: <span class="text-thin">{{ decodeURIComponent(name!) }}</span>
<ControlsToggle v-if="songs.length" v-model="showingControls" />
<template v-slot:thumbnail>
<ThumbnailStack :thumbnails="thumbnails"/>
<template #thumbnail>
<ThumbnailStack :thumbnails="thumbnails" />
</template>
<template v-if="genre" v-slot:meta>
<template v-if="genre" #meta>
<span>{{ pluralize(genre.song_count, 'song') }}</span>
<span>{{ duration }}</span>
</template>
<template v-slot:controls>
<SongListControls v-if="!isPhone || showingControls" @playAll="playAll" @playSelected="playSelected"/>
<template #controls>
<SongListControls v-if="!isPhone || showingControls" @play-all="playAll" @play-selected="playSelected" />
</template>
</ScreenHeader>
<ScreenHeaderSkeleton v-else/>
<ScreenHeaderSkeleton v-else />
<SongListSkeleton v-if="showSkeletons"/>
<SongListSkeleton v-if="showSkeletons" />
<SongList
v-else
ref="songList"
@ -30,8 +30,8 @@
/>
<ScreenEmptyState v-if="!songs.length && !loading">
<template v-slot:icon>
<icon :icon="faTags"/>
<template #icon>
<icon :icon="faTags" />
</template>
No songs in this genre.

View file

@ -4,8 +4,8 @@
<div class="main-scroll-wrap" @scroll="scrolling">
<ScreenEmptyState v-if="libraryEmpty">
<template v-slot:icon>
<icon :icon="faVolumeOff"/>
<template #icon>
<icon :icon="faVolumeOff" />
</template>
No songs found.
<span class="secondary d-block">
@ -15,19 +15,19 @@
<template v-else>
<div class="two-cols">
<MostPlayedSongs data-testid="most-played-songs" :loading="loading"/>
<RecentlyPlayedSongs data-testid="recently-played-songs" :loading="loading"/>
<MostPlayedSongs data-testid="most-played-songs" :loading="loading" />
<RecentlyPlayedSongs data-testid="recently-played-songs" :loading="loading" />
</div>
<div class="two-cols">
<RecentlyAddedAlbums data-testid="recently-added-albums" :loading="loading"/>
<RecentlyAddedSongs data-testid="recently-added-songs" :loading="loading"/>
<RecentlyAddedAlbums data-testid="recently-added-albums" :loading="loading" />
<RecentlyAddedSongs data-testid="recently-added-songs" :loading="loading" />
</div>
<MostPlayedArtists data-testid="most-played-artists" :loading="loading"/>
<MostPlayedAlbums data-testid="most-played-albums" :loading="loading"/>
<MostPlayedArtists data-testid="most-played-artists" :loading="loading" />
<MostPlayedAlbums data-testid="most-played-albums" :loading="loading" />
<ToTopButton/>
<ToTopButton />
</template>
</div>
</section>

View file

@ -4,8 +4,8 @@
<div class="main-scroll-wrap">
<ScreenEmptyState>
<template v-slot:icon>
<icon :icon="faKiwiBird" :mask="faMap" transform="shrink-12"/>
<template #icon>
<icon :icon="faKiwiBird" :mask="faMap" transform="shrink-12" />
</template>
The requested content cannot be found.

View file

@ -2,18 +2,17 @@
<section v-if="playlist" id="playlistWrapper">
<ScreenHeader :layout="songs.length === 0 ? 'collapsed' : headerLayout" :disabled="loading">
{{ playlist.name }}
<ControlsToggle v-if="songs.length" v-model="showingControls"/>
<ControlsToggle v-if="songs.length" v-model="showingControls" />
<template v-slot:thumbnail>
<ThumbnailStack :thumbnails="thumbnails"/>
<template #thumbnail>
<ThumbnailStack :thumbnails="thumbnails" />
</template>
<template v-if="songs.length" v-slot:meta>
<template v-if="songs.length" #meta>
<span>{{ pluralize(songs, 'song') }}</span>
<span>{{ duration }}</span>
<a
v-if="allowDownload"
href
role="button"
title="Download all songs in playlist"
@click.prevent="download"
@ -22,19 +21,19 @@
</a>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="!isPhone || showingControls"
:config="controlsConfig"
@deletePlaylist="destroy"
@playAll="playAll"
@playSelected="playSelected"
@delete-playlist="destroy"
@play-all="playAll"
@play-selected="playSelected"
@refresh="fetchSongs(true)"
/>
</template>
</ScreenHeader>
<SongListSkeleton v-show="loading"/>
<SongListSkeleton v-show="loading" />
<SongList
v-if="!loading && songs.length"
ref="songList"
@ -45,8 +44,8 @@
/>
<ScreenEmptyState v-if="!songs.length && !loading">
<template v-slot:icon>
<icon :icon="faFile"/>
<template #icon>
<icon :icon="faFile" />
</template>
<template v-if="playlist?.is_smart">
@ -111,9 +110,9 @@ const { removeSongsFromPlaylist } = usePlaylistManagement()
const allowDownload = toRef(commonStore.state, 'allow_download')
const destroy = () => eventBus.emit('PLAYLIST_DELETE', playlist.value)
const destroy = () => eventBus.emit('PLAYLIST_DELETE', playlist.value!)
const download = () => downloadService.fromPlaylist(playlist.value!)
const editPlaylist = () => eventBus.emit('MODAL_SHOW_EDIT_PLAYLIST_FORM', playlist.value)
const editPlaylist = () => eventBus.emit('MODAL_SHOW_EDIT_PLAYLIST_FORM', playlist.value!)
const removeSelected = async () => await removeSongsFromPlaylist(playlist.value!, selectedSongs.value)

View file

@ -3,10 +3,10 @@
<ScreenHeader>Profile &amp; Preferences</ScreenHeader>
<div class="main-scroll-wrap">
<ProfileForm/>
<ThemeList/>
<PreferencesForm/>
<LastfmIntegration/>
<ProfileForm />
<ThemeList />
<PreferencesForm />
<LastfmIntegration />
</div>
</section>
</template>
@ -16,7 +16,8 @@ import ScreenHeader from '@/components/ui/ScreenHeader.vue'
import ProfileForm from '@/components/profile-preferences/ProfileForm.vue'
import LastfmIntegration from '@/components/profile-preferences/LastfmIntegration.vue'
import PreferencesForm from '@/components/profile-preferences/PreferencesForm.vue'
import ThemeList from '@/components/profile-preferences/ThemeList.vue'</script>
import ThemeList from '@/components/profile-preferences/ThemeList.vue'
</script>
<style lang="scss">
#profileWrapper {

View file

@ -2,29 +2,29 @@
<section id="queueWrapper">
<ScreenHeader :layout="songs.length === 0 ? 'collapsed' : headerLayout">
Current Queue
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:thumbnail>
<ThumbnailStack :thumbnails="thumbnails"/>
<template #thumbnail>
<ThumbnailStack :thumbnails="thumbnails" />
</template>
<template v-if="songs.length" v-slot:meta>
<template v-if="songs.length" #meta>
<span>{{ pluralize(songs, 'song') }}</span>
<span>{{ duration }}</span>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="songs.length && (!isPhone || showingControls)"
:config="controlConfig"
@clearQueue="clearQueue"
@playAll="playAll"
@playSelected="playSelected"
@clear-queue="clearQueue"
@play-all="playAll"
@play-selected="playSelected"
/>
</template>
</ScreenHeader>
<SongListSkeleton v-if="loading"/>
<SongListSkeleton v-if="loading" />
<SongList
v-if="songs.length"
ref="songList"
@ -35,8 +35,8 @@
/>
<ScreenEmptyState v-else>
<template v-slot:icon>
<icon :icon="faCoffee"/>
<template #icon>
<icon :icon="faCoffee" />
</template>
No songs queued.

View file

@ -2,33 +2,33 @@
<section id="recentlyPlayedWrapper">
<ScreenHeader :layout="songs.length === 0 ? 'collapsed' : headerLayout">
Recently Played
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:thumbnail>
<ThumbnailStack :thumbnails="thumbnails"/>
<template #thumbnail>
<ThumbnailStack :thumbnails="thumbnails" />
</template>
<template v-slot:meta v-if="songs.length">
<template v-if="songs.length" #meta>
<span>{{ pluralize(songs, 'song') }}</span>
<span>{{ duration }}</span>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="songs.length && (!isPhone || showingControls)"
@playAll="playAll"
@playSelected="playSelected"
@play-all="playAll"
@play-selected="playSelected"
/>
</template>
</ScreenHeader>
<SongListSkeleton v-if="loading"/>
<SongListSkeleton v-if="loading" />
<SongList v-if="songs.length" ref="songList" @press:enter="onPressEnter" @scroll-breakpoint="onScrollBreakpoint"/>
<SongList v-if="songs.length" ref="songList" @press:enter="onPressEnter" @scroll-breakpoint="onScrollBreakpoint" />
<ScreenEmptyState v-else>
<template v-slot:icon>
<icon :icon="faClock"/>
<template #icon>
<icon :icon="faClock" />
</template>
No songs recently played.
<span class="secondary d-block">Start playing to populate this playlist.</span>

View file

@ -3,14 +3,14 @@
<ScreenHeader layout="collapsed">
Upload Media
<template v-slot:controls>
<BtnGroup uppercased v-if="hasUploadFailures">
<template #controls>
<BtnGroup v-if="hasUploadFailures" uppercased>
<Btn data-testid="upload-retry-all-btn" green @click="retryAll">
<icon :icon="faRotateRight"/>
<icon :icon="faRotateRight" />
Retry All
</Btn>
<Btn data-testid="upload-remove-all-btn" orange @click="removeFailedEntries">
<icon :icon="faTrashCan"/>
<icon :icon="faTrashCan" />
Remove Failed
</Btn>
</BtnGroup>
@ -27,13 +27,13 @@
@drop.prevent="onDrop"
@dragover.prevent
>
<div class="upload-files" v-if="files.length">
<UploadItem v-for="file in files" :key="file.id" :file="file" data-testid="upload-item"/>
<div v-if="files.length" class="upload-files">
<UploadItem v-for="file in files" :key="file.id" :file="file" data-testid="upload-item" />
</div>
<ScreenEmptyState v-else>
<template v-slot:icon>
<icon :icon="faUpload"/>
<template #icon>
<icon :icon="faUpload" />
</template>
{{ canDropFolders ? 'Drop files or folders to upload' : 'Drop files to upload' }}
@ -41,15 +41,15 @@
<span class="secondary d-block">
<a class="or-click d-block" role="button">
or click here to select songs
<input :accept="acceptAttribute" multiple name="file[]" type="file" @change="onFileInputChange"/>
<input :accept="acceptAttribute" multiple name="file[]" type="file" @change="onFileInputChange">
</a>
</span>
</ScreenEmptyState>
</div>
<ScreenEmptyState v-else>
<template v-slot:icon>
<icon :icon="faWarning"/>
<template #icon>
<icon :icon="faWarning" />
</template>
No media path set.
</ScreenEmptyState>
@ -85,7 +85,7 @@ const hasUploadFailures = computed(() => files.value.filter((file) => file.statu
const onDragEnter = () => (droppable.value = allowsUpload.value)
const onDragLeave = () => (droppable.value = false)
const onFileInputChange = (event: InputEvent) => {
const onFileInputChange = (event: Event) => {
const selectedFileList = (event.target as HTMLInputElement).files
if (selectedFileList?.length) {

View file

@ -2,12 +2,12 @@
<section id="usersWrapper">
<ScreenHeader layout="collapsed">
Users
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:controls>
<BtnGroup uppercased v-if="showingControls || !isPhone">
<template #controls>
<BtnGroup v-if="showingControls || !isPhone" uppercased>
<Btn class="btn-add" green @click="showAddUserForm">
<icon :icon="faPlus"/>
<icon :icon="faPlus" />
Add
</Btn>
</BtnGroup>
@ -17,7 +17,7 @@
<div class="main-scroll-wrap">
<ul class="users">
<li v-for="user in users" :key="user.id">
<UserCard :user="user"/>
<UserCard :user="user" />
</li>
</ul>
</div>

View file

@ -1,12 +1,12 @@
<template>
<section id="vizContainer" :class="{ fullscreen: isFullscreen }" @dblclick="toggleFullscreen">
<div class="artifacts">
<div class="credits" v-if="selectedVisualizer">
<div v-if="selectedVisualizer" class="credits">
<h3>{{ selectedVisualizer.name }}</h3>
<p class="text-secondary" v-if="selectedVisualizer.credits">
<p v-if="selectedVisualizer.credits" class="text-secondary">
by {{ selectedVisualizer.credits.author }}
<a :href="selectedVisualizer.credits.url" target="_blank">
<icon :icon="faUpRightFromSquare"/>
<icon :icon="faUpRightFromSquare" />
</a>
</p>
</div>
@ -16,7 +16,7 @@
<option v-for="v in visualizers" :key="v.id" :value="v.id">{{ v.name }}</option>
</select>
</div>
<div ref="el" class="viz"/>
<div ref="el" class="viz" />
</section>
</template>

View file

@ -4,8 +4,8 @@
<div id="player">
<ScreenEmptyState data-testid="youtube-placeholder">
<template v-slot:icon>
<icon :icon="faYoutube"/>
<template #icon>
<icon :icon="faYoutube" />
</template>
YouTube videos will be played here.
<span class="d-block instruction">Start a video playback from the right sidebar.</span>

View file

@ -4,13 +4,13 @@
<ol v-if="loading" class="two-cols top-album-list">
<li v-for="i in 4" :key="i">
<AlbumCardSkeleton layout="compact"/>
<AlbumCardSkeleton layout="compact" />
</li>
</ol>
<template v-else>
<ol v-if="albums.length" class="two-cols top-album-list">
<li v-for="album in albums" :key="album.id">
<AlbumCard :album="album" layout="compact"/>
<AlbumCard :album="album" layout="compact" />
</li>
</ol>
<p v-else class="text-secondary">No albums found.</p>

View file

@ -4,13 +4,13 @@
<ol v-if="loading" class="two-cols top-album-list">
<li v-for="i in 4" :key="i">
<ArtistCardSkeleton layout="compact"/>
<ArtistCardSkeleton layout="compact" />
</li>
</ol>
<template v-else>
<ol v-if="artists.length" class="two-cols top-artist-list">
<li v-for="artist in artists" :key="artist.id">
<ArtistCard :artist="artist" layout="compact"/>
<ArtistCard :artist="artist" layout="compact" />
</li>
</ol>
<p v-else class="text-secondary">No artists found.</p>

View file

@ -3,13 +3,13 @@
<h1>Most Played</h1>
<ol v-if="loading" class="top-song-list">
<li v-for="i in 3" :key="i">
<SongCardSkeleton/>
<SongCardSkeleton />
</li>
</ol>
<template v-else>
<ol v-if="songs.length" class="top-song-list">
<li v-for="song in songs" :key="song.id">
<SongCard :song="song"/>
<SongCard :song="song" />
</li>
</ol>
<p v-else class="text-secondary">You dont seem to have been playing.</p>

View file

@ -4,13 +4,13 @@
<ol v-if="loading" class="recently-added-album-list">
<li v-for="i in 2" :key="i">
<AlbumCardSkeleton layout="compact"/>
<AlbumCardSkeleton layout="compact" />
</li>
</ol>
<template v-else>
<ol v-if="albums.length" class="recently-added-album-list">
<li v-for="album in albums" :key="album.id">
<AlbumCard :album="album" layout="compact"/>
<AlbumCard :album="album" layout="compact" />
</li>
</ol>
<p v-else class="text-secondary">No albums added yet.</p>

View file

@ -3,13 +3,13 @@
<h1>New Songs</h1>
<ol v-if="loading" class="recently-added-song-list">
<li v-for="i in 3" :key="i">
<SongCardSkeleton/>
<SongCardSkeleton />
</li>
</ol>
<template v-else>
<ol v-if="songs.length" class="recently-added-song-list">
<li v-for="song in songs" :key="song.id">
<SongCard :song="song"/>
<SongCard :song="song" />
</li>
</ol>
<p v-else class="text-secondary">No songs added so far.</p>

View file

@ -15,13 +15,13 @@
<ol v-if="loading" class="recent-song-list">
<li v-for="i in 3" :key="i">
<SongCardSkeleton/>
<SongCardSkeleton />
</li>
</ol>
<template v-else>
<ol v-if="songs.length" class="recent-song-list">
<li v-for="song in songs" :key="song.id">
<SongCard :song="song"/>
<SongCard :song="song" />
</li>
</ol>
<p v-else class="text-secondary">No songs played as of late.</p>

View file

@ -23,13 +23,13 @@
</h1>
<ul v-if="searching">
<li v-for="i in 6" :key="i">
<SongCardSkeleton/>
<SongCardSkeleton />
</li>
</ul>
<template v-else>
<ul v-if="excerpt.songs.length">
<li v-for="song in excerpt.songs" :key="song.id">
<SongCard :song="song"/>
<SongCard :song="song" />
</li>
</ul>
<p v-else>None found.</p>
@ -40,13 +40,13 @@
<h1>Artists</h1>
<ul v-if="searching">
<li v-for="i in 6" :key="i">
<ArtistAlbumCardSkeleton layout="compact"/>
<ArtistAlbumCardSkeleton layout="compact" />
</li>
</ul>
<template v-else>
<ul v-if="excerpt.artists.length">
<li v-for="artist in excerpt.artists" :key="artist.id">
<ArtistCard :artist="artist" layout="compact"/>
<ArtistCard :artist="artist" layout="compact" />
</li>
</ul>
<p v-else>None found.</p>
@ -57,13 +57,13 @@
<h1>Albums</h1>
<ul v-if="searching">
<li v-for="i in 6" :key="i">
<ArtistAlbumCardSkeleton layout="compact"/>
<ArtistAlbumCardSkeleton layout="compact" />
</li>
</ul>
<template v-else>
<ul v-if="excerpt.albums.length">
<li v-for="album in excerpt.albums" :key="album.id">
<AlbumCard :album="album" layout="compact"/>
<AlbumCard :album="album" layout="compact" />
</li>
</ul>
<p v-else>None found.</p>
@ -72,8 +72,8 @@
</div>
<ScreenEmptyState v-else>
<template v-slot:icon>
<icon :icon="faSearch"/>
<template #icon>
<icon :icon="faSearch" />
</template>
Find songs, artists, and albums,
<span class="secondary d-block">all in one place.</span>

View file

@ -2,28 +2,28 @@
<section id="songResultsWrapper">
<ScreenHeader :layout="songs.length === 0 ? 'collapsed' : headerLayout">
Songs for <span class="text-thin">{{ decodedQ }}</span>
<ControlsToggle v-model="showingControls"/>
<ControlsToggle v-model="showingControls" />
<template v-slot:thumbnail>
<ThumbnailStack :thumbnails="thumbnails"/>
<template #thumbnail>
<ThumbnailStack :thumbnails="thumbnails" />
</template>
<template v-if="songs.length" v-slot:meta>
<template v-if="songs.length" #meta>
<span>{{ pluralize(songs, 'song') }}</span>
<span>{{ duration }}</span>
</template>
<template v-slot:controls>
<template #controls>
<SongListControls
v-if="songs.length && (!isPhone || showingControls)"
@playAll="playAll"
@playSelected="playSelected"
@play-all="playAll"
@play-selected="playSelected"
/>
</template>
</ScreenHeader>
<SongListSkeleton v-if="loading"/>
<SongList v-else ref="songList" @sort="sort" @press:enter="onPressEnter" @scroll-breakpoint="onScrollBreakpoint"/>
<SongListSkeleton v-if="loading" />
<SongList v-else ref="songList" @sort="sort" @press:enter="onPressEnter" @scroll-breakpoint="onScrollBreakpoint" />
</section>
</template>

View file

@ -1,7 +1,7 @@
<template>
<form @submit.prevent="submit" @keydown.esc="maybeClose">
<header>
<span class="cover" :style="{ backgroundImage: `url(${coverUrl})` }"/>
<span class="cover" :style="{ backgroundImage: `url(${coverUrl})` }" />
<div class="meta">
<h1 :class="{ mixed: !editingOnlyOneSong }">{{ displayedTitle }}</h1>
<h2
@ -140,7 +140,7 @@
list="genres"
>
<datalist id="genres">
<option v-for="genre in genres" :key="genre" :value="genre"/>
<option v-for="genre in genres" :key="genre" :value="genre" />
</datalist>
</label>
<label>
@ -165,13 +165,13 @@
tabindex="0"
>
<div class="form-row">
<textarea
v-model="formData.lyrics"
v-koel-focus
data-testid="lyrics-input"
name="lyrics"
title="Lyrics"
/>
<textarea
v-model="formData.lyrics"
v-koel-focus
data-testid="lyrics-input"
name="lyrics"
title="Lyrics"
/>
</div>
</div>
</div>

View file

@ -7,7 +7,7 @@
@contextmenu.prevent="requestContextMenu"
@dblclick.prevent="play"
>
<SongThumbnail :song="song"/>
<SongThumbnail :song="song" />
<main>
<div class="details">
<h3>{{ song.title }}</h3>
@ -16,7 +16,7 @@
- {{ pluralize(song.play_count, 'play') }}
</p>
</div>
<LikeButton :song="song"/>
<LikeButton :song="song" />
</main>
</article>
</template>

View file

@ -18,24 +18,24 @@
</template>
<li v-else @click="queueSongsToBottom">Queue</li>
<template v-if="!isFavoritesScreen">
<li class="separator"/>
<li class="separator" />
<li @click="addSongsToFavorite">Favorites</li>
</template>
<li v-if="normalPlaylists.length" class="separator"/>
<li v-if="normalPlaylists.length" class="separator" />
<li v-for="p in normalPlaylists" :key="p.id" @click="addSongsToExistingPlaylist(p)">{{ p.name }}</li>
</ul>
</li>
<template v-if="isQueueScreen">
<li class="separator"/>
<li class="separator" />
<li @click="removeFromQueue">Remove from Queue</li>
<li class="separator"/>
<li class="separator" />
</template>
<template v-if="isFavoritesScreen">
<li class="separator"/>
<li class="separator" />
<li @click="removeFromFavorites">Remove from Favorites</li>
<li class="separator"/>
<li class="separator" />
</template>
<li v-if="isAdmin" @click="openEditForm">Edit</li>
@ -43,12 +43,12 @@
<li v-if="onlyOneSongSelected" @click="copyUrl">Copy Shareable URL</li>
<template v-if="canBeRemovedFromPlaylist">
<li class="separator"/>
<li class="separator" />
<li @click="removeFromPlaylist">Remove from Playlist</li>
</template>
<template v-if="isAdmin">
<li class="separator"/>
<li class="separator" />
<li @click="deleteFromFilesystem">Delete from Filesystem</li>
</template>
</ContextMenuBase>
@ -73,7 +73,7 @@ const { toastSuccess } = useMessageToaster()
const { showConfirmDialog } = useDialogBox()
const { go, getRouteParam, isCurrentScreen } = useRouter()
const { isAdmin } = useAuthorization()
const { context, base, ContextMenuBase, open, close, trigger } = useContextMenu()
const { base, ContextMenuBase, open, close, trigger } = useContextMenu()
const { removeSongsFromPlaylist } = usePlaylistManagement()
const songs = ref<Song[]>([])
@ -154,6 +154,6 @@ const deleteFromFilesystem = () => trigger(async () => {
eventBus.on('SONG_CONTEXT_MENU_REQUESTED', async (e, _songs) => {
songs.value = arrayify(_songs)
await open(e.pageY, e.pageX, { songs: songs.value })
await open(e.pageY, e.pageX)
})
</script>

View file

@ -1,7 +1,7 @@
<template>
<button :title="title" type="button" @click.stop="toggleLike">
<icon v-if="song.liked" :icon="faHeart"/>
<icon v-else :icon="faEmptyHeart"/>
<icon v-if="song.liked" :icon="faHeart" />
<icon v-else :icon="faEmptyHeart" />
</button>
</template>

View file

@ -18,8 +18,8 @@
>
#
<template v-if="config.sortable">
<icon v-if="sortField === 'track' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'track' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<icon v-if="sortField === 'track' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight" />
<icon v-if="sortField === 'track' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight" />
</template>
</span>
<span
@ -31,8 +31,8 @@
>
Title
<template v-if="config.sortable">
<icon v-if="sortField === 'title' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'title' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<icon v-if="sortField === 'title' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight" />
<icon v-if="sortField === 'title' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight" />
</template>
</span>
<span
@ -44,8 +44,8 @@
>
Album
<template v-if="config.sortable">
<icon v-if="sortField === 'album_name' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'album_name' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<icon v-if="sortField === 'album_name' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight" />
<icon v-if="sortField === 'album_name' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight" />
</template>
</span>
<span
@ -57,12 +57,12 @@
>
Time
<template v-if="config.sortable">
<icon v-if="sortField === 'length' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight"/>
<icon v-if="sortField === 'length' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight"/>
<icon v-if="sortField === 'length' && sortOrder === 'asc'" :icon="faCaretDown" class="text-highlight" />
<icon v-if="sortField === 'length' && sortOrder === 'desc'" :icon="faCaretUp" class="text-highlight" />
</template>
</span>
<span class="extra">
<SongListSorter v-if="config.sortable" :field="sortField" :order="sortOrder" @sort="sort"/>
<SongListSorter v-if="config.sortable" :field="sortField" :order="sortOrder" @sort="sort" />
</span>
</div>
@ -243,7 +243,7 @@ const onDragStart = (row: SongRow, event: DragEvent) => {
// Add "dragging" class to the wrapper so that we can disable pointer events on child elements.
// This prevents dragleave events from firing when the user drags the mouse over the child elements.
wrapper.value.classList.add('dragging')
wrapper.value?.classList.add('dragging')
startDragging(event, selectedSongs.value)
}
@ -261,11 +261,11 @@ const onDragEnter = (event: DragEvent) => {
const onDrop = (item: SongRow, event: DragEvent) => {
if (!config.reorderable || !getDroppedData(event) || !selectedSongs.value.length) {
wrapper.value.classList.remove('dragging')
wrapper.value?.classList.remove('dragging')
return onDragLeave(event)
}
wrapper.value.classList.remove('dragging')
wrapper.value?.classList.remove('dragging')
emit('reorder', item.song)
return onDragLeave(event)
@ -276,7 +276,7 @@ const onDragLeave = (event: DragEvent) => {
return false
}
const onDragEnd = () => wrapper.value.classList.remove('dragging')
const onDragEnd = () => wrapper.value?.classList.remove('dragging')
const openContextMenu = async (row: SongRow, event: MouseEvent) => {
if (!row.selected) {

View file

@ -11,7 +11,7 @@
title="Play all songs"
@click.prevent="playAll"
>
<icon :icon="faPlay" fixed-width/>
<icon :icon="faPlay" fixed-width />
All
</Btn>
@ -22,7 +22,7 @@
title="Play selected songs"
@click.prevent="playSelected"
>
<icon :icon="faPlay" fixed-width/>
<icon :icon="faPlay" fixed-width />
Selected
</Btn>
</template>
@ -36,7 +36,7 @@
title="Shuffle all songs"
@click.prevent="shuffle"
>
<icon :icon="faRandom" fixed-width/>
<icon :icon="faRandom" fixed-width />
All
</Btn>
@ -48,7 +48,7 @@
title="Shuffle selected songs"
@click.prevent="shuffleSelected"
>
<icon :icon="faRandom" fixed-width/>
<icon :icon="faRandom" fixed-width />
Selected
</Btn>
</template>
@ -63,7 +63,7 @@
<BtnGroup>
<Btn v-if="config.refresh" v-koel-tooltip green title="Refresh" @click.prevent="refresh">
<icon :icon="faRotateRight" fixed-width/>
<icon :icon="faRotateRight" fixed-width />
</Btn>
<Btn
@ -74,13 +74,13 @@
title="Delete this playlist"
@click.prevent="deletePlaylist"
>
<icon :icon="faTrashCan"/>
<icon :icon="faTrashCan" />
</Btn>
</BtnGroup>
</div>
<div ref="addToMenu" v-koel-clickaway="closeAddToMenu" class="menu-wrapper">
<AddToMenu :config="mergedConfig.addTo" :songs="selectedSongs" @closing="closeAddToMenu"/>
<AddToMenu :config="mergedConfig.addTo" :songs="selectedSongs" @closing="closeAddToMenu" />
</div>
</div>
</template>
@ -103,7 +103,7 @@ const [songs] = requireInjection<[Ref<Song[]>]>(SongsKey)
const [selectedSongs] = requireInjection(SelectedSongsKey)
const el = ref<HTMLElement>()
const addToButton = ref<InstanceType<Btn>>()
const addToButton = ref<InstanceType<typeof Btn>>()
const addToMenu = ref<HTMLDivElement>()
const showingAddToMenu = ref(false)
const altPressed = ref(false)
@ -147,7 +147,7 @@ watch(showAddToButton, async showingButton => {
await nextTick()
if (showingButton) {
usedFloatingUi = useFloatingUi(addToButton.value.button, addToMenu, { autoTrigger: false })
usedFloatingUi = useFloatingUi(addToButton.value!.button!, addToMenu, { autoTrigger: false })
usedFloatingUi.setup()
} else {
usedFloatingUi?.teardown()

View file

@ -7,11 +7,11 @@
@dblclick.prevent.stop="play"
>
<span class="track-number">
<SoundBars v-if="song.playback_state === 'Playing'"/>
<SoundBars v-if="song.playback_state === 'Playing'" />
<span v-else class="text-secondary">{{ song.track || '' }}</span>
</span>
<span class="thumbnail">
<SongThumbnail :song="song"/>
<SongThumbnail :song="song" />
</span>
<span class="title-artist">
<span class="title text-primary">{{ song.title }}</span>
@ -22,7 +22,7 @@
<span class="album">{{ song.album_name }}</span>
<span class="time">{{ fmtLength }}</span>
<span class="extra">
<LikeButton :song="song"/>
<LikeButton :song="song" />
</span>
</div>
</template>

View file

@ -1,15 +1,20 @@
<template>
<div>
<button ref="button" title="Sort" @click.stop="trigger">
<icon :icon="faSort"/>
<icon :icon="faSort" />
</button>
<menu ref="menu" v-koel-clickaway="hide">
<li v-for="item in menuItems" :class="item.field === field && 'active'" @click="sort(item.field)">
<li
v-for="item in menuItems"
:key="item.label"
:class="item.field === field && 'active'"
@click="sort(item.field)"
>
<span>{{ item.label }}</span>
<span class="icon">
<icon v-if="order === 'asc'" :icon="faArrowDown"/>
<icon v-else :icon="faArrowUp"/>
</span>
<icon v-if="order === 'asc'" :icon="faArrowDown" />
<icon v-else :icon="faArrowUp" />
</span>
</li>
</menu>
</div>

View file

@ -1,8 +1,8 @@
<template>
<div :style="{ backgroundImage: `url(${defaultCover})` }" class="cover">
<img v-koel-hide-broken-icon :alt="song.album_name" :src="song.album_cover" loading="lazy"/>
<img v-koel-hide-broken-icon :alt="song.album_name" :src="song.album_cover" loading="lazy">
<a :title="title" class="control" role="button" @click.prevent="changeSongState">
<icon :icon="song.playback_state === 'Playing' ? faPause : faPlay" class="text-highlight"/>
<icon :icon="song.playback_state === 'Playing' ? faPause : faPlay" class="text-highlight" />
</a>
</div>
</template>

View file

@ -1,5 +1,5 @@
<template>
<div :style="{ backgroundImage: thumbnailUrl ? `url(${thumbnailUrl})` : 'none' }" data-testid="album-art-overlay"/>
<div :style="{ backgroundImage: thumbnailUrl ? `url(${thumbnailUrl})` : 'none' }" data-testid="album-art-overlay" />
</template>
<script lang="ts" setup>

View file

@ -5,10 +5,9 @@
class="cover"
data-testid="album-artist-thumbnail"
>
<img v-koel-hide-broken-icon :alt="entity.name" :src="image" loading="lazy"/>
<img v-koel-hide-broken-icon :alt="entity.name" :src="image" loading="lazy">
<a
class="control control-play"
href
role="button"
@click.prevent="playOrQueue"
@dragenter.prevent="onDragEnter"
@ -17,7 +16,7 @@
@dragover.prevent
>
<span class="hidden">{{ buttonLabel }}</span>
<span class="icon"/>
<span class="icon" />
</a>
</span>
</template>
@ -57,7 +56,7 @@ const buttonLabel = computed(() => forAlbum.value
const { isAdmin: allowsUpload } = useAuthorization()
const playOrQueue = async (event: KeyboardEvent) => {
const playOrQueue = async (event: MouseEvent) => {
const songs = forAlbum.value
? await songStore.fetchForAlbum(entity.value as Album)
: await songStore.fetchForArtist(entity.value as Artist)

View file

@ -12,7 +12,7 @@
fill-rule="nonzero"
stroke="none"
stroke-width="1"
></path>
/>
</svg>
</a>
</template>
@ -20,7 +20,7 @@
<script lang="ts" setup>
import { toRefs } from 'vue'
const props = defineProps({ url: String })
const props = defineProps<{ url: string }>()
const { url } = toRefs(props)
</script>

View file

@ -8,13 +8,13 @@
@dragstart="onDragStart"
@contextmenu.prevent="onContextMenu"
>
<AlbumArtistThumbnail :entity="entity"/>
<AlbumArtistThumbnail :entity="entity" />
<footer>
<div class="name">
<slot name="name"/>
<slot name="name" />
</div>
<p class="meta">
<slot name="meta"/>
<slot name="meta" />
</p>
</footer>
</article>

View file

@ -1,10 +1,10 @@
<template>
<div class="tabs">
<header>
<slot name="header"/>
<slot name="header" />
</header>
<main>
<slot/>
<slot />
</main>
</div>
</template>

View file

@ -1,5 +1,5 @@
<template>
<button type="button" ref="button">
<button ref="button" type="button">
<slot>Click me</slot>
</button>
</template>

View file

@ -1,6 +1,6 @@
<template>
<button data-testid="close-modal-btn" title="Dismiss" type="button" @click.prevent="$emit('click')">
<icon :icon="faTimes"/>
<icon :icon="faTimes" />
</button>
</template>

View file

@ -1,6 +1,6 @@
<template>
<span class="btn-group">
<slot></slot>
<slot />
</span>
</template>

View file

@ -1,7 +1,7 @@
<template>
<Transition name="fade">
<button v-show="showing" ref="el" title="Scroll to top" type="button" @click="scrollToTop">
<icon :icon="faCircleUp"/>&nbsp;
<icon :icon="faCircleUp" />&nbsp;
Top
</button>
</Transition>

View file

@ -1,7 +1,7 @@
<template>
<span>
<input :checked="checked" type="checkbox" v-bind="$attrs" @input="onInput">
<icon :icon="faCheck" v-if="checked"/>
<icon v-if="checked" :icon="faCheck" />
</span>
</template>
@ -17,7 +17,7 @@ const checked = ref(props.modelValue)
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
const onInput = (event: InputEvent) => {
const onInput = (event: Event) => {
checked.value = (event.target as HTMLInputElement).checked
emit('update:modelValue', checked.value)
}

View file

@ -78,20 +78,20 @@ const open = async (_top = 0, _left = 0) => {
try {
await preventOffScreen(el.value!)
await initSubmenus()
initSubmenus()
} catch (e) {
logger.error(e)
// in a non-browser environment (e.g., unit testing), these two functions are broken due to calls to
// getBoundingClientRect() and querySelectorAll()
}
eventBus.emit('CONTEXT_MENU_OPENED', el)
eventBus.emit('CONTEXT_MENU_OPENED', el.value!)
}
const close = () => (shown.value = false)
// ensure there's only one context menu at any time
eventBus.on('CONTEXT_MENU_OPENED', target => target === el || close())
eventBus.on('CONTEXT_MENU_OPENED', target => target === el.value || close())
defineExpose({ open, close, shown })
</script>

View file

@ -6,14 +6,14 @@
<option disabled value="-1">Preset</option>
<option v-for="preset in presets" :key="preset.id" :value="preset.id">{{ preset.name }}</option>
</select>
<icon :icon="faCaretDown" class="arrow text-highlight" size="sm"/>
<icon :icon="faCaretDown" class="arrow text-highlight" size="sm" />
</label>
</header>
<main>
<div class="bands">
<span class="band">
<span class="slider"/>
<span class="slider" />
<label>Preamp</label>
</span>
@ -24,7 +24,7 @@
</span>
<span v-for="band in bands" :key="band.label" class="band">
<span class="slider"/>
<span class="slider" />
<label>{{ band.label }}</label>
</span>
</div>

View file

@ -7,7 +7,7 @@
type="button"
@click.prevent="toggleTab('Lyrics')"
>
<icon :icon="faFileLines" fixed-width/>
<icon :icon="faFileLines" fixed-width />
</button>
<button
id="extraTabArtist"
@ -17,7 +17,7 @@
type="button"
@click.prevent="toggleTab('Artist')"
>
<icon :icon="faMicrophone" fixed-width/>
<icon :icon="faMicrophone" fixed-width />
</button>
<button
id="extraTabAlbum"
@ -27,18 +27,18 @@
type="button"
@click.prevent="toggleTab('Album')"
>
<icon :icon="faCompactDisc" fixed-width/>
<icon :icon="faCompactDisc" fixed-width />
</button>
<button
v-if="useYouTube"
v-koel-tooltip.left
id="extraTabYouTube"
v-koel-tooltip.left
:class="{ active: value === 'YouTube' }"
title="Related YouTube videos"
type="button"
@click.prevent="toggleTab('YouTube')"
>
<icon :icon="faYoutube" fixed-width/>
<icon :icon="faYoutube" fixed-width />
</button>
</template>
@ -48,9 +48,11 @@ import { faYoutube } from '@fortawesome/free-brands-svg-icons'
import { computed } from 'vue'
import { useThirdPartyServices } from '@/composables'
const props = defineProps<{ modelValue?: ExtraPanelTab }>()
const props = withDefaults(defineProps<{ modelValue?: ExtraPanelTab | null }>(), {
modelValue: null
})
const emit = defineEmits<{ (e: 'update:modelValue', value: ExtraPanelTab): void }>()
const emit = defineEmits<{ (e: 'update:modelValue', value: ExtraPanelTab | null): void }>()
const { useYouTube } = useThirdPartyServices()
@ -59,7 +61,7 @@ const value = computed({
set: value => emit('update:modelValue', value)
})
const toggleTab = (tab: ExtraPanelTab) => (value.value = value.value === tab ? undefined : tab)
const toggleTab = (tab: ExtraPanelTab) => (value.value = value.value === tab ? null : tab)
</script>
<style scoped>

View file

@ -55,7 +55,11 @@ new class extends UnitTestCase {
})
})
it.each<[ScreenName, object, string]>([
it.each<[
ScreenName,
typeof favoriteStore | typeof recentlyPlayedStore,
MethodOf<typeof favoriteStore | typeof recentlyPlayedStore>
]>([
['Favorites', favoriteStore, 'fetch'],
['RecentlyPlayed', recentlyPlayedStore, 'fetch']
])('initiates playback for %s screen', async (screenName, store, fetchMethod) => {

Some files were not shown because too many files have changed in this diff Show more