mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
feat: use FontAwesome as components
This commit is contained in:
parent
1861b30f56
commit
67ff46880a
60 changed files with 432 additions and 347 deletions
|
@ -14,11 +14,15 @@
|
|||
"url": "https://github.com/koel/koel"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.1",
|
||||
"alertify.js": "^1.0.12",
|
||||
"axios": "^0.21.1",
|
||||
"blueimp-md5": "^2.3.0",
|
||||
"compare-versions": "^3.5.1",
|
||||
"font-awesome": "^4.7.0",
|
||||
"ismobilejs": "^0.4.0",
|
||||
"local-storage": "^2.0.0",
|
||||
"lodash": "^4.17.19",
|
||||
|
@ -64,7 +68,6 @@
|
|||
"eslint-plugin-vue": "^8.7.1",
|
||||
"factoria": "^4.0.0",
|
||||
"file-loader": "^1.1.6",
|
||||
"font-awesome": "^4.7.0",
|
||||
"husky": "^4.2.5",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"jsdom": "^19.0.0",
|
||||
|
|
|
@ -2,8 +2,10 @@ import './staticLoader'
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { clickaway, droppable, focus } from '@/directives'
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
createApp(App)
|
||||
.component('icon', FontAwesomeIcon)
|
||||
.directive('koel-focus', focus)
|
||||
.directive('koel-clickaway', clickaway)
|
||||
.directive('koel-droppable', droppable)
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
role="button"
|
||||
@click.prevent="shuffle"
|
||||
>
|
||||
<i class="fa fa-random"/>
|
||||
<icon :icon="faRandom"/>
|
||||
</a>
|
||||
<a
|
||||
v-if="allowDownload"
|
||||
|
@ -47,7 +47,7 @@
|
|||
role="button"
|
||||
@click.prevent="download"
|
||||
>
|
||||
<i class="fa fa-download"/>
|
||||
<icon :icon="faDownload"/>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
|
@ -56,6 +56,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faDownload, faRandom } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRef, toRefs } from 'vue'
|
||||
import { defaultCover, eventBus, pluralize, secondsToHis, startDragging } from '@/utils'
|
||||
import { albumStore, artistStore, commonStore, songStore } from '@/stores'
|
||||
|
@ -82,13 +83,5 @@ const requestContextMenu = (event: MouseEvent) => eventBus.emit('ALBUM_CONTEXT_M
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sep {
|
||||
display: none;
|
||||
|
||||
.as-list & {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
@include artist-album-card();
|
||||
</style>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<article :class="mode" class="album-info" data-testid="album-info">
|
||||
<h1 class="name">
|
||||
<span>{{ album.name }}</span>
|
||||
<button :title="`Play all songs in ${album.name}`" class="control play" type="button" @click.prevent="play">
|
||||
<i class="fa fa-play"/>
|
||||
<button :title="`Play all songs in ${album.name}`" class="control" type="button" @click.prevent="play">
|
||||
<icon :icon="faCirclePlay" size="xl"/>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCirclePlay } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, defineAsyncComponent, ref, toRefs, watch } from 'vue'
|
||||
import { useThirdPartyServices } from '@/composables'
|
||||
import { songStore } from '@/stores'
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
data-testid="shuffle-artist"
|
||||
@click.prevent="shuffle"
|
||||
>
|
||||
<i class="fa fa-random"/>
|
||||
<icon :icon="faRandom"/>
|
||||
</a>
|
||||
<a
|
||||
v-if="allowDownload"
|
||||
|
@ -47,7 +47,7 @@
|
|||
data-testid="download-artist"
|
||||
@click.prevent="download"
|
||||
>
|
||||
<i class="fa fa-download"/>
|
||||
<icon :icon="faDownload"/>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
|
@ -56,11 +56,11 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faDownload, faRandom } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRef, toRefs } from 'vue'
|
||||
import { eventBus, pluralize, startDragging } from '@/utils'
|
||||
import { artistStore, commonStore, songStore } from '@/stores'
|
||||
import { downloadService, playbackService } from '@/services'
|
||||
|
||||
import ArtistThumbnail from '@/components/ui/AlbumArtistThumbnail.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ artist: Artist, layout?: ArtistAlbumCardLayout }>(), { layout: 'full' })
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<article :class="mode" class="artist-info" data-testid="artist-info">
|
||||
<h1 class="name">
|
||||
<span>{{ artist.name }}</span>
|
||||
<button :title="`Play all songs by ${artist.name}`" class="play control" type="button" @click.prevent="play">
|
||||
<i class="fa fa-play"/>
|
||||
<button :title="`Play all songs by ${artist.name}`" class="control" type="button" @click.prevent="play">
|
||||
<icon :icon="faCirclePlay" size="xl"/>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCirclePlay } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, ref, toRefs, watch } from 'vue'
|
||||
import { playbackService } from '@/services'
|
||||
import { useThirdPartyServices } from '@/composables'
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<header id="mainHeader">
|
||||
<h1 class="brand">Koel</h1>
|
||||
<span class="hamburger" role="button" title="Show or hide the sidebar" @click="toggleSidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
<icon :icon="faBars"/>
|
||||
</span>
|
||||
<span class="magnifier" role="button" title="Show or hide the search form" @click="toggleSearchForm">
|
||||
<i class="fa fa-search"></i>
|
||||
<icon :icon="faSearch"/>
|
||||
</span>
|
||||
<SearchForm v-if="showSearchForm"/>
|
||||
<div class="header-right">
|
||||
|
@ -20,13 +20,14 @@
|
|||
<span v-if="shouldNotifyNewVersion" class="new-version" data-testid="new-version">
|
||||
{{ latestVersion }} available!
|
||||
</span>
|
||||
<i v-else class="fa fa-info-circle"></i>
|
||||
<icon v-else :icon="faInfoCircle"/>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faBars, faInfoCircle, faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { ref } from 'vue'
|
||||
import { eventBus } from '@/utils'
|
||||
|
|
|
@ -35,11 +35,11 @@
|
|||
type="button"
|
||||
@click.prevent="toggleEqualizer"
|
||||
>
|
||||
<i class="fa fa-sliders"></i>
|
||||
<icon :icon="faSliders"/>
|
||||
</button>
|
||||
|
||||
<a v-else :class="{ active: viewingQueue }" class="queue control" href="#!/queue">
|
||||
<i class="fa fa-list-ol"></i>
|
||||
<icon :icon="faListOl"/>
|
||||
</a>
|
||||
|
||||
<RepeatModeSwitch/>
|
||||
|
@ -49,8 +49,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRef, toRefs } from 'vue'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { faListOl, faSliders } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ref, toRef, toRefs } from 'vue'
|
||||
import { eventBus, isAudioContextSupported as useEqualizer } from '@/utils'
|
||||
import { preferenceStore } from '@/stores'
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div class="side player-controls" :class="{ playing }">
|
||||
<i
|
||||
class="prev fa fa-step-backward control"
|
||||
<icon
|
||||
:icon="faStepBackward"
|
||||
class="prev control"
|
||||
data-testid="play-prev-btn"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -20,7 +21,7 @@
|
|||
title="Play or resume"
|
||||
@click.prevent="toggle"
|
||||
>
|
||||
<i class="fa fa-play"></i>
|
||||
<icon :icon="faPlay" size="lg"/>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
|
@ -31,12 +32,13 @@
|
|||
title="Pause"
|
||||
@click.prevent="toggle"
|
||||
>
|
||||
<i class="fa fa-pause"></i>
|
||||
<icon :icon="faPause" size="lg"/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<i
|
||||
class="next fa fa-step-forward control"
|
||||
<icon
|
||||
:icon="faStepForward"
|
||||
class="next control"
|
||||
data-testid="play-next-btn"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -47,6 +49,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPause, faPlay, faStepBackward, faStepForward } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { playbackService } from '@/services'
|
||||
import { defaultCover } from '@/utils'
|
||||
|
@ -148,8 +151,10 @@ const toggle = async () => await playbackService.toggle()
|
|||
top: 0;
|
||||
left: 0;
|
||||
transition: opacity .4s ease-out;
|
||||
font-size: 3rem;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
font-size: 2rem;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: calc(var(--footer-height) + 30px);
|
||||
|
@ -160,9 +165,8 @@ const toggle = async () => await playbackService.toggle()
|
|||
text-shadow: 0 0 1px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.pause {
|
||||
text-indent: 0;
|
||||
font-size: 2rem;
|
||||
.play {
|
||||
margin-left: .2rem;
|
||||
}
|
||||
|
||||
.enabled {
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
type="button"
|
||||
@click.prevent="currentTab = 'YouTube'"
|
||||
>
|
||||
<i class="fa fa-youtube-play"></i>
|
||||
<icon :icon="faYoutube"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -97,6 +97,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import isMobile from 'ismobilejs'
|
||||
import { faYoutube } from '@fortawesome/free-brands-svg-icons'
|
||||
import { ref, toRef, watch } from 'vue'
|
||||
import { $, eventBus } from '@/utils'
|
||||
import { albumStore, artistStore, preferenceStore as preferences } from '@/stores'
|
||||
|
|
|
@ -5,24 +5,40 @@
|
|||
|
||||
<ul class="menu">
|
||||
<li>
|
||||
<a :class="['home', currentView === 'Home' ? 'active' : '']" href="#!/home">Home</a>
|
||||
<a :class="['home', currentView === 'Home' ? 'active' : '']" href="#!/home">
|
||||
<icon :icon="faHome" fixed-width/>
|
||||
Home
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a v-koel-droppable="handleDrop" :class="['queue', currentView === 'Queue' ? 'active' : '']" href="#!/queue">
|
||||
<icon :icon="faListOl" fixed-width/>
|
||||
Current Queue
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a :class="['songs', currentView === 'Songs' ? 'active' : '']" href="#!/songs">All Songs</a>
|
||||
<a :class="['songs', currentView === 'Songs' ? 'active' : '']" href="#!/songs">
|
||||
<icon :icon="faMusic" fixed-width/>
|
||||
All Songs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a :class="['albums', currentView === 'Albums' ? 'active' : '']" href="#!/albums">Albums</a>
|
||||
<a :class="['albums', currentView === 'Albums' ? 'active' : '']" href="#!/albums">
|
||||
<icon :icon="faCompactDisc" fixed-width/>
|
||||
Albums
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a :class="['artists', currentView === 'Artists' ? 'active' : '']" href="#!/artists">Artists</a>
|
||||
<a :class="['artists', currentView === 'Artists' ? 'active' : '']" href="#!/artists">
|
||||
<icon :icon="faMicrophone" fixed-width/>
|
||||
Artists
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="useYouTube">
|
||||
<a :class="['youtube', currentView === 'YouTube' ? 'active' : '']" href="#!/youtube">YouTube Video</a>
|
||||
<a :class="['youtube', currentView === 'YouTube' ? 'active' : '']" href="#!/youtube">
|
||||
<icon :icon="faYoutube" fixed-width/>
|
||||
YouTube Video
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
@ -34,13 +50,22 @@
|
|||
|
||||
<ul class="menu">
|
||||
<li>
|
||||
<a :class="['settings', currentView === 'Settings' ? 'active' : '']" href="#!/settings">Settings</a>
|
||||
<a :class="['settings', currentView === 'Settings' ? 'active' : '']" href="#!/settings">
|
||||
<icon :icon="faTools" fixed-width/>
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a :class="['upload', currentView === 'Upload' ? 'active' : '']" href="#!/upload">Upload</a>
|
||||
<a :class="['upload', currentView === 'Upload' ? 'active' : '']" href="#!/upload">
|
||||
<icon :icon="faUpload" fixed-width/>
|
||||
Upload
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a :class="['users', currentView === 'Users' ? 'active' : '']" href="#!/users">Users</a>
|
||||
<a :class="['users', currentView === 'Users' ? 'active' : '']" href="#!/users">
|
||||
<icon :icon="faUsers" fixed-width/>
|
||||
Users
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
@ -49,6 +74,17 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import isMobile from 'ismobilejs'
|
||||
import {
|
||||
faCompactDisc,
|
||||
faHome,
|
||||
faListOl,
|
||||
faMicrophone,
|
||||
faMusic,
|
||||
faTools,
|
||||
faUpload,
|
||||
faUsers
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faYoutube } from '@fortawesome/free-brands-svg-icons'
|
||||
import { ref } from 'vue'
|
||||
import { eventBus, resolveSongsFromDragEvent } from '@/utils'
|
||||
import { queueStore } from '@/stores'
|
||||
|
@ -122,10 +158,12 @@ eventBus.on('TOGGLE_SIDEBAR', () => (showing.value = !showing.value))
|
|||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .7rem;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding: 0 12px 0 16px;
|
||||
padding: 0 16px 0 12px;
|
||||
border-left: 4px solid transparent;
|
||||
|
||||
&.active, &:hover {
|
||||
|
@ -142,50 +180,6 @@ eventBus.on('TOGGLE_SIDEBAR', () => (showing.value = !showing.value))
|
|||
&:hover {
|
||||
border-left-color: var(--color-highlight);
|
||||
}
|
||||
|
||||
&::before {
|
||||
width: 24px;
|
||||
display: inline-block;
|
||||
font-family: FontAwesome;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
&.home::before {
|
||||
content: "\f015";
|
||||
}
|
||||
|
||||
&.queue::before {
|
||||
content: "\f0cb";
|
||||
}
|
||||
|
||||
&.songs::before {
|
||||
content: "\f001";
|
||||
}
|
||||
|
||||
&.albums::before {
|
||||
content: "\f152";
|
||||
}
|
||||
|
||||
&.artists::before {
|
||||
content: "\f130";
|
||||
}
|
||||
|
||||
&.youtube::before {
|
||||
content: "\f16a";
|
||||
}
|
||||
|
||||
&.settings::before {
|
||||
content: "\f013";
|
||||
}
|
||||
|
||||
&.users::before {
|
||||
content: "\f0c0";
|
||||
}
|
||||
|
||||
&.upload::before {
|
||||
content: "\f093";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-koel-focus class="about text-secondary" data-testid="about-modal" tabindex="0" @keydown.esc="close">
|
||||
<header>
|
||||
<h1 class="text-white">About Koel</h1>
|
||||
<h1 class="text-primary">About Koel</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
|
|
@ -11,9 +11,14 @@
|
|||
:href="url"
|
||||
@contextmenu.prevent="openContextMenu"
|
||||
>
|
||||
<icon v-if="type === 'favorites'" :icon="faHeart" class="text-maroon" fixed-width/>
|
||||
<icon v-else :icon="faMusic" :mask="faFile" transform="shrink-7 down-2" fixed-width/>
|
||||
{{ playlist.name }}
|
||||
</a>
|
||||
|
||||
<a v-else :class="{ active }" :href="url" @contextmenu.prevent="openContextMenu">
|
||||
<icon v-if="type === 'recently-played'" :icon="faClockRotateLeft" class="text-green" fixed-width/>
|
||||
<icon v-else :icon="faBoltLightning" :mask="faFile" transform="shrink-7 down-2" fixed-width/>
|
||||
{{ playlist.name }}
|
||||
</a>
|
||||
|
||||
|
@ -29,6 +34,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faBoltLightning, faClockRotateLeft, faFile, faHeart, faMusic } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, defineAsyncComponent, nextTick, ref, toRefs } from 'vue'
|
||||
import { alerts, eventBus, pluralize, resolveSongsFromDragEvent } from '@/utils'
|
||||
import { favoriteStore, playlistStore } from '@/stores'
|
||||
|
@ -149,24 +155,6 @@ eventBus.on('LOAD_MAIN_CONTENT', (view: MainViewName, _playlist: Playlist): void
|
|||
span {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "\f0f6";
|
||||
}
|
||||
}
|
||||
|
||||
&.favorites a::before {
|
||||
content: "\f004";
|
||||
color: var(--color-maroon);
|
||||
}
|
||||
|
||||
&.recently-played a::before {
|
||||
content: "\f1da";
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
&.smart a::before {
|
||||
content: "\f069";
|
||||
}
|
||||
|
||||
input {
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<section id="playlists">
|
||||
<h1>Playlists
|
||||
<i
|
||||
<h1>
|
||||
<span>Playlists</span>
|
||||
<icon
|
||||
:class="{ creating }"
|
||||
class="fa fa-plus-circle control create"
|
||||
:icon="faCirclePlus"
|
||||
class="control create"
|
||||
data-testid="sidebar-create-playlist-btn"
|
||||
role="button"
|
||||
title="Create a new playlist"
|
||||
@click.stop.prevent="toggleContextMenu"
|
||||
></i>
|
||||
/>
|
||||
</h1>
|
||||
|
||||
<form v-if="creating" @submit.prevent="createPlaylist" name="create-simple-playlist-form" class="create">
|
||||
|
@ -39,6 +41,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCirclePlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { nextTick, ref, toRef } from 'vue'
|
||||
import { favoriteStore, playlistStore } from '@/stores'
|
||||
import router from '@/router'
|
||||
|
@ -79,11 +82,17 @@ const toggleContextMenu = async (event: MouseEvent) => {
|
|||
|
||||
<style lang="scss">
|
||||
#playlists {
|
||||
h1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.control.create {
|
||||
margin: -8px -10px -10px;
|
||||
font-size: 16px;
|
||||
transition: .3s;
|
||||
padding: 10px;
|
||||
|
||||
&.creating {
|
||||
transform: rotate(135deg);
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
@input="onGroupChanged"
|
||||
/>
|
||||
<Btn class="btn-add-group" green small uppercase @click.prevent="addGroup">
|
||||
<i class="fa fa-plus"></i> Group
|
||||
<icon :icon="faPlus"/>
|
||||
Group
|
||||
</Btn>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -37,6 +38,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { playlistStore } from '@/stores'
|
||||
import { alerts } from '@/utils'
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
@input="onGroupChanged"
|
||||
/>
|
||||
<Btn class="btn-add-group" green small uppercase @click.prevent="addGroup">
|
||||
<i class="fa fa-plus"></i> Group
|
||||
<icon :icon="faPlus"/>
|
||||
</Btn>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -37,6 +37,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, inject, reactive, ref, watch } from 'vue'
|
||||
import { cloneDeep, isEqual } from 'lodash'
|
||||
import { playlistStore } from '@/stores'
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<div class="row" data-testid="smart-playlist-rule-row">
|
||||
<Btn class="remove-rule" red @click.prevent="removeRule"><i class="fa fa-times"></i></Btn>
|
||||
<Btn class="remove-rule" red @click.prevent="removeRule">
|
||||
<icon :icon="faTimes"/>
|
||||
</Btn>
|
||||
|
||||
<select v-model="selectedModel" name="model[]">
|
||||
<option v-for="model in models" :key="model.name" :value="model">{{ model.label }}</option>
|
||||
|
@ -26,6 +28,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, defineAsyncComponent, ref, toRefs, watch } from 'vue'
|
||||
import models from '@/config/smart-playlist/models'
|
||||
import inputTypes from '@/config/smart-playlist/inputTypes'
|
||||
|
|
|
@ -18,13 +18,14 @@
|
|||
/>
|
||||
|
||||
<Btn @click.prevent="addRule" class="btn-add-rule" green small uppercase>
|
||||
<i class="fa fa-plus"></i>
|
||||
<icon :icon="faPlus"/>
|
||||
Rule
|
||||
</Btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { defineAsyncComponent, reactive, toRefs } from 'vue'
|
||||
import { playlistStore } from '@/stores'
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<p>
|
||||
Connecting Koel and your Last.fm account enables such exciting features as
|
||||
<a
|
||||
class="text-orange"
|
||||
class="text-highlight"
|
||||
href="https://www.last.fm/about/trackmymusic"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
|
@ -23,7 +23,7 @@
|
|||
</p>
|
||||
<div class="buttons">
|
||||
<Btn class="connect" @click.prevent="connect">
|
||||
<i class="fa fa-lastfm"></i>
|
||||
<icon :icon="faLastfm"/>
|
||||
{{ connected ? 'Reconnect' : 'Connect' }}
|
||||
</Btn>
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
|||
This installation of Koel has no Last.fm integration.
|
||||
<span v-if="isAdmin" data-testid="lastfm-admin-instruction">
|
||||
Visit
|
||||
<a href="https://docs.koel.dev/3rd-party.html#last-fm" class="text-orange" target="_blank">Koel’s Wiki</a>
|
||||
<a href="https://docs.koel.dev/3rd-party.html#last-fm" class="text-highlight" target="_blank">Koel’s Wiki</a>
|
||||
for a quick how-to.
|
||||
</span>
|
||||
<span v-else data-testid="lastfm-user-instruction">
|
||||
|
@ -48,6 +48,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faLastfm } from '@fortawesome/free-brands-svg-icons'
|
||||
import { computed, defineAsyncComponent } from 'vue'
|
||||
import { authService, httpService } from '@/services'
|
||||
import { forceReloadWindow } from '@/utils'
|
||||
|
|
|
@ -2,25 +2,26 @@
|
|||
<div>
|
||||
<div class="form-row" v-if="!isPhone">
|
||||
<label>
|
||||
<input type="checkbox" name="notify" v-model="preferences.notify">
|
||||
<CheckBox name="notify" v-model="preferences.notify"/>
|
||||
Show “Now Playing” song notification
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row" v-if="!isPhone">
|
||||
<label>
|
||||
<input type="checkbox" name="confirm_closing" v-model="preferences.confirmClosing">
|
||||
<CheckBox name="confirm_closing" v-model="preferences.confirmClosing"/>
|
||||
Confirm before closing Koel
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row" v-if="isPhone">
|
||||
<label>
|
||||
<CheckBox name="transcode_on_mobile" v-model="preferences.transcodeOnMobile"/>
|
||||
<input type="checkbox" name="transcode_on_mobile" v-model="preferences.transcodeOnMobile">
|
||||
Convert and play media at 128kbps on mobile
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>
|
||||
<input type="checkbox" name="show_album_art_overlay" v-model="preferences.showAlbumArtOverlay">
|
||||
<CheckBox name="show_album_art_overlay" v-model="preferences.showAlbumArtOverlay"/>
|
||||
Show a translucent, blurred overlay of the current album’s art
|
||||
</label>
|
||||
</div>
|
||||
|
@ -30,6 +31,7 @@
|
|||
<script lang="ts" setup>
|
||||
import isMobile from 'ismobilejs'
|
||||
import { preferenceStore as preferences } from '@/stores'
|
||||
import CheckBox from '@/components/ui/CheckBox.vue'
|
||||
|
||||
const isPhone = isMobile.phone
|
||||
</script>
|
||||
|
|
|
@ -39,12 +39,12 @@
|
|||
|
||||
<ScreenEmptyState v-else>
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-frown-o"></i>
|
||||
<icon :icon="faHeartBroken"/>
|
||||
</template>
|
||||
No favorites yet.
|
||||
<span class="secondary d-block">
|
||||
Click the
|
||||
<i class="fa fa-heart-o"></i>
|
||||
<icon :icon="faHeart"/>
|
||||
icon to mark a song as favorite.
|
||||
</span>
|
||||
</ScreenEmptyState>
|
||||
|
@ -52,6 +52,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faHeartBroken } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faHeart } from '@fortawesome/free-regular-svg-icons'
|
||||
import { eventBus, pluralize } from '@/utils'
|
||||
import { commonStore, favoriteStore } from '@/stores'
|
||||
import { downloadService } from '@/services'
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="main-scroll-wrap" @scroll="scrolling">
|
||||
<ScreenEmptyState v-if="libraryEmpty">
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-volume-off"></i>
|
||||
<icon :icon="faVolumeOff"/>
|
||||
</template>
|
||||
No songs found.
|
||||
<span class="secondary d-block">
|
||||
|
@ -34,9 +34,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faVolumeOff } from '@fortawesome/free-solid-svg-icons'
|
||||
import { sample } from 'lodash'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { eventBus, noop } from '@/utils'
|
||||
import { commonStore, overviewStore, userStore } from '@/stores'
|
||||
import { useAuthorization, useInfiniteScroll } from '@/composables'
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
<ScreenEmptyState v-if="!songs.length && !loading">
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-file-o"></i>
|
||||
<icon :icon="faFile"/>
|
||||
</template>
|
||||
|
||||
<template v-if="playlist?.is_smart">
|
||||
|
@ -57,6 +57,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faFile } from '@fortawesome/free-regular-svg-icons'
|
||||
import { difference } from 'lodash'
|
||||
import { ref, toRef } from 'vue'
|
||||
import { alerts, eventBus, pluralize } from '@/utils'
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
<ScreenEmptyState v-else>
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-coffee"></i>
|
||||
<icon :icon="faCoffee"/>
|
||||
</template>
|
||||
|
||||
No songs queued.
|
||||
|
@ -43,6 +43,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCoffee } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRef } from 'vue'
|
||||
import { pluralize } from '@/utils'
|
||||
import { commonStore, queueStore } from '@/stores'
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
<ScreenEmptyState v-else>
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-clock-o"></i>
|
||||
<icon :icon="faClock"/>
|
||||
</template>
|
||||
No songs recently played.
|
||||
<span class="secondary d-block">Start playing to populate this playlist.</span>
|
||||
|
@ -31,6 +31,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faClock } from '@fortawesome/free-regular-svg-icons'
|
||||
import { eventBus, pluralize } from '@/utils'
|
||||
import { recentlyPlayedStore } from '@/stores'
|
||||
import { useSongList } from '@/composables'
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
<template v-slot:controls>
|
||||
<BtnGroup uppercased v-if="hasUploadFailures">
|
||||
<Btn data-testid="upload-retry-all-btn" green @click="retryAll">
|
||||
<i class="fa fa-repeat"></i>
|
||||
<icon :icon="faRotateBack"/>
|
||||
Retry All
|
||||
</Btn>
|
||||
<Btn data-testid="upload-remove-all-btn" orange @click="removeFailedEntries">
|
||||
<i class="fa fa-times"></i>
|
||||
<icon :icon="faTimes"/>
|
||||
Remove Failed
|
||||
</Btn>
|
||||
</BtnGroup>
|
||||
|
@ -33,10 +33,11 @@
|
|||
|
||||
<ScreenEmptyState v-else>
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-upload"></i>
|
||||
<icon :icon="faUpload"/>
|
||||
</template>
|
||||
|
||||
{{ instructionText }}
|
||||
{{ canDropFolders ? 'Drop files or folders to upload' : 'Drop files to upload' }}
|
||||
|
||||
<span class="secondary d-block">
|
||||
<a class="or-click d-block" role="button">
|
||||
or click here to select songs
|
||||
|
@ -48,7 +49,7 @@
|
|||
|
||||
<ScreenEmptyState v-else>
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<icon :icon="faWarning"/>
|
||||
</template>
|
||||
No media path set.
|
||||
</ScreenEmptyState>
|
||||
|
@ -57,12 +58,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faRotateBack, faTimes, faUpload, faWarning } from '@fortawesome/free-solid-svg-icons'
|
||||
import ismobile from 'ismobilejs'
|
||||
import md5 from 'blueimp-md5'
|
||||
import { computed, defineAsyncComponent, ref, toRef } from 'vue'
|
||||
|
||||
import { settingStore } from '@/stores'
|
||||
import { eventBus, getAllFileEntries, isDirectoryReadingSupported } from '@/utils'
|
||||
import { eventBus, getAllFileEntries, isDirectoryReadingSupported as canDropFolders } from '@/utils'
|
||||
import { acceptedMediaTypes, UploadFile } from '@/config'
|
||||
import { uploadService } from '@/services'
|
||||
import { useAuthorization } from '@/composables'
|
||||
|
@ -84,10 +86,6 @@ const hasUploadFailures = ref(false)
|
|||
const { isAdmin } = useAuthorization()
|
||||
const allowsUpload = computed(() => isAdmin.value && !ismobile.any)
|
||||
|
||||
const instructionText = isDirectoryReadingSupported
|
||||
? 'Drop files or folders to upload'
|
||||
: 'Drop files to upload'
|
||||
|
||||
const onDragEnter = () => (droppable.value = allowsUpload.value)
|
||||
const onDragLeave = () => (droppable.value = false)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<template v-slot:controls>
|
||||
<BtnGroup uppercased v-if="showingControls || !isPhone">
|
||||
<Btn class="btn-add" data-testid="add-user-btn" green @click="showAddUserForm">
|
||||
<i class="fa fa-plus"></i>
|
||||
<icon :icon="faPlus"/>
|
||||
Add
|
||||
</Btn>
|
||||
</BtnGroup>
|
||||
|
@ -25,6 +25,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { defineAsyncComponent, onMounted, ref, toRef } from 'vue'
|
||||
import { userStore } from '@/stores'
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div id="player">
|
||||
<ScreenEmptyState data-testid="youtube-placeholder">
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-youtube-play"></i>
|
||||
<icon :icon="faYoutube"/>
|
||||
</template>
|
||||
YouTube videos will be played here.
|
||||
<span class="d-block instruction">Start a video playback from the right sidebar.</span>
|
||||
|
@ -15,6 +15,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faYoutube } from '@fortawesome/free-brands-svg-icons'
|
||||
import createYouTubePlayer from 'youtube-player'
|
||||
import { ref } from 'vue'
|
||||
import type { YouTubePlayer } from 'youtube-player/dist/types'
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
<ScreenEmptyState v-else>
|
||||
<template v-slot:icon>
|
||||
<i class="fa fa-search"></i>
|
||||
<icon :icon="faSearch"/>
|
||||
</template>
|
||||
Find songs, artists, and albums,
|
||||
<span class="secondary d-block">all in one place.</span>
|
||||
|
@ -62,6 +62,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ref, toRef } from 'vue'
|
||||
import { eventBus } from '@/utils'
|
||||
import { searchStore } from '@/stores'
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
>
|
||||
<span :style="{ backgroundImage: `url(${song.album_cover}), url(${defaultCover})` }" class="cover">
|
||||
<a class="control" @click.prevent="changeSongState" data-testid="play-control">
|
||||
<i v-if="song.playback_state !== 'Playing'" class="fa fa-play"/>
|
||||
<i class="fa fa-pause" v-else/>
|
||||
<icon :icon="song.playback_state === 'Playing' ? faPause : faPlay" class="text-highlight"/>
|
||||
</a>
|
||||
</span>
|
||||
<span class="main">
|
||||
|
@ -31,6 +30,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPause, faPlay } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { defaultCover, eventBus, pluralize, startDragging } from '@/utils'
|
||||
import { queueStore } from '@/stores'
|
||||
|
@ -88,7 +88,7 @@ article {
|
|||
|
||||
&:hover .cover, &:focus .cover {
|
||||
.control {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&::before {
|
||||
|
@ -103,8 +103,9 @@ article {
|
|||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
@include vertical-center();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::before {
|
||||
content: " ";
|
||||
|
@ -127,16 +128,16 @@ article {
|
|||
width: 28px;
|
||||
height: 28px;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
line-height: 2rem;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
display: none;
|
||||
color: var(--color-text-primary);
|
||||
transition: .3s;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@media (hover: none) {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<template>
|
||||
<button @click.stop="toggleLike" :title="title" class="text-secondary" data-testid="like-btn">
|
||||
<i class="fa fa-heart text-maroon" v-if="song.liked" data-testid="btn-like-liked"></i>
|
||||
<i class="fa fa-heart-o" data-testid="btn-like-unliked" v-else></i>
|
||||
<icon v-if="song.liked" :icon="faHeart" class="text-maroon" data-testid="btn-like-liked"/>
|
||||
<icon v-else :icon="faEmptyHeart" data-testid="btn-like-unliked"/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faHeart } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faHeart as faEmptyHeart } from '@fortawesome/free-regular-svg-icons'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { favoriteStore } from '@/stores'
|
||||
|
||||
|
@ -19,7 +21,7 @@ const toggleLike = () => favoriteStore.toggleOne(song.value)
|
|||
|
||||
<style lang="scss" scoped>
|
||||
button {
|
||||
&:hover .fa-heart-o {
|
||||
&:hover .fa-heart {
|
||||
color: var(--color-maroon);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
@click="sort('track')"
|
||||
>
|
||||
#
|
||||
<i v-show="sortField === 'track' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||
<i v-show="sortField === 'track' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||
<icon v-if="sortField === 'track' && sortOrder === 'asc'" :icon="faAngleDown" class="text-highlight"/>
|
||||
<icon v-if="sortField === 'track' && sortOrder === 'desc'" :icon="faAngleUp" class="text-highlight"/>
|
||||
</span>
|
||||
<span
|
||||
v-if="config.columns.includes('title')"
|
||||
|
@ -27,8 +27,8 @@
|
|||
@click="sort('title')"
|
||||
>
|
||||
Title
|
||||
<i v-show="sortField === 'title' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||
<i v-show="sortField === 'title' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||
<icon v-if="sortField === 'title' && sortOrder === 'asc'" :icon="faAngleDown" class="text-highlight"/>
|
||||
<icon v-if="sortField === 'title' && sortOrder === 'desc'" :icon="faAngleUp" class="text-highlight"/>
|
||||
</span>
|
||||
<span
|
||||
v-if="config.columns.includes('artist')"
|
||||
|
@ -37,8 +37,8 @@
|
|||
@click="sort('artist_name')"
|
||||
>
|
||||
Artist
|
||||
<i v-show="sortField === 'artist_name' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||
<i v-show="sortField === 'artist_name' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||
<icon v-if="sortField === 'artist_name' && sortOrder === 'asc'" :icon="faAngleDown" class="text-highlight"/>
|
||||
<icon v-if="sortField === 'artist_name' && sortOrder === 'desc'" :icon="faAngleUp" class="text-highlight"/>
|
||||
</span>
|
||||
<span
|
||||
v-if="config.columns.includes('album')"
|
||||
|
@ -47,8 +47,8 @@
|
|||
@click="sort('album_name')"
|
||||
>
|
||||
Album
|
||||
<i v-show="sortField === 'album_name' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||
<i v-show="sortField === 'album_name' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||
<icon v-if="sortField === 'album_name' && sortOrder === 'asc'" :icon="faAngleDown" class="text-highlight"/>
|
||||
<icon v-if="sortField === 'album_name' && sortOrder === 'desc'" :icon="faAngleUp" class="text-highlight"/>
|
||||
</span>
|
||||
<span
|
||||
v-if="config.columns.includes('length')"
|
||||
|
@ -56,9 +56,10 @@
|
|||
data-testid="header-length"
|
||||
@click="sort('length')"
|
||||
>
|
||||
<i v-show="sortField === 'length' && sortOrder === 'asc'" class="fa fa-angle-down"></i>
|
||||
<i v-show="sortField === 'length' && sortOrder === 'desc'" class="fa fa-angle-up"></i>
|
||||
<i class="duration-header fa fa-clock-o"></i>
|
||||
<icon v-if="sortField === 'length' && sortOrder === 'asc'" :icon="faAngleDown" class="text-highlight"/>
|
||||
<icon v-if="sortField === 'length' && sortOrder === 'desc'" :icon="faAngleUp" class="text-highlight"/>
|
||||
|
||||
<icon :icon="faClock" class="duration-header"/>
|
||||
</span>
|
||||
<span class="favorite"></span>
|
||||
<span class="play"></span>
|
||||
|
@ -83,6 +84,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faAngleDown, faAngleUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faClock } from '@fortawesome/free-regular-svg-icons'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { findIndex } from 'lodash'
|
||||
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
title="Play all songs"
|
||||
@click.prevent="playAll"
|
||||
>
|
||||
<i class="fa fa-play"></i> All
|
||||
<icon :icon="faPlay"/>
|
||||
All
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
|
@ -20,7 +21,8 @@
|
|||
title="Play selected songs"
|
||||
@click.prevent="playSelected"
|
||||
>
|
||||
<i class="fa fa-play"></i> Selected
|
||||
<icon :icon="faPlay"/>
|
||||
Selected
|
||||
</Btn>
|
||||
</template>
|
||||
|
||||
|
@ -33,7 +35,8 @@
|
|||
title="Shuffle all songs"
|
||||
@click.prevent="shuffle"
|
||||
>
|
||||
<i class="fa fa-random"></i> All
|
||||
<icon :icon="faRandom"/>
|
||||
All
|
||||
</Btn>
|
||||
|
||||
<Btn
|
||||
|
@ -44,7 +47,8 @@
|
|||
title="Shuffle selected songs"
|
||||
@click.prevent="shuffleSelected"
|
||||
>
|
||||
<i class="fa fa-random"></i> Selected
|
||||
<icon :icon="faRandom"/>
|
||||
Selected
|
||||
</Btn>
|
||||
</template>
|
||||
</template>
|
||||
|
@ -69,7 +73,8 @@
|
|||
title="Delete this playlist"
|
||||
@click.prevent="deletePlaylist"
|
||||
>
|
||||
<i class="fa fa-times"></i> Playlist
|
||||
<icon :icon="faTimes"/>
|
||||
Playlist
|
||||
</Btn>
|
||||
|
||||
</BtnGroup>
|
||||
|
@ -85,6 +90,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPlay, faRandom, faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, inject, nextTick, onMounted, onUnmounted, ref, toRefs } from 'vue'
|
||||
import { SelectedSongsKey, SongsKey } from '@/symbols'
|
||||
|
||||
|
|
|
@ -14,13 +14,14 @@
|
|||
<LikeButton :song="song"/>
|
||||
</span>
|
||||
<span class="play" data-testid="song-item-play" role="button" @click.stop="doPlayback">
|
||||
<i class="fa fa-pause-circle" v-if="song.playback_state === 'Playing'"></i>
|
||||
<i class="fa fa-play-circle" v-else></i>
|
||||
<icon v-if="song.playback_state === 'Playing'" :icon="faCirclePause"/>
|
||||
<icon v-else :icon="faCirclePlay"/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCirclePause, faCirclePlay } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { playbackService } from '@/services'
|
||||
import { queueStore } from '@/stores'
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
data-testid="album-artist-thumbnail"
|
||||
>
|
||||
<a
|
||||
class="control control-play font-size-0"
|
||||
class="control control-play"
|
||||
href
|
||||
role="button"
|
||||
@click.prevent="playOrQueue"
|
||||
|
@ -15,12 +15,16 @@
|
|||
@drop.stop.prevent="onDrop"
|
||||
@dragover.prevent
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
<span class="hidden">{{ buttonLabel }}</span>
|
||||
<span class="icon-wrapper">
|
||||
<icon :icon="faPlay" size="5x"/>
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faPlay } from '@fortawesome/free-solid-svg-icons'
|
||||
import { orderBy } from 'lodash'
|
||||
import { computed, ref, toRef, toRefs } from 'vue'
|
||||
import { albumStore, artistStore, queueStore, songStore, userStore } from '@/stores'
|
||||
|
@ -157,20 +161,18 @@ const onDrop = async (event: DragEvent) => {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
width: 60%;
|
||||
max-width: 128px;
|
||||
height: 60%;
|
||||
max-height: 128px;
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEzcHgiIGhlaWdodD0iMTMxcHgiIHZpZXdCb3g9IjAgMCAxMTMgMTMxIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPGcgaWQ9InRyaWFuZ2xlIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8cG9seWdvbiBpZD0iUG9seWdvbiIgZmlsbD0iI0ZGRkZGRiIgcG9pbnRzPSIxMTMuMDIzNzI5IDY1LjI1NDI4MDMgLTEuNTg1Njc4MzFlLTE0IDEzMC41MDg1NjEgLTUuNjg0MzQxODllLTE0IDAiPjwvcG9seWdvbj4KICAgIDwvZz4KPC9zdmc+);
|
||||
background-size: 45%;
|
||||
background-position: 58% 50%;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 50%;
|
||||
.icon-wrapper {
|
||||
background-color: var(--color-bg-primary);
|
||||
color: var(--color-highlight);
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
border-radius: 50%;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-left: 4%;
|
||||
z-index: 99;
|
||||
|
||||
@media (hover: none) {
|
||||
opacity: .5;
|
||||
|
@ -178,7 +180,7 @@ const onDrop = async (event: DragEvent) => {
|
|||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
&::before, &::after {
|
||||
&::before, .icon-wrapper {
|
||||
transition: .3s opacity;
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -189,7 +191,7 @@ const onDrop = async (event: DragEvent) => {
|
|||
background: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
&::after {
|
||||
.icon-wrapper {
|
||||
transform: scale(.9);
|
||||
}
|
||||
}
|
||||
|
@ -215,4 +217,8 @@ const onDrop = async (event: DragEvent) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compact .icon-wrapper {
|
||||
font-size: .3rem; // to control the size of the icon
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -11,6 +11,9 @@ button {
|
|||
font-size: 1rem;
|
||||
padding: .6rem 1rem;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .3rem;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 10rem rgba(0, 0, 0, .05);
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<template>
|
||||
<button data-testid="close-modal-btn" type="button" @click.prevent="$emit('click')">
|
||||
<i class="fa fa-times"></i>
|
||||
<icon :icon="faTimes"/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
button {
|
||||
display: flex;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<template>
|
||||
<Transition name="fade">
|
||||
<button v-show="showing" ref="el" title="Scroll to top" type="button" @click="scrollToTop">
|
||||
<i class="fa fa-arrow-circle-up"/> Top
|
||||
<icon :icon="faCircleUp"/>
|
||||
Top
|
||||
</button>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCircleUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { $ } from '@/utils'
|
||||
|
||||
|
@ -39,10 +41,6 @@ button {
|
|||
background: rgba(0, 0, 0, .5);
|
||||
border: 1px solid var(--color-text-primary);
|
||||
color: var(--color-text-primary);
|
||||
|
||||
i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 415px) {
|
||||
|
|
37
resources/assets/js/components/ui/CheckBox.vue
Normal file
37
resources/assets/js/components/ui/CheckBox.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<span>
|
||||
<input :checked="checked" type="checkbox" v-bind="$attrs" @input="onInput">
|
||||
<icon :icon="faCheck" v-if="checked"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ modelValue?: any }>(), {
|
||||
modelValue: false
|
||||
})
|
||||
|
||||
const checked = ref(props.modelValue)
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const onInput = (event: InputEvent) => {
|
||||
checked.value = (event.target as HTMLInputElement).checked
|
||||
emit('update:modelValue', checked.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
span {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--color-highlight);
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 2px;
|
||||
}
|
||||
</style>
|
|
@ -6,6 +6,7 @@
|
|||
<option disabled value="-1">Preset</option>
|
||||
<option v-for="preset in presets" :value="preset.id" :key="preset.id" v-once>{{ preset.name }}</option>
|
||||
</select>
|
||||
<icon :icon="faAngleDown" class="arrow text-highlight" size="sm"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="bands">
|
||||
|
@ -30,6 +31,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import noUiSlider from 'nouislider'
|
||||
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { eventBus } from '@/utils'
|
||||
import { equalizerStore, preferenceStore as preferences } from '@/stores'
|
||||
|
@ -204,14 +206,8 @@ onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
|
|||
position: relative;
|
||||
margin-bottom: 0;
|
||||
|
||||
&::after {
|
||||
content: '\f107';
|
||||
font-family: FontAwesome;
|
||||
color: var(--color-highlight);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 6px;
|
||||
.arrow {
|
||||
margin-left: -14px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<p v-if="song.id && !song.lyrics" class="none text-secondary">
|
||||
<template v-if="isAdmin">
|
||||
No lyrics found.
|
||||
<button class="text-orange" data-testid="add-lyrics-btn" type="button" @click.prevent="showEditSongForm">
|
||||
<button class="text-highlight" data-testid="add-lyrics-btn" type="button" @click.prevent="showEditSongForm">
|
||||
Click here
|
||||
</button>
|
||||
to add lyrics.
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<div v-if="state.showing" id="overlay" :class="state.type" class="overlay" data-testid="overlay">
|
||||
<div class="display">
|
||||
<SoundBar v-if="state.type === 'loading'"/>
|
||||
<i v-if="state.type === 'error'" class="fa fa-exclamation-circle"/>
|
||||
<i v-if="state.type === 'warning'" class="fa fa-exclamation-triangle"/>
|
||||
<i v-if="state.type === 'info'" class="fa fa-info-circle"/>
|
||||
<i v-if="state.type === 'success'" class="fa fa-check-circle"/>
|
||||
<icon v-if="state.type === 'error'" :icon="faCircleExclamation"/>
|
||||
<icon v-if="state.type === 'warning'" :icon="faWarning"/>
|
||||
<icon v-if="state.type === 'info'" :icon="faCircleInfo"/>
|
||||
<icon v-if="state.type === 'success'" :icon="faCircleCheck"/>
|
||||
|
||||
<span class="message" v-html="state.message"/>
|
||||
</div>
|
||||
|
@ -15,6 +15,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCircleCheck, faCircleExclamation, faCircleInfo, faWarning } from '@fortawesome/free-solid-svg-icons'
|
||||
import { eventBus } from '@/utils'
|
||||
import { defineAsyncComponent, reactive } from 'vue'
|
||||
|
||||
|
@ -48,8 +49,8 @@ eventBus.on({
|
|||
.display {
|
||||
@include vertical-center();
|
||||
|
||||
i {
|
||||
margin-right: 6px;
|
||||
.message {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
<template>
|
||||
<button
|
||||
:class="mode"
|
||||
:class="{ active: mode !== 'NO_REPEAT' }"
|
||||
:title="`Change repeat mode (current mode: ${readableMode})`"
|
||||
class="control"
|
||||
data-testid="repeat-mode-switch"
|
||||
type="button"
|
||||
@click.prevent="changeMode"
|
||||
>
|
||||
<i class="fa fa-repeat"></i>
|
||||
<FontAwesomeLayers>
|
||||
<icon :icon="faRepeat"/>
|
||||
<FontAwesomeLayersText v-if="mode === 'REPEAT_ONE'" counter value="1"/>
|
||||
</FontAwesomeLayers>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FontAwesomeLayers, FontAwesomeLayersText } from '@fortawesome/vue-fontawesome'
|
||||
import { faRepeat } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRef } from 'vue'
|
||||
import { playbackService } from '@/services'
|
||||
import { preferenceStore } from '@/stores'
|
||||
|
@ -28,25 +33,17 @@ const changeMode = () => playbackService.changeRepeatMode()
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
button {
|
||||
position: relative;
|
||||
.fa-layers-counter {
|
||||
transform: none;
|
||||
font-size: .45rem;
|
||||
font-weight: bold;
|
||||
right: 2px;
|
||||
top: 2px;
|
||||
color: var(--color-highlight);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&.REPEAT_ALL, &.REPEAT_ONE {
|
||||
.active {
|
||||
color: var(--color-highlight);
|
||||
}
|
||||
|
||||
&.REPEAT_ONE::after {
|
||||
content: "1";
|
||||
position: absolute;
|
||||
display: flex;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-weight: 700;
|
||||
font-size: .5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<span v-if="isMobile.phone" class="text-orange" data-testid="controls-toggle" @click="$emit('toggleControls')">
|
||||
<i v-if="showingControls" class="fa fa-angle-up toggle"/>
|
||||
<i v-else class="fa fa-angle-down toggle"/>
|
||||
<span v-if="isMobile.phone" class="text-highlight" data-testid="controls-toggle" @click="$emit('toggleControls')">
|
||||
<icon v-if="showingControls" :icon="showingControls ? faAngleUp : faAngleDown" class="toggle"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faAngleDown, faAngleUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
<div class="screen-empty-state" data-testid="screen-empty-state">
|
||||
<div>
|
||||
<span class="jumbo-icon">
|
||||
<slot name="icon">
|
||||
<i class="fa fa-upload"></i>
|
||||
</slot>
|
||||
<slot name="icon">☕️</slot>
|
||||
</span>
|
||||
<div class="text">
|
||||
<slot>Placeholder text goes here.</slot>
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
<template>
|
||||
<span>
|
||||
<button title="Zoom out" type="button" @click.prevent="zoom(-1)">
|
||||
<i class="fa fa-search-minus"/>
|
||||
<icon :icon="faSearchMinus"/>
|
||||
</button>
|
||||
<button title="Zoom in" type="button" @click.prevent="zoom(1)">
|
||||
<i class="fa fa-search-plus"/>
|
||||
<icon :icon="faSearchPlus"/>
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faSearchMinus, faSearchPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
const props = defineProps<{ target: HTMLElement | null }>()
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<i :title="title" class="fa fa-question-circle help-trigger text-blue"/>
|
||||
<icon :icon="faCircleQuestion" :title="title" class="help-trigger text-blue"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCircleQuestion } from '@fortawesome/free-solid-svg-icons'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
const props = defineProps<{ title: string }>()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
title="View as thumbnails"
|
||||
>
|
||||
<input v-model="value" class="hidden" name="view-mode" type="radio" value="thumbnails">
|
||||
<i class="fa fa-th-large"/>
|
||||
<icon :icon="faThumbnailsHehe"/>
|
||||
<span class="hidden">View as thumbnails</span>
|
||||
</label>
|
||||
|
||||
|
@ -18,13 +18,15 @@
|
|||
title="View as list"
|
||||
>
|
||||
<input v-model="value" class="hidden" name="view-mode" type="radio" value="list">
|
||||
<i class="fa fa-list"/>
|
||||
<icon :icon="faList"/>
|
||||
<span class="hidden">View as list</span>
|
||||
</label>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faMicrosoft as faThumbnailsHehe } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ modelValue?: ArtistAlbumViewMode }>(), { modelValue: 'thumbnails' })
|
||||
|
@ -46,9 +48,10 @@ const value = computed({
|
|||
|
||||
label {
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
line-height: 2rem;
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2rem;
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
<template>
|
||||
<span id="volume" class="volume control">
|
||||
<i v-if="muted" class="fa fa-volume-off" role="button" tabindex="0" title="Unmute" @click="unmute"/>
|
||||
<i v-else class="fa fa-volume-up" role="button" tabindex="0" title="Mute" @click="mute"/>
|
||||
<icon
|
||||
v-if="level === 'muted'"
|
||||
:icon="faVolumeMute"
|
||||
fixed-width
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Unmute"
|
||||
@click="unmute"
|
||||
/>
|
||||
<icon
|
||||
v-else
|
||||
:icon="level === 'discreet' ? faVolumeLow : faVolumeHigh"
|
||||
fixed-width
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Mute"
|
||||
@click="mute"
|
||||
/>
|
||||
|
||||
<input
|
||||
id="volumeInput"
|
||||
class="plyr__volume"
|
||||
|
@ -17,33 +34,40 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faVolumeHigh, faVolumeLow, faVolumeMute } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ref } from 'vue'
|
||||
import { playbackService, socketService } from '@/services'
|
||||
import { preferenceStore as preferences } from '@/stores'
|
||||
import { eventBus } from '@/utils'
|
||||
|
||||
const muted = ref(false)
|
||||
const level = ref<'muted' | 'discreet' | 'loud'>()
|
||||
|
||||
const mute = () => {
|
||||
muted.value = true
|
||||
playbackService.mute()
|
||||
level.value = 'muted'
|
||||
}
|
||||
|
||||
const unmute = () => {
|
||||
muted.value = false
|
||||
playbackService.unmute()
|
||||
level.value = preferences.volume < 3 ? 'discreet' : 'loud'
|
||||
}
|
||||
|
||||
const setVolume = (e: InputEvent) => {
|
||||
const volume = parseFloat((e.target as HTMLInputElement).value)
|
||||
playbackService.setVolume(volume)
|
||||
muted.value = volume === 0
|
||||
setLevel(volume)
|
||||
}
|
||||
|
||||
const setLevel = (volume: number) => (level.value = volume === 0 ? 'muted' : volume < 3 ? 'discreet' : 'loud')
|
||||
|
||||
/**
|
||||
* Broadcast the volume changed event to remote controller.
|
||||
*/
|
||||
const broadcastVolume = (e: InputEvent) => {
|
||||
socketService.broadcast('SOCKET_VOLUME_CHANGED', parseFloat((e.target as HTMLInputElement).value))
|
||||
}
|
||||
|
||||
eventBus.on('KOEL_READY', () => setLevel(preferences.volume))
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -53,11 +77,11 @@ const broadcastVolume = (e: InputEvent) => {
|
|||
|
||||
// More tweaks
|
||||
[type=range] {
|
||||
margin: -1px 0 0 5px;
|
||||
margin: 0 0 0 8px;
|
||||
transform: rotate(270deg);
|
||||
transform-origin: 0;
|
||||
position: absolute;
|
||||
bottom: -25px;
|
||||
bottom: -22px;
|
||||
border: 14px solid var(--color-bg-primary);
|
||||
border-left-width: 30px;
|
||||
z-index: 0;
|
||||
|
@ -70,8 +94,7 @@ const broadcastVolume = (e: InputEvent) => {
|
|||
display: block;
|
||||
}
|
||||
|
||||
i {
|
||||
width: 16px;
|
||||
[role=button] {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
<span class="name">{{ file.name }}</span>
|
||||
<span class="controls">
|
||||
<Btn v-if="canRetry" icon-only title="Retry" transparent unrounded @click="retry">
|
||||
<i class="fa fa-repeat"></i>
|
||||
<icon :icon="faRotateBack"/>
|
||||
</Btn>
|
||||
<Btn v-if="canRemove" icon-only title="Remove" transparent unrounded @click="remove">
|
||||
<i class="fa fa-times"></i>
|
||||
<icon :icon="faTimes"/>
|
||||
</Btn>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -17,6 +17,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import slugify from 'slugify'
|
||||
import { faRotateBack, faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, defineAsyncComponent, toRefs } from 'vue'
|
||||
import { UploadFile } from '@/config'
|
||||
import { uploadService } from '@/services'
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
role="button"
|
||||
@click.prevent="logout"
|
||||
>
|
||||
<i class="fa fa-sign-out"></i>
|
||||
<icon :icon="faSignOutAlt"/>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { eventBus } from '@/utils'
|
||||
import { useAuthorization } from '@/composables'
|
||||
|
||||
|
|
|
@ -5,8 +5,13 @@
|
|||
<main>
|
||||
<h1>
|
||||
<span class="name">{{ user.name }}</span>
|
||||
<i v-if="isCurrentUser" class="you text-orange fa fa-check-circle" title="This is you!"/>
|
||||
<i v-if="user.is_admin" class="is-admin text-blue fa fa-shield" title="User has admin privileges"/>
|
||||
<icon v-if="isCurrentUser" :icon="faCircleCheck" class="you text-highlight" title="This is you!"/>
|
||||
<icon
|
||||
v-if="user.is_admin"
|
||||
:icon="faShield"
|
||||
class="is-admin text-blue"
|
||||
title="User has admin privileges"
|
||||
/>
|
||||
</h1>
|
||||
|
||||
<p class="email text-secondary">{{ user.email }}</p>
|
||||
|
@ -24,6 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { faCircleCheck, faShield } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { userStore } from '@/stores'
|
||||
import { alerts, eventBus } from '@/utils'
|
||||
|
|
|
@ -18,18 +18,16 @@
|
|||
<p class="none text-secondary" v-else>No song is playing.</p>
|
||||
<footer>
|
||||
<a class="favorite" @click.prevent="toggleFavorite">
|
||||
<i class="fa fa-heart yep" v-if="song?.liked"></i>
|
||||
<i class="fa fa-heart-o" v-else></i>
|
||||
<icon :icon="song?.liked ? faHeart : faEmptyHeart"/>
|
||||
</a>
|
||||
<a class="prev" @click="playPrev">
|
||||
<i class="fa fa-step-backward"></i>
|
||||
<icon :icon="faStepBackward"/>
|
||||
</a>
|
||||
<a class="play-pause" @click.prevent="togglePlayback">
|
||||
<i class="fa fa-pause" v-if="playing"/>
|
||||
<i class="fa fa-play" v-else/>
|
||||
<icon :icon="playing ? faPause : faPlay"/>
|
||||
</a>
|
||||
<a class="next" @click.prevent="playNext">
|
||||
<i class="fa fa-step-forward"></i>
|
||||
<icon :icon="faStepForward"/>
|
||||
</a>
|
||||
<span class="volume">
|
||||
<span
|
||||
|
@ -39,8 +37,7 @@
|
|||
v-koel-clickaway="closeVolumeSlider"
|
||||
/>
|
||||
<span class="icon" @click.stop="toggleVolumeSlider">
|
||||
<i class="fa fa-volume-off" v-if="muted"/>
|
||||
<i class="fa fa-volume-up" v-else/>
|
||||
<icon :icon="muted ? faVolumeHigh : faVolumeMute" fixed-width/>
|
||||
</span>
|
||||
</span>
|
||||
</footer>
|
||||
|
@ -52,7 +49,7 @@
|
|||
</div>
|
||||
<p v-else>
|
||||
No active Koel instance found.
|
||||
<a class="rescan text-orange" @click.prevent="rescan">Rescan</a>
|
||||
<a class="rescan text-highlight" @click.prevent="rescan">Rescan</a>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -65,6 +62,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
faHeart,
|
||||
faPause,
|
||||
faPlay,
|
||||
faStepBackward,
|
||||
faStepForward,
|
||||
faVolumeHigh,
|
||||
faVolumeMute
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { faHeart as faEmptyHeart } from '@fortawesome/free-regular-svg-icons'
|
||||
import noUISlider from 'nouislider'
|
||||
import { authService, socketService } from '@/services'
|
||||
import { preferenceStore, userStore } from '@/stores'
|
||||
|
|
|
@ -75,7 +75,6 @@ export default [
|
|||
thumbnailUrl: bgRosePetal,
|
||||
properties: {
|
||||
'--color-bg-primary': '#7d083b',
|
||||
'--color-highlight': '#d84179',
|
||||
'--bg-image': `url(${bgRosePetal})`
|
||||
}
|
||||
},
|
||||
|
@ -86,7 +85,6 @@ export default [
|
|||
thumbnailUrl: bgPurpleWaves,
|
||||
properties: {
|
||||
'--color-bg-primary': '#44115c',
|
||||
'--color-highlight': '#b854dd',
|
||||
'--bg-image': `url(${bgPurpleWaves})`
|
||||
}
|
||||
},
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
@import '#/vendor/_reset.scss';
|
||||
|
||||
@import '@modules/nouislider/distribute/nouislider.min.css';
|
||||
@import '@modules/font-awesome/css/font-awesome.min.css';
|
||||
|
||||
@import "#/partials/_vars.scss";
|
||||
@import "#/partials/_hacks.scss";
|
||||
|
|
|
@ -218,7 +218,6 @@
|
|||
margin-top: 24px;
|
||||
font-size: .9rem;
|
||||
text-align: right;
|
||||
clear: both;
|
||||
|
||||
a {
|
||||
color: var(--color-text-primary);
|
||||
|
@ -246,20 +245,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.play {
|
||||
font-size: 1rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: 1px solid var(--color-text-secondary);
|
||||
border-radius: 9999rem;
|
||||
text-indent: .1rem; // the Play triangle icon doesn't look balanced otherwise
|
||||
opacity: .5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin inset-when-pressed() {
|
||||
|
|
|
@ -19,7 +19,7 @@ body, html {
|
|||
}
|
||||
|
||||
input, select, button, textarea, .btn {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-family: var(--font-family);
|
||||
|
@ -67,54 +67,19 @@ select {
|
|||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
border: 1px solid rgba(255, 255, 255, .3);
|
||||
border-radius: 0;
|
||||
clear: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
height: 16px;
|
||||
height: 15px;
|
||||
margin: -4px 4px 0 0;
|
||||
outline: 0;
|
||||
padding: 0 !important;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
min-width: 16px;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
|
||||
width: 15px;
|
||||
background: var(--color-text-primary);
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
color: var(--color-highlight);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
line-height: 1.3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
&:before {
|
||||
content: "\f00c";
|
||||
}
|
||||
}
|
||||
|
||||
&:indeterminate {
|
||||
&:before {
|
||||
content: "\f068";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -130,14 +95,6 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
.clear, .clearfix {
|
||||
&::after {
|
||||
content: " ";
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.ir {
|
||||
color: transparent;
|
||||
font: 0/0 serif;
|
||||
|
@ -289,7 +246,7 @@ label {
|
|||
}
|
||||
|
||||
.text- {
|
||||
&white {
|
||||
&primary {
|
||||
color: var(--color-text-primary) !important;
|
||||
}
|
||||
|
||||
|
@ -297,7 +254,7 @@ label {
|
|||
color: var(--color-text-secondary) !important;
|
||||
}
|
||||
|
||||
&orange {
|
||||
&highlight {
|
||||
color: var(--color-highlight) !important;
|
||||
}
|
||||
|
||||
|
@ -313,6 +270,10 @@ label {
|
|||
color: var(--color-blue) !important;
|
||||
}
|
||||
|
||||
&green {
|
||||
color: var(--color-green) !important;
|
||||
}
|
||||
|
||||
&uppercase {
|
||||
text-transform: uppercase !important;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
@import '#/vendor/_reset.scss';
|
||||
|
||||
@import '@modules/nouislider/distribute/nouislider.min.css';
|
||||
@import '@modules/font-awesome/css/font-awesome.min.css';
|
||||
|
||||
@import "#/partials/_vars.scss";
|
||||
@import "#/partials/_shared.scss";
|
||||
|
|
|
@ -25,7 +25,7 @@ export default defineConfig({
|
|||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "#/partials/_mixins.scss";`
|
||||
additionalData: '@import "#/partials/_mixins.scss";'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
43
yarn.lock
43
yarn.lock
|
@ -973,6 +973,44 @@
|
|||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-6.2.0.tgz#6d836ee8a580589b60c9088a3bdb0b669a468825"
|
||||
integrity sha512-3kIcQ+aTr3I+LqDbJwbINFk5oA+a63LH57GPJt9PM8AWqN7nCwnubuSiWosHYQlyf2NicrvpzXQxllyLeEdpyQ==
|
||||
|
||||
"@fortawesome/fontawesome-common-types@6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz#7dc996042d21fc1ae850e3173b5c67b0549f9105"
|
||||
integrity sha512-wVn5WJPirFTnzN6tR95abCx+ocH+3IFLXAgyavnf9hUmN0CfWoDjPT/BAWsUVwSlYYVBeCLJxaqi7ZGe4uSjBA==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz#3424ec6182515951816be9b11665d67efdce5b5f"
|
||||
integrity sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.1.1"
|
||||
|
||||
"@fortawesome/free-brands-svg-icons@^6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.1.1.tgz#3580961d4f42bd51dc171842402f23a18a5480b1"
|
||||
integrity sha512-mFbI/czjBZ+paUtw5NPr2IXjun5KAC8eFqh1hnxowjA4mMZxWz4GCIksq6j9ZSa6Uxj9JhjjDVEd77p2LN2Blg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.1.1"
|
||||
|
||||
"@fortawesome/free-regular-svg-icons@^6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.1.1.tgz#3f2f58262a839edf0643cbacee7a8a8230061c98"
|
||||
integrity sha512-xXiW7hcpgwmWtndKPOzG+43fPH7ZjxOaoeyooptSztGmJxCAflHZxXNK0GcT0uEsR4jTGQAfGklDZE5NHoBhKg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.1.1"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.1.1.tgz#3369e673f8fe8be2fba30b1ec274d47490a830a6"
|
||||
integrity sha512-0/5exxavOhI/D4Ovm2r3vxNojGZioPwmFrKg0ZUH69Q68uFhFPs6+dhAToh6VEQBntxPRYPuT5Cg1tpNa9JUPg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.1.1"
|
||||
|
||||
"@fortawesome/vue-fontawesome@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.1.tgz#ced35cefc52b364f7db973f2fe9f50c3dd160715"
|
||||
integrity sha512-CdXZJoCS+aEPec26ZP7hWWU3SaJlQPZSCGdgpQ2qGl2HUmtUUNrI3zC4XWdn1JUmh3t5OuDeRG1qB4eGRNSD4A==
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17"
|
||||
|
@ -3484,11 +3522,6 @@ follow-redirects@^1.14.8:
|
|||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
|
||||
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
|
||||
|
||||
font-awesome@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
|
||||
integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
|
|
Loading…
Reference in a new issue