refactor(front): Player
This commit is contained in:
parent
fd83ebb287
commit
614cfeafc0
|
@ -15,6 +15,7 @@ import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
|||
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
|
||||
import PlayerControls from './PlayerControls.vue'
|
||||
import VolumeControl from './VolumeControl.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
const {
|
||||
LoopingMode,
|
||||
|
@ -160,10 +161,11 @@ const hideArtist = () => {
|
|||
class="ui tiny image"
|
||||
@click.stop.prevent="router.push({name: 'library.tracks.detail', params: {id: currentTrack.id }})"
|
||||
>
|
||||
<!-- TODO: Use smaller covers -->
|
||||
<img
|
||||
ref="cover"
|
||||
alt=""
|
||||
:src="store.getters['instance/absoluteUrl'](currentTrack.coverUrl)"
|
||||
v-lazy="store.getters['instance/absoluteUrl'](currentTrack.coverUrl)"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
|
@ -172,7 +174,7 @@ const hideArtist = () => {
|
|||
>
|
||||
<strong>
|
||||
<router-link
|
||||
class="small header discrete link track"
|
||||
class="header discrete link track"
|
||||
:to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}"
|
||||
@click.stop.prevent=""
|
||||
>
|
||||
|
@ -186,7 +188,7 @@ const hideArtist = () => {
|
|||
:key="ac.artist.id"
|
||||
>
|
||||
<router-link
|
||||
class="discrete link"
|
||||
class="small discrete link"
|
||||
:to="{name: 'library.artists.detail', params: {id: ac.artist.id }}"
|
||||
@click.stop.prevent=""
|
||||
>
|
||||
|
@ -198,7 +200,7 @@ const hideArtist = () => {
|
|||
<template v-if="currentTrack.albumId !== -1">
|
||||
<span class="middle slash symbol" />
|
||||
<router-link
|
||||
class="discrete link"
|
||||
class="small discrete link"
|
||||
:to="{name: 'library.albums.detail', params: {id: currentTrack.albumId }}"
|
||||
@click.stop.prevent=""
|
||||
>
|
||||
|
@ -210,10 +212,11 @@ const hideArtist = () => {
|
|||
</div>
|
||||
<div class="controls track-controls queue-not-focused desktop-and-below">
|
||||
<div class="ui tiny image">
|
||||
<!-- TODO: Use smaller covers -->
|
||||
<img
|
||||
ref="cover"
|
||||
alt=""
|
||||
:src="store.getters['instance/absoluteUrl'](currentTrack.coverUrl)"
|
||||
v-lazy="store.getters['instance/absoluteUrl'](currentTrack.coverUrl)"
|
||||
>
|
||||
</div>
|
||||
<div class="middle aligned content ellipsis">
|
||||
|
@ -240,21 +243,22 @@ const hideArtist = () => {
|
|||
class="controls desktop-and-up fluid align-right"
|
||||
>
|
||||
<track-favorite-icon
|
||||
class="control white"
|
||||
ghost
|
||||
:track="currentTrack"
|
||||
/>
|
||||
<track-playlist-icon
|
||||
class="control white"
|
||||
ghost
|
||||
:track="currentTrack"
|
||||
/>
|
||||
<button
|
||||
:class="['ui', 'really', 'basic', 'circular', 'icon', 'button', 'control']"
|
||||
<!-- <Button
|
||||
round
|
||||
ghost
|
||||
icon="bi-eye-slash"
|
||||
:aria-label="labels.addArtistContentFilter"
|
||||
:title="labels.addArtistContentFilter"
|
||||
@click="hideArtist"
|
||||
>
|
||||
<i :class="['eye slash outline', 'basic', 'icon']" />
|
||||
</button>
|
||||
</Button> -->
|
||||
</div>
|
||||
<player-controls class="controls queue-not-focused" />
|
||||
<div class="controls progress-controls queue-not-focused tablet-and-up small align-left">
|
||||
|
@ -274,49 +278,37 @@ const hideArtist = () => {
|
|||
<div class="controls queue-controls when-queue-focused align-right">
|
||||
<div class="group">
|
||||
<volume-control class="expandable" />
|
||||
<button
|
||||
class="circular control button"
|
||||
<Button
|
||||
:class="{ looping: looping !== LoopingMode.None }"
|
||||
:title="loopingTitle"
|
||||
ghost
|
||||
round
|
||||
:aria-label="loopingTitle"
|
||||
:disabled="!currentTrack"
|
||||
:icon="looping === LoopingMode.LoopTrack ? 'bi-repeat-1' : looping === LoopingMode.LoopQueue ? 'bi-repeat' : 'bi-arrow-clockwise'"
|
||||
@click.prevent.stop="toggleLooping"
|
||||
>
|
||||
<i class="repeat icon">
|
||||
<span
|
||||
v-if="looping !== LoopingMode.None"
|
||||
class="ui circular tiny vibrant label"
|
||||
>
|
||||
<span
|
||||
v-if="looping === LoopingMode.LoopTrack"
|
||||
class="symbol single"
|
||||
/>
|
||||
<span
|
||||
v-else-if="looping === LoopingMode.LoopQueue"
|
||||
class="infinity symbol"
|
||||
/>
|
||||
</span>
|
||||
</i>
|
||||
</button>
|
||||
/>
|
||||
|
||||
<button
|
||||
class="circular control button"
|
||||
<Button
|
||||
round
|
||||
ghost
|
||||
:class="{ shuffling: isShuffled }"
|
||||
:disabled="queue.length === 0"
|
||||
:title="labels.shuffle"
|
||||
:aria-label="labels.shuffle"
|
||||
icon="bi-shuffle"
|
||||
@click.prevent.stop="shuffle()"
|
||||
>
|
||||
<i :class="['ui', 'random', { disabled: queue.length === 0, shuffling: isShuffled }, 'icon']" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="fake-dropdown">
|
||||
<button
|
||||
<Button
|
||||
class="position circular control button desktop-and-up"
|
||||
aria-expanded="true"
|
||||
ghost
|
||||
icon="bi-music-note-list"
|
||||
@click.stop="toggleMobilePlayer"
|
||||
>
|
||||
<i class="stream icon" />
|
||||
<i18n-t keypath="components.audio.Player.meta.position">
|
||||
<template #index>
|
||||
{{ currentIndex + 1 }}
|
||||
|
@ -325,12 +317,12 @@ const hideArtist = () => {
|
|||
{{ queue.length }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
class="position circular control button desktop-and-below"
|
||||
@click.stop="switchTab"
|
||||
icon="bi-music-note-list"
|
||||
>
|
||||
<i class="stream icon" />
|
||||
<i18n-t keypath="components.audio.Player.meta.position">
|
||||
<template #index>
|
||||
{{ currentIndex + 1 }}
|
||||
|
@ -339,46 +331,54 @@ const hideArtist = () => {
|
|||
{{ queue.length }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<button
|
||||
<Button
|
||||
v-if="store.state.ui.queueFocused"
|
||||
class="circular control button close-control desktop-and-up"
|
||||
ghost
|
||||
class="close-control desktop-and-up"
|
||||
icon="bi-caret-down-fill"
|
||||
@click.stop="toggleMobilePlayer"
|
||||
>
|
||||
<i class="large down angle icon" />
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
class="circular control button desktop-and-up"
|
||||
ghost
|
||||
class="desktop-and-up"
|
||||
icon="bi-caret-up-fill"
|
||||
@click.stop="toggleMobilePlayer"
|
||||
>
|
||||
<i class="large up angle icon" />
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
v-if="store.state.ui.queueFocused === 'player'"
|
||||
class="circular control button close-control desktop-and-below"
|
||||
ghost
|
||||
class="close-control desktop-and-below"
|
||||
icon="bi-caret-up-fill"
|
||||
@click.stop="switchTab"
|
||||
>
|
||||
<i class="large up angle icon" />
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
v-if="store.state.ui.queueFocused === 'queue'"
|
||||
class="circular control button desktop-and-below"
|
||||
ghost
|
||||
class="desktop-and-below"
|
||||
icon="bi-caret-down-fill"
|
||||
@click.stop="switchTab"
|
||||
>
|
||||
<i class="large down angle icon" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<button
|
||||
class="circular control button close-control desktop-and-below"
|
||||
<Button
|
||||
class="close-control desktop-and-below"
|
||||
icon="bi-x"
|
||||
@click.stop="store.commit('ui/queueFocused', null)"
|
||||
>
|
||||
<i class="x icon" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -5,6 +5,8 @@ import { computed } from 'vue'
|
|||
import { usePlayer } from '~/composables/audio/player'
|
||||
import { useQueue } from '~/composables/audio/queue'
|
||||
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
const { playPrevious, hasNext, playNext, currentTrack } = useQueue()
|
||||
const { isPlaying } = usePlayer()
|
||||
|
||||
|
@ -19,40 +21,36 @@ const labels = computed(() => ({
|
|||
|
||||
<template>
|
||||
<div class="player-controls">
|
||||
<button
|
||||
<Button
|
||||
:title="labels.previous"
|
||||
:aria-label="labels.previous"
|
||||
class="circular button control tablet-and-up"
|
||||
round
|
||||
ghost
|
||||
alignSelf="center"
|
||||
class="control tablet-and-up"
|
||||
icon="bi-skip-backward-fill"
|
||||
@click.prevent.stop="playPrevious()"
|
||||
>
|
||||
<i :class="['ui', 'large', 'backward step', 'icon']" />
|
||||
</button>
|
||||
<button
|
||||
v-if="!isPlaying"
|
||||
:title="labels.play"
|
||||
:aria-label="labels.play"
|
||||
class="circular button control"
|
||||
@click.prevent.stop="isPlaying = true"
|
||||
>
|
||||
<i :class="['ui', 'big', 'play', {'disabled': !currentTrack}, 'icon']" />
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:title="labels.pause"
|
||||
:aria-label="labels.pause"
|
||||
class="circular button control"
|
||||
@click.prevent.stop="isPlaying = false"
|
||||
>
|
||||
<i :class="['ui', 'big', 'pause', {'disabled': !currentTrack}, 'icon']" />
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<Button
|
||||
:title="isPlaying ? labels.pause : labels.play"
|
||||
round
|
||||
ghost
|
||||
alignSelf="center"
|
||||
:aria-label="isPlaying ? labels.pause : labels.play"
|
||||
:class="['control', isPlaying ? 'pause' : 'play', 'large']"
|
||||
:icon="isPlaying ? 'bi-pause-fill' : 'bi-play-fill'"
|
||||
@click.prevent.stop="isPlaying = !isPlaying"
|
||||
/>
|
||||
<Button
|
||||
:title="labels.next"
|
||||
:aria-label="labels.next"
|
||||
round
|
||||
ghost
|
||||
alignSelf="center"
|
||||
:disabled="!hasNext"
|
||||
class="circular button control"
|
||||
class="control"
|
||||
icon="bi-skip-forward-fill"
|
||||
@click.prevent.stop="playNext()"
|
||||
>
|
||||
<i :class="['ui', 'large', {'disabled': !hasNext}, 'forward step', 'icon']" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -4,6 +4,8 @@ import { useTimeoutFn } from '@vueuse/core'
|
|||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
const { volume, mute } = usePlayer()
|
||||
const expanded = ref(false)
|
||||
|
||||
|
@ -32,8 +34,9 @@ const scroll = (event: WheelEvent) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="circular control button"
|
||||
<Button
|
||||
round
|
||||
ghost
|
||||
:class="['component-volume-control', {'expanded': expanded}]"
|
||||
@click.prevent.stop=""
|
||||
@mouseover="handleOver"
|
||||
|
@ -47,7 +50,7 @@ const scroll = (event: WheelEvent) => {
|
|||
:aria-label="labels.unmute"
|
||||
@click.prevent.stop="mute"
|
||||
>
|
||||
<i class="volume off icon" />
|
||||
<i class="bi bi-volume-mute-fill" />
|
||||
</span>
|
||||
<span
|
||||
v-else-if="volume < 0.5"
|
||||
|
@ -56,7 +59,7 @@ const scroll = (event: WheelEvent) => {
|
|||
:aria-label="labels.mute"
|
||||
@click.prevent.stop="mute"
|
||||
>
|
||||
<i class="volume down icon" />
|
||||
<i class="bi bi-volume-down-fill" />
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
|
@ -65,7 +68,7 @@ const scroll = (event: WheelEvent) => {
|
|||
:aria-label="labels.mute"
|
||||
@click.prevent.stop="mute"
|
||||
>
|
||||
<i class="volume up icon" />
|
||||
<i class="bi bi-volume-up-fill" />
|
||||
</span>
|
||||
<div class="popup">
|
||||
<label
|
||||
|
@ -81,5 +84,5 @@ const scroll = (event: WheelEvent) => {
|
|||
max="1"
|
||||
>
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
|
@ -102,9 +102,6 @@ $dropdown-item-selected-background: var(--dropdown-item-hover-background) !defau
|
|||
$segment-color: var(--text-color) !default;
|
||||
$segment-background: var(--site-background) !default;
|
||||
|
||||
$player-color: rgba(255, 255, 255, 0.9) !default;
|
||||
$player-background: #1B1C1D !default;
|
||||
|
||||
$table-background: transparent !default;
|
||||
$table-border: var(--divider) !default;
|
||||
|
||||
|
|
|
@ -107,6 +107,9 @@
|
|||
--disabled-background-color:var(--fw-beige-100);
|
||||
--disabled-border-color:var(--fw-beige-100);
|
||||
|
||||
--player-color: var(--fw-gray-700);
|
||||
--player-background: var(--fw-blue-010);
|
||||
|
||||
&.raised {
|
||||
--background-color:var(--fw-beige-300);
|
||||
--border-color:color-mix(in oklab, var(--fw-beige-400) 90%, currentcolor);
|
||||
|
@ -295,6 +298,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Dark theme
|
||||
|
||||
:is(body.theme-dark, html.dark>body:not(.theme-light)), .force-dark-theme.force-dark-theme.force-dark-theme {
|
||||
|
@ -323,6 +329,9 @@
|
|||
--disabled-background-color:var(--fw-gray-950);
|
||||
--disabled-border-color:var(--fw-gray-950);
|
||||
|
||||
--player-color: var(--fw-gray-300);
|
||||
--player-background: var(--fw-gray-950);
|
||||
|
||||
&.raised{
|
||||
--background-color:var(--fw-gray-950);
|
||||
--border-color:var(--fw-gray-600);
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
|
||||
.ui.top.attached.progress {
|
||||
top: 0;
|
||||
height: 1rem;
|
||||
height: 3px;
|
||||
z-index: 1;
|
||||
padding-bottom: 0.8rem;
|
||||
border-radius: 0;
|
||||
|
||||
.bar {
|
||||
|
@ -18,7 +17,7 @@
|
|||
|
||||
}
|
||||
.ui.bottom-player > .segment.fixed-controls {
|
||||
color: var(--player-color);
|
||||
color: var(--color);
|
||||
background: var(--player-background);
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
|
@ -106,18 +105,44 @@
|
|||
justify-content: flex-start;
|
||||
flex-grow: 1;
|
||||
.image {
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
width: auto;
|
||||
margin-right: 0.5em;
|
||||
margin-right: 1em;
|
||||
> img {
|
||||
max-height: 3.7em;
|
||||
max-width: 4.7em;
|
||||
max-height: 40px;
|
||||
max-width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.controls {
|
||||
min-width: 8em;
|
||||
font-size: 1.1em;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
> i {
|
||||
color: var(--color);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#volume-slider {
|
||||
accent-color: var(--vibrant-color);
|
||||
}
|
||||
|
||||
@include media(">desktop") {
|
||||
&:not(.fluid) {
|
||||
width: 20%;
|
||||
|
@ -129,7 +154,11 @@
|
|||
width: 10%;
|
||||
}
|
||||
&.player-controls {
|
||||
width: 15%;
|
||||
gap: 8px;
|
||||
|
||||
& i {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.small, .small {
|
||||
|
@ -137,11 +166,12 @@
|
|||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
font-size: 1.1em;
|
||||
i {
|
||||
font-size: 1.3em;
|
||||
color: var(--player-color);
|
||||
}
|
||||
.icon.large {
|
||||
font-size: 1.4em;
|
||||
.large i {
|
||||
font-size: 3.2em;
|
||||
}
|
||||
&:not(.track-controls) {
|
||||
@include media(">desktop") {
|
||||
|
@ -159,11 +189,6 @@
|
|||
padding: 0.5em;
|
||||
}
|
||||
}
|
||||
&.player-controls {
|
||||
.icon {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -171,74 +196,12 @@
|
|||
.component-player {
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.controls .icon.big {
|
||||
cursor: pointer;
|
||||
font-size: 2em !important;
|
||||
}
|
||||
|
||||
.controls .icon {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.timer {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.looping {
|
||||
i {
|
||||
position: relative;
|
||||
}
|
||||
.ui.circular.label {
|
||||
font-family: sans-serif;
|
||||
position: absolute;
|
||||
font-size: 0.5em !important;
|
||||
bottom: -0.7rem;
|
||||
right: -0.7rem;
|
||||
padding: 2px 0 !important;
|
||||
width: 15px !important;
|
||||
height: 15px !important;
|
||||
min-width: 15px !important;
|
||||
min-height: 15px !important;
|
||||
@include media(">desktop") {
|
||||
font-size: 0.6em !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.shuffling {
|
||||
color: var(--vibrant-color);
|
||||
}
|
||||
.control.circular.button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
|
||||
}
|
||||
.fake-dropdown {
|
||||
border: 1px solid gray;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 8em;
|
||||
z-index: 2;
|
||||
> .control.button {
|
||||
padding: 0.5em;
|
||||
|
||||
}
|
||||
.position.control {
|
||||
flex-grow: 1;
|
||||
|
||||
i.stream.icon {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
}
|
||||
}
|
||||
.angle.icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue