mirror of
https://github.com/swingmx/webclient.git
synced 2025-12-25 11:50:21 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf2d9537ff | ||
|
|
6f4a59f971 | ||
|
|
7b21853f97 | ||
|
|
663dbd2a7c | ||
|
|
c7a0b5ab7e | ||
|
|
ad8eeb7a2a | ||
|
|
e799c96872 | ||
|
|
234aed54d7 | ||
|
|
574d7fd5e7 | ||
|
|
4a1106d784 | ||
|
|
d9f7e5fb14 | ||
|
|
571c4a5264 | ||
|
|
e71bc7164c | ||
|
|
77f18ac640 | ||
|
|
78d57a64b9 | ||
|
|
ff502521e8 | ||
|
|
7caa70b9d6 | ||
|
|
cc3b372090 | ||
|
|
c297f75132 | ||
|
|
7c954ef805 |
2
TODO.md
2
TODO.md
@@ -4,7 +4,6 @@
|
|||||||
- Check out the mobile sidebar and navbar
|
- Check out the mobile sidebar and navbar
|
||||||
- Remove old settings page files
|
- Remove old settings page files
|
||||||
- Fix: track loading indicator in bottom bar
|
- Fix: track loading indicator in bottom bar
|
||||||
|
|
||||||
- Unfuck javascript controlled responsiveness
|
- Unfuck javascript controlled responsiveness
|
||||||
|
|
||||||
- Redesign the album page header for mobile
|
- Redesign the album page header for mobile
|
||||||
@@ -14,7 +13,6 @@
|
|||||||
- Add trailing slash to folder url accessed from the breadcrumb
|
- Add trailing slash to folder url accessed from the breadcrumb
|
||||||
- Clip the browseable items on the homepage
|
- Clip the browseable items on the homepage
|
||||||
- Fix: The responsiveness glitch between 900px - 964px 😅
|
- Fix: The responsiveness glitch between 900px - 964px 😅
|
||||||
- Fix: Queue repeat
|
|
||||||
- Make All Albums/Artists view sort banner sticky
|
- Make All Albums/Artists view sort banner sticky
|
||||||
|
|
||||||
# DONE ✅
|
# DONE ✅
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ $g-border: solid 1px $gray5;
|
|||||||
.b-bar {
|
.b-bar {
|
||||||
grid-area: bottombar;
|
grid-area: bottombar;
|
||||||
border-top: $g-border;
|
border-top: $g-border;
|
||||||
|
// background-color: $bars;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-page {
|
.content-page {
|
||||||
@@ -127,7 +128,8 @@ $g-border: solid 1px $gray5;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topnav {
|
.topnav {
|
||||||
background-color: $gray;
|
// background-color: $bars;
|
||||||
|
border-bottom: $g-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vue-recycle-scroller,
|
.vue-recycle-scroller,
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ $content-padding-bottom: 2rem;
|
|||||||
$black: #181a1c;
|
$black: #181a1c;
|
||||||
$white: #ffffffde;
|
$white: #ffffffde;
|
||||||
|
|
||||||
$gray: #1c1c1e;
|
$gray: #1a1919;
|
||||||
$gray1: #8e8e93;
|
$gray1: #8e8e93;
|
||||||
$gray2: #636366;
|
$gray2: #636366;
|
||||||
$gray3: #48484a;
|
$gray3: #48484a;
|
||||||
$gray4: #3a3a3c;
|
$gray4: #3a3a3c;
|
||||||
$gray5: #2c2c2e;
|
$gray5: #2c2c2e;
|
||||||
$body: #111111;
|
$body: #000;
|
||||||
|
|
||||||
$red: #f7635c;
|
$red: #f7635c;
|
||||||
$blue: #0a84ff;
|
$blue: #0a84ff;
|
||||||
@@ -41,6 +41,7 @@ $brown: #ac8e68;
|
|||||||
$indigo: #5e5ce6;
|
$indigo: #5e5ce6;
|
||||||
$teal: rgb(64, 200, 224);
|
$teal: rgb(64, 200, 224);
|
||||||
$lightbrown: #ebca89;
|
$lightbrown: #ebca89;
|
||||||
|
$bars: #111111;
|
||||||
|
|
||||||
$primary: $gray4;
|
$primary: $gray4;
|
||||||
$accent: $gray1;
|
$accent: $gray1;
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ function handleFav() {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.b-bar {
|
.b-bar {
|
||||||
background-color: rgb(22, 22, 22);
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr max-content 1fr;
|
grid-template-columns: 1fr max-content 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
<Volume />
|
<Volume />
|
||||||
<button
|
<button
|
||||||
class="repeat"
|
class="repeat"
|
||||||
:class="{ 'repeat-disabled': settings.no_repeat }"
|
:class="{ 'repeat-disabled': settings.repeat == 'none' }"
|
||||||
:title="settings.repeat_all ? 'Repeat all' : settings.no_repeat ? 'No repeat' : 'Repeat one'"
|
:title="settings.repeat == 'all' ? 'Repeat all' : settings.repeat == 'one' ? 'Repeat one' : 'No repeat'"
|
||||||
@click="settings.toggleRepeatMode"
|
@click="settings.toggleRepeatMode"
|
||||||
>
|
>
|
||||||
<RepeatOneSvg v-if="settings.repeat_one" />
|
<RepeatOneSvg v-if="settings.repeat == 'one'" />
|
||||||
<RepeatAllSvg v-else />
|
<RepeatAllSvg v-else />
|
||||||
</button>
|
</button>
|
||||||
<button title="Shuffle" @click="queue.shuffleQueue">
|
<button title="Shuffle" @click="queue.shuffleQueue">
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const res_type = computed(() => {
|
|||||||
type It = Album & Artist & Track
|
type It = Album & Artist & Track
|
||||||
|
|
||||||
const item = computed(() => {
|
const item = computed(() => {
|
||||||
return top_results.value.top_result.item as It
|
return top_results.value.top_result as It
|
||||||
})
|
})
|
||||||
|
|
||||||
const context_menu_showing = ref(false)
|
const context_menu_showing = ref(false)
|
||||||
@@ -106,7 +106,7 @@ function showMenu(e: MouseEvent) {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.top-result-item {
|
.top-result-item {
|
||||||
background-color: $gray5;
|
background-color: $gray;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
<div class="item__favorites">
|
<div class="item__favorites">
|
||||||
{{ backup.favorites }} favorite{{ backup.favorites !== 1 ? 's' : '' }}
|
{{ backup.favorites }} favorite{{ backup.favorites !== 1 ? 's' : '' }}
|
||||||
</div>
|
</div>
|
||||||
|
•
|
||||||
|
<div class="item__collections">
|
||||||
|
{{ backup.collections }} collection{{ backup.collections !== 1 ? 's' : '' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
@@ -55,6 +59,7 @@ interface Backup {
|
|||||||
playlists: number
|
playlists: number
|
||||||
scrobbles: number
|
scrobbles: number
|
||||||
favorites: number
|
favorites: number
|
||||||
|
collections: number
|
||||||
date: string
|
date: string
|
||||||
}
|
}
|
||||||
const backups = ref<Backup[]>([])
|
const backups = ref<Backup[]>([])
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="statitem" :class="props.icon">
|
<div class="statitem" :class="props.icon" :style="dynamicBackgroundStyle">
|
||||||
<svg
|
<svg
|
||||||
class="noise"
|
class="noise"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
surfaceScale="21"
|
surfaceScale="21"
|
||||||
specularConstant="1.7"
|
specularConstant="1.7"
|
||||||
specularExponent="20"
|
specularExponent="20"
|
||||||
lighting-color="#7957A8"
|
lighting-color="transparent"
|
||||||
x="0%"
|
x="0%"
|
||||||
y="0%"
|
y="0%"
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -50,20 +50,20 @@
|
|||||||
<rect width="700" height="700" fill="transparent"></rect>
|
<rect width="700" height="700" fill="transparent"></rect>
|
||||||
<rect width="700" height="700" fill="#7957a8" filter="url(#nnnoise-filter)"></rect>
|
<rect width="700" height="700" fill="#7957a8" filter="url(#nnnoise-filter)"></rect>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="itemcontent">
|
<div class="itemcontent" :style="{ color: textColor }">
|
||||||
<div class="count ellip2" :title="formattedValue">{{ formattedValue }}</div>
|
<div class="count ellip2" :title="formattedValue">{{ formattedValue }}</div>
|
||||||
<div class="title">{{ text }}</div>
|
<div class="title">{{ text }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<component :is="icon" class="staticon" v-if="!props.icon.startsWith('top')" />
|
<component :is="icon" v-if="!props.icon.startsWith('top')" class="staticon" :style="{ color: textColor }" />
|
||||||
<router-link
|
<router-link
|
||||||
|
v-if="props.icon.startsWith('top') && props.image"
|
||||||
:to="{
|
:to="{
|
||||||
name: Routes.album,
|
name: Routes.album,
|
||||||
params: {
|
params: {
|
||||||
albumhash: props.image?.replace('.webp', ''),
|
albumhash: props.image?.replace('.webp', ''),
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
v-if="props.icon.startsWith('top') && props.image"
|
|
||||||
>
|
>
|
||||||
<img class="staticon statimage shadow-sm" :src="paths.images.thumb.small + props.image" alt="" />
|
<img class="staticon statimage shadow-sm" :src="paths.images.thumb.small + props.image" alt="" />
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -81,6 +81,11 @@ import SparklesSvg from '@/assets/icons/sparkles.svg'
|
|||||||
|
|
||||||
import { paths } from '@/config'
|
import { paths } from '@/config'
|
||||||
import { Routes } from '@/router'
|
import { Routes } from '@/router'
|
||||||
|
import useArtistStore from '@/stores/pages/artist'
|
||||||
|
import useAlbumStore from '@/stores/pages/album'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { getTextColor } from '@/utils/colortools/shift'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
value: string
|
value: string
|
||||||
@@ -89,6 +94,13 @@ const props = defineProps<{
|
|||||||
image?: string
|
image?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
// Get current route and colors from stores
|
||||||
|
const route = useRoute()
|
||||||
|
const artistStore = useArtistStore()
|
||||||
|
const albumStore = useAlbumStore()
|
||||||
|
const { colors: artistColors } = storeToRefs(artistStore)
|
||||||
|
const { colors: albumColors } = storeToRefs(albumStore)
|
||||||
|
|
||||||
const icon = computed(() => {
|
const icon = computed(() => {
|
||||||
switch (props.icon) {
|
switch (props.icon) {
|
||||||
case 'streams':
|
case 'streams':
|
||||||
@@ -110,6 +122,61 @@ const icon = computed(() => {
|
|||||||
const formattedValue = computed(() => {
|
const formattedValue = computed(() => {
|
||||||
return props.value.toLocaleString()
|
return props.value.toLocaleString()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Determine which dynamic color to use based on current route
|
||||||
|
const dynamicColor = computed(() => {
|
||||||
|
switch (route.name) {
|
||||||
|
// Album-related pages should use album colors
|
||||||
|
case Routes.album:
|
||||||
|
return albumColors.value?.bg || null
|
||||||
|
|
||||||
|
// Artist-related pages should use artist colors
|
||||||
|
case Routes.artist:
|
||||||
|
return artistColors.value?.bg || null
|
||||||
|
|
||||||
|
// All other pages should use default colors
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Default hardcoded background styles
|
||||||
|
const defaultBackgroundStyles = computed(() => {
|
||||||
|
switch (props.icon) {
|
||||||
|
case 'streams':
|
||||||
|
return 'linear-gradient(to top, #c79081 0%, #dfa579 100%)'
|
||||||
|
case 'playtime':
|
||||||
|
return 'linear-gradient(-225deg, #3d4e81 0%, #5753c9 48%, #6e7ff3 100%)'
|
||||||
|
case 'trackcount':
|
||||||
|
return 'linear-gradient(to top, #6a66b9 0%, #7777db 52%, #7b7bd4 100%)'
|
||||||
|
case 'toptrack':
|
||||||
|
return 'linear-gradient(-225deg, #65379b 0%, #6750b3 53%, #6457c6 100%)'
|
||||||
|
default:
|
||||||
|
return 'linear-gradient(to top right, rgb(120, 76, 129), #9643da91, rgb(132, 80, 228))'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Computed style that uses dynamic color or falls back to hardcoded
|
||||||
|
const dynamicBackgroundStyle = computed(() => {
|
||||||
|
if (dynamicColor.value) {
|
||||||
|
return {
|
||||||
|
backgroundColor: dynamicColor.value,
|
||||||
|
backgroundImage: 'none',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
backgroundImage: defaultBackgroundStyles.value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Computed text color based on background using the same logic as headers
|
||||||
|
const textColor = computed(() => {
|
||||||
|
if (dynamicColor.value) {
|
||||||
|
return getTextColor(dynamicColor.value)
|
||||||
|
}
|
||||||
|
// Return default white color when using gradients
|
||||||
|
return '#ffffff'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -121,25 +188,10 @@ const formattedValue = computed(() => {
|
|||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
// Default background - will be overridden by dynamic styles
|
||||||
background-image: linear-gradient(to top right, rgb(120, 76, 129), #9643da91, rgb(132, 80, 228));
|
background-image: linear-gradient(to top right, rgb(120, 76, 129), #9643da91, rgb(132, 80, 228));
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.streams {
|
|
||||||
background-image: linear-gradient(to top, #c79081 0%, #dfa579 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.playtime {
|
|
||||||
background-image: linear-gradient(-225deg, #3d4e81 0%, #5753c9 48%, #6e7ff3 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.trackcount {
|
|
||||||
background-image: linear-gradient(to top, #6a66b9 0%, #7777db 52%, #7b7bd4 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toptrack {
|
|
||||||
background-image: linear-gradient(-225deg, #65379b 0%, #6750b3 53%, #6457c6 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemcontent {
|
.itemcontent {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<form action="" v-if="delete">
|
<form action="" v-if="delete">
|
||||||
<div>Are you sure you want to delete this page?</div>
|
<br>
|
||||||
|
<div>Are you sure you want to delete this collection?</div>
|
||||||
<br />
|
<br />
|
||||||
<button @click.prevent="submit" class="critical">Yes, Delete</button>
|
<button @click.prevent="submit" class="critical">Yes, Delete</button>
|
||||||
</form>
|
</form>
|
||||||
<form class="playlist-modal" @submit.prevent="submit" v-else>
|
<form class="playlist-modal" @submit.prevent="submit" v-else>
|
||||||
<label for="name">Page name</label>
|
<label for="name">Collection name</label>
|
||||||
<br />
|
<br />
|
||||||
<input type="search" class="rounded-sm" id="name" :value="page?.name" />
|
<input type="search" class="rounded-sm" id="name" :value="collection?.name" />
|
||||||
<br />
|
<br />
|
||||||
<label for="description">Description</label>
|
<label for="description">Description</label>
|
||||||
<br />
|
<br />
|
||||||
<input type="search" class="rounded-sm" id="description" :value="page?.extra.description" />
|
<input type="search" class="rounded-sm" id="description" :value="collection?.extra.description" />
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="submit">{{ page ? 'Update' : 'Create' }}</button>
|
<button type="submit">{{ collection ? 'Update' : 'Create' }}</button>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Page } from '@/interfaces'
|
import { Collection } from '@/interfaces'
|
||||||
import { createNewPage, deletePage, updatePage } from '@/requests/pages'
|
import { createNewCollection, deleteCollection, updateCollection } from '@/requests/collections'
|
||||||
import { router } from '@/router';
|
import { router } from '@/router'
|
||||||
import { NotifType, Notification } from '@/stores/notification'
|
import { NotifType, Notification } from '@/stores/notification'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
page?: Page
|
collection?: Collection
|
||||||
hash?: string
|
hash?: string
|
||||||
type?: string
|
type?: string
|
||||||
extra?: any
|
extra?: any
|
||||||
@@ -36,13 +37,13 @@ const emit = defineEmits<{
|
|||||||
(e: 'setTitle', title: string): void
|
(e: 'setTitle', title: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
emit('setTitle', props.page ? (props.delete ? 'Delete Page' : 'Update Page') : 'New Page')
|
emit('setTitle', (props.collection ? (props.delete ? 'Delete' : 'Update') : 'New') + ' Collection')
|
||||||
|
|
||||||
async function submit(e: Event) {
|
async function submit(e: Event) {
|
||||||
if (props.delete && props.page) {
|
if (props.delete && props.collection) {
|
||||||
const deleted = await deletePage(props.page.id)
|
const deleted = await deleteCollection(props.collection.id)
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
new Notification('Page deleted', NotifType.Success)
|
new Notification('Collection deleted', NotifType.Success)
|
||||||
emit('hideModal')
|
emit('hideModal')
|
||||||
router.push('/')
|
router.push('/')
|
||||||
}
|
}
|
||||||
@@ -54,8 +55,8 @@ async function submit(e: Event) {
|
|||||||
const description = (e.target as any).elements['description'].value
|
const description = (e.target as any).elements['description'].value
|
||||||
|
|
||||||
// If the page is null, we are creating a new page
|
// If the page is null, we are creating a new page
|
||||||
if (props.page == null) {
|
if (props.collection == null) {
|
||||||
const created = await createNewPage(name, description, [
|
const created = await createNewCollection(name, description, [
|
||||||
{
|
{
|
||||||
hash: props.hash as string,
|
hash: props.hash as string,
|
||||||
type: props.type as string,
|
type: props.type as string,
|
||||||
@@ -64,16 +65,16 @@ async function submit(e: Event) {
|
|||||||
])
|
])
|
||||||
|
|
||||||
if (created) {
|
if (created) {
|
||||||
new Notification('New page created', NotifType.Success)
|
new Notification('New collection created', NotifType.Success)
|
||||||
emit('hideModal')
|
emit('hideModal')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const updatedPage = await updatePage(props.page, name, description)
|
const updatedPage = await updateCollection(props.collection, name, description)
|
||||||
|
|
||||||
if (updatedPage) {
|
if (updatedPage) {
|
||||||
props.page.name = updatedPage.name
|
props.collection.name = updatedPage.name
|
||||||
props.page.extra.description = updatedPage.extra.description
|
props.collection.extra.description = updatedPage.extra.description
|
||||||
new Notification('Page updated', NotifType.Success)
|
new Notification('Collection updated', NotifType.Success)
|
||||||
emit('hideModal')
|
emit('hideModal')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,19 @@
|
|||||||
{{ title }}
|
{{ title }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</b>
|
</b>
|
||||||
<SeeAll v-if="route && itemlist.length >= maxAbumCards" :route="route" :text="seeAllText" />
|
<!-- INFO: This SEE ALL is shown when there's no description. Eg. in favorites page -->
|
||||||
|
<SeeAll
|
||||||
|
v-if="!description && route && itemlist.length >= maxAbumCards"
|
||||||
|
:route="route"
|
||||||
|
:text="seeAllText"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="description" class="rdesc">
|
<div v-if="description" class="rdesc">
|
||||||
<RouterLink :to="route || ''">
|
<RouterLink :to="route || ''">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
<!-- INFO: This SEE ALL is shown when there's a description. Eg. in the home page -->
|
||||||
|
<SeeAll v-if="route && itemlist.length >= maxAbumCards" :route="route" :text="seeAllText" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="recentitems">
|
<div class="recentitems">
|
||||||
@@ -90,7 +97,7 @@ function getComponent(type: string) {
|
|||||||
return FolderCard
|
return FolderCard
|
||||||
case 'playlist':
|
case 'playlist':
|
||||||
return PlaylistCard
|
return PlaylistCard
|
||||||
case 'favorite_tracks':
|
case 'favorite':
|
||||||
return FavoritesCard
|
return FavoritesCard
|
||||||
case 'mix':
|
case 'mix':
|
||||||
return MixCard
|
return MixCard
|
||||||
@@ -127,7 +134,7 @@ function getProps(item: { type: string; item?: any; with_helptext?: boolean }) {
|
|||||||
return {
|
return {
|
||||||
playlist: item.item,
|
playlist: item.item,
|
||||||
}
|
}
|
||||||
case 'favorite_tracks':
|
case 'favorite':
|
||||||
return {
|
return {
|
||||||
item: item.item,
|
item: item.item,
|
||||||
}
|
}
|
||||||
@@ -171,6 +178,9 @@ function getProps(item: { type: string; item?: any; with_helptext?: boolean }) {
|
|||||||
.rdesc {
|
.rdesc {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: rgba(255, 255, 255, 0.747);
|
color: rgba(255, 255, 255, 0.747);
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<RouterLink :to="{ name: Routes.favoriteTracks }" class="favoritescard rounded">
|
<RouterLink :to="{ name: Routes.favoriteTracks }" class="favoritescard rounded">
|
||||||
<div class="img">
|
<div class="img">
|
||||||
<svg width="100" height="100" viewBox="0 0 28 28" fill="#ff453a" xmlns="http://www.w3.org/2000/svg">
|
<div class="blur" :style="{ backgroundImage: `url(${paths.images.thumb.small + item.image})` }"></div>
|
||||||
|
</div>
|
||||||
|
<div class="overlay">
|
||||||
|
<PlayBtn :source="playSources.favorite" />
|
||||||
|
<svg
|
||||||
|
class="heart"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 28 28"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
:style="{ color: color }"
|
||||||
<path
|
<path
|
||||||
d="M13.9912 22.1445C14.2197 22.1445 14.5449 21.9775 14.8086 21.8105C19.7217 18.6465 22.8682 14.9375 22.8682 11.1758C22.8682 7.9502 20.6445 5.7002 17.8408 5.7002C16.0918 5.7002 14.7822 6.66699 13.9912 8.11719C13.2178 6.67578 11.8994 5.7002 10.1504 5.7002C7.34668 5.7002 5.11426 7.9502 5.11426 11.1758C5.11426 14.9375 8.26074 18.6465 13.1738 21.8105C13.4463 21.9775 13.7715 22.1445 13.9912 22.1445Z"
|
d="M13.9912 22.1445C14.2197 22.1445 14.5449 21.9775 14.8086 21.8105C19.7217 18.6465 22.8682 14.9375 22.8682 11.1758C22.8682 7.9502 20.6445 5.7002 17.8408 5.7002C16.0918 5.7002 14.7822 6.66699 13.9912 8.11719C13.2178 6.67578 11.8994 5.7002 10.1504 5.7002C7.34668 5.7002 5.11426 7.9502 5.11426 11.1758C5.11426 14.9375 8.26074 18.6465 13.1738 21.8105C13.4463 21.9775 13.7715 22.1445 13.9912 22.1445Z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<PlayBtn :source="playSources.favorite" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="rhelp playlist">
|
<div class="rhelp playlist">
|
||||||
@@ -15,36 +26,71 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="title">Favorite Tracks</div>
|
<div class="title">Favorite Tracks</div>
|
||||||
<div class="fcount">
|
<div class="fcount">
|
||||||
<b>{{ item.count + ` Track${item.count == 1 ? "" : "s"}` }}</b>
|
<b>{{ item.count + ` Track${item.count == 1 ? '' : 's'}` }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { playSources } from "@/enums";
|
import { paths } from '@/config'
|
||||||
import { Routes } from "@/router";
|
import { Routes } from '@/router'
|
||||||
import PlayBtn from "../shared/PlayBtn.vue";
|
import { playSources } from '@/enums'
|
||||||
|
import PlayBtn from '../shared/PlayBtn.vue'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
item: any;
|
item: {
|
||||||
}>();
|
time: string
|
||||||
|
count: number
|
||||||
|
image: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.favoritescard {
|
.favoritescard {
|
||||||
padding: $medium;
|
padding: $medium;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.img {
|
.img,
|
||||||
|
.overlay {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
background-color: $gray5;
|
|
||||||
border-radius: $small;
|
border-radius: $small;
|
||||||
margin-bottom: $medium;
|
margin-bottom: $medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.blur {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-image: linear-gradient(37deg, $gray5, $gray, $gray);
|
||||||
|
// background-image: url('http://localhost:1980/img/thumbnail/xsmall/e74d8c49e8d6340f.webp?pathhash=24bf8142d7150965');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
filter: brightness(0.5) blur(15px);
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-image: linear-gradient(37deg, $gray5, $gray, $gray);
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
$size: calc(100% - $medium * 2);
|
||||||
|
position: absolute;
|
||||||
|
top: $medium;
|
||||||
|
left: $medium;
|
||||||
|
width: $size;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heart {
|
||||||
|
color: $pink;
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-btn {
|
.play-btn {
|
||||||
|
|||||||
@@ -2,10 +2,17 @@
|
|||||||
<div
|
<div
|
||||||
class="songlist-item rounded-sm"
|
class="songlist-item rounded-sm"
|
||||||
:class="[{ current: isCurrent() }, { contexton: context_menu_showing }]"
|
:class="[{ current: isCurrent() }, { contexton: context_menu_showing }]"
|
||||||
@dblclick.prevent="emitUpdate"
|
@dblclick="emitUpdate"
|
||||||
@contextmenu.prevent="showMenu"
|
@contextmenu.prevent="showMenu"
|
||||||
>
|
>
|
||||||
<TrackIndex v-if="!isSmall" :index="index" :is_fav="is_fav" @add-to-fav="addToFav(track.trackhash)" />
|
<TrackIndex
|
||||||
|
v-if="!isSmall"
|
||||||
|
:index="index"
|
||||||
|
:is_fav="is_fav"
|
||||||
|
:show-inline-fav-icon="settings.showInlineFavIcon"
|
||||||
|
@add-to-fav="addToFav(track.trackhash)"
|
||||||
|
/>
|
||||||
|
|
||||||
<TrackTitle
|
<TrackTitle
|
||||||
:track="track"
|
:track="track"
|
||||||
:is_current="isCurrent()"
|
:is_current="isCurrent()"
|
||||||
@@ -23,10 +30,13 @@
|
|||||||
/>
|
/>
|
||||||
<TrackDuration
|
<TrackDuration
|
||||||
:duration="track.duration || 0"
|
:duration="track.duration || 0"
|
||||||
@showMenu="showMenu"
|
|
||||||
:help_text="track.help_text"
|
:help_text="track.help_text"
|
||||||
:is_fav="is_fav"
|
:is_fav="is_fav"
|
||||||
:showFavIcon="!isFavoritesPage"
|
:showFavIcon="!isFavoritesPage"
|
||||||
|
:showInlineFavIcon="settings.showInlineFavIcon"
|
||||||
|
:highlightFavoriteTracks="settings.highlightFavoriteTracks"
|
||||||
|
@showMenu="showMenu"
|
||||||
|
@toggleFav="addToFav(track.trackhash)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,7 +57,9 @@ import TrackAlbum from './SongItem/TrackAlbum.vue'
|
|||||||
import TrackDuration from './SongItem/TrackDuration.vue'
|
import TrackDuration from './SongItem/TrackDuration.vue'
|
||||||
import TrackIndex from './SongItem/TrackIndex.vue'
|
import TrackIndex from './SongItem/TrackIndex.vue'
|
||||||
import TrackTitle from './SongItem/TrackTitle.vue'
|
import TrackTitle from './SongItem/TrackTitle.vue'
|
||||||
|
import useSettings from '@/stores/settings'
|
||||||
|
|
||||||
|
const settings = useSettings()
|
||||||
const context_menu_showing = ref(false)
|
const context_menu_showing = ref(false)
|
||||||
|
|
||||||
const queue = useQueueStore()
|
const queue = useQueueStore()
|
||||||
@@ -131,9 +143,9 @@ const isFavoritesPage = route.path.startsWith('/favorites')
|
|||||||
transition: background-color 0.2s ease-out;
|
transition: background-color 0.2s ease-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $gray5;
|
background-color: $gray;
|
||||||
|
|
||||||
.index {
|
.index.ready {
|
||||||
.text {
|
.text {
|
||||||
transition-delay: 400ms;
|
transition-delay: 400ms;
|
||||||
|
|
||||||
@@ -157,6 +169,10 @@ const isFavoritesPage = route.path.startsWith('/favorites')
|
|||||||
.song-duration.help-text {
|
.song-duration.help-text {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options-and-duration .heart-icon.showInlineFavIcon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="options-and-duration">
|
<div class="options-and-duration">
|
||||||
<div v-if="is_fav && showFavIcon !== false" class="heart-icon is-favorited">
|
<div
|
||||||
|
v-if="showInlineFavIcon"
|
||||||
|
class="heart-icon"
|
||||||
|
:class="{ showInlineFavIcon, 'is_fav': is_fav && highlightFavoriteTracks }"
|
||||||
|
@click.stop="$emit('toggleFav')"
|
||||||
|
>
|
||||||
<HeartSvg :state="is_fav" :no_emit="true" />
|
<HeartSvg :state="is_fav" :no_emit="true" />
|
||||||
</div>
|
</div>
|
||||||
<div class="song-duration" :class="{ has_help_text: help_text }">{{ formatSeconds(duration) }}</div>
|
<div class="song-duration" :class="{ has_help_text: help_text }">{{ formatSeconds(duration) }}</div>
|
||||||
@@ -21,12 +26,15 @@ import HeartSvg from '../HeartSvg.vue'
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
duration: number
|
duration: number
|
||||||
is_fav: boolean
|
is_fav: boolean
|
||||||
|
showInlineFavIcon: boolean
|
||||||
|
highlightFavoriteTracks: boolean
|
||||||
showFavIcon?: boolean
|
showFavIcon?: boolean
|
||||||
help_text?: string
|
help_text?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: 'showMenu', event: MouseEvent): void
|
(e: 'showMenu', event: MouseEvent): void
|
||||||
|
(e: 'toggleFav'): void
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -39,23 +47,24 @@ defineEmits<{
|
|||||||
margin-right: $small;
|
margin-right: $small;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@include allPhones {
|
|
||||||
gap: $small;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include mediumPhones {
|
@include mediumPhones {
|
||||||
> .heart-icon.is-favorited {
|
> .heart-icon.is-favorited {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .heart-icon.is-favorited {
|
.heart-icon {
|
||||||
display: block;
|
display: none;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 0.2s ease-out;
|
transition: opacity 0.2s ease-out;
|
||||||
|
transform: scale(0.8);
|
||||||
|
margin-right: $small;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
|
|
||||||
@include mediumPhones {
|
@include mediumPhones {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -66,6 +75,10 @@ defineEmits<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heart-icon.is_fav {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.song-duration {
|
.song-duration {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
class="index t-center ellip"
|
class="index t-center ellip"
|
||||||
@click.prevent="$emit('addToFav')"
|
@click.prevent="$emit('addToFav')"
|
||||||
@dblclick.prevent.stop="() => {}"
|
@dblclick.prevent.stop="() => {}"
|
||||||
|
:class="{ 'ready': !showInlineFavIcon }"
|
||||||
>
|
>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
{{ index }}
|
{{ index }}
|
||||||
</div>
|
</div>
|
||||||
<div class="heart-icon">
|
<div class="heart-icon" v-if="!showInlineFavIcon">
|
||||||
<HeartSvg :state="is_fav" :no_emit="true" />
|
<HeartSvg :state="is_fav" :no_emit="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,6 +20,7 @@ import HeartSvg from "../HeartSvg.vue";
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
index: number | string;
|
index: number | string;
|
||||||
is_fav: boolean | undefined;
|
is_fav: boolean | undefined;
|
||||||
|
showInlineFavIcon: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
@@ -53,7 +55,6 @@ defineEmits<{
|
|||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
transform: translateX(-1.5rem);
|
transform: translateX(-1.5rem);
|
||||||
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border: none;
|
border: none;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
const development = import.meta.env.DEV
|
const development = import.meta.env.DEV
|
||||||
|
|
||||||
export function getBaseUrl() {
|
export function getBaseUrl() {
|
||||||
const base_url = window.location.origin
|
|
||||||
|
|
||||||
if (!development) {
|
if (!development) {
|
||||||
return base_url
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base_url = window.location.origin
|
||||||
const splits = base_url.split(':')
|
const splits = base_url.split(':')
|
||||||
return base_url.replace(splits[splits.length - 1], '1980')
|
return base_url.replace(splits[splits.length - 1], '1980')
|
||||||
}
|
}
|
||||||
|
|
||||||
const base_url = getBaseUrl()
|
const base_url = getBaseUrl()
|
||||||
|
axios.defaults.baseURL = base_url
|
||||||
|
|
||||||
const baseImgUrl = base_url + '/img'
|
const baseImgUrl = base_url + '/img'
|
||||||
|
|
||||||
const imageRoutes = {
|
const imageRoutes = {
|
||||||
@@ -31,7 +34,7 @@ const imageRoutes = {
|
|||||||
|
|
||||||
export const paths = {
|
export const paths = {
|
||||||
api: {
|
api: {
|
||||||
favorites: base_url + '/favorites',
|
favorites: '/favorites',
|
||||||
get favAlbums() {
|
get favAlbums() {
|
||||||
return this.favorites + '/albums'
|
return this.favorites + '/albums'
|
||||||
},
|
},
|
||||||
@@ -50,15 +53,15 @@ export const paths = {
|
|||||||
get removeFavorite() {
|
get removeFavorite() {
|
||||||
return this.favorites + '/remove'
|
return this.favorites + '/remove'
|
||||||
},
|
},
|
||||||
artist: base_url + '/artist',
|
artist: '/artist',
|
||||||
lyrics: base_url + '/lyrics',
|
lyrics: '/lyrics',
|
||||||
plugins: base_url + '/plugins',
|
plugins: '/plugins',
|
||||||
get mixes() {
|
get mixes() {
|
||||||
return this.plugins + '/mixes'
|
return this.plugins + '/mixes'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Single album
|
// Single album
|
||||||
album: base_url + '/album',
|
album: '/album',
|
||||||
get albumartists() {
|
get albumartists() {
|
||||||
return this.album + '/artists'
|
return this.album + '/artists'
|
||||||
},
|
},
|
||||||
@@ -72,12 +75,12 @@ export const paths = {
|
|||||||
return this.album + '/other-versions'
|
return this.album + '/other-versions'
|
||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
base: base_url + '/folder',
|
base: '/folder',
|
||||||
showInFiles: base_url + '/folder/show-in-files',
|
showInFiles: '/folder/show-in-files',
|
||||||
},
|
},
|
||||||
dir_browser: base_url + '/folder/dir-browser',
|
dir_browser: '/folder/dir-browser',
|
||||||
playlist: {
|
playlist: {
|
||||||
base: base_url + '/playlists',
|
base: '/playlists',
|
||||||
get new() {
|
get new() {
|
||||||
return this.base + '/new'
|
return this.base + '/new'
|
||||||
},
|
},
|
||||||
@@ -85,11 +88,11 @@ export const paths = {
|
|||||||
return this.base + '/artists'
|
return this.base + '/artists'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pages: {
|
collections: {
|
||||||
base: base_url + '/pages',
|
base: '/collections',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
base: base_url + '/search',
|
base: '/search',
|
||||||
get top() {
|
get top() {
|
||||||
return this.base + '/top?q='
|
return this.base + '/top?q='
|
||||||
},
|
},
|
||||||
@@ -107,13 +110,13 @@ export const paths = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
logger: {
|
logger: {
|
||||||
base: base_url + '/logger',
|
base: '/logger',
|
||||||
get logTrack() {
|
get logTrack() {
|
||||||
return this.base + '/track/log'
|
return this.base + '/track/log'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getall: {
|
getall: {
|
||||||
base: base_url + '/getall',
|
base: '/getall',
|
||||||
get albums() {
|
get albums() {
|
||||||
return this.base + '/albums'
|
return this.base + '/albums'
|
||||||
},
|
},
|
||||||
@@ -122,7 +125,7 @@ export const paths = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
base: base_url + '/colors',
|
base: '/colors',
|
||||||
get album() {
|
get album() {
|
||||||
return this.base + '/album'
|
return this.base + '/album'
|
||||||
},
|
},
|
||||||
@@ -145,9 +148,9 @@ export const paths = {
|
|||||||
return this.base + '/update'
|
return this.base + '/update'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
files: base_url + '/file',
|
files: '/file',
|
||||||
home: {
|
home: {
|
||||||
base: base_url + '/nothome',
|
base: '/nothome',
|
||||||
get recentlyAdded() {
|
get recentlyAdded() {
|
||||||
return this.base + '/recents/added'
|
return this.base + '/recents/added'
|
||||||
},
|
},
|
||||||
@@ -186,7 +189,7 @@ export const paths = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
backups: {
|
backups: {
|
||||||
base: base_url + '/backup',
|
base: '/backup',
|
||||||
get get_backups() {
|
get get_backups() {
|
||||||
return this.base + '/list'
|
return this.base + '/list'
|
||||||
},
|
},
|
||||||
@@ -201,7 +204,7 @@ export const paths = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
base: base_url + '/logger',
|
base: '/logger',
|
||||||
get topArtists() {
|
get topArtists() {
|
||||||
return this.base + '/top-artists'
|
return this.base + '/top-artists'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { router, Routes } from '@/router'
|
import { router, Routes } from '@/router'
|
||||||
|
|
||||||
import useAlbum from '@/stores/pages/album'
|
import useAlbum from '@/stores/pages/album'
|
||||||
|
import useCollection from '@/stores/pages/collections'
|
||||||
import useTracklist from '@/stores/queue/tracklist'
|
import useTracklist from '@/stores/queue/tracklist'
|
||||||
import usePage from '@/stores/pages/page'
|
|
||||||
|
|
||||||
import { getAlbumTracks } from '@/requests/album'
|
import { getAlbumTracks } from '@/requests/album'
|
||||||
|
import { addOrRemoveItemFromCollection } from '@/requests/collections'
|
||||||
import { addAlbumToPlaylist } from '@/requests/playlists'
|
import { addAlbumToPlaylist } from '@/requests/playlists'
|
||||||
import { addOrRemoveItemFromPage } from '@/requests/pages'
|
|
||||||
|
|
||||||
import { Album, Option, Page, Playlist, Track } from '@/interfaces'
|
|
||||||
import { AddToQueueIcon, DeleteIcon, PlayNextIcon, PlusIcon } from '@/icons'
|
import { AddToQueueIcon, DeleteIcon, PlayNextIcon, PlusIcon } from '@/icons'
|
||||||
import { getAddToPageOptions, getAddToPlaylistOptions, get_find_on_social } from './utils'
|
import { Album, Collection, Option, Playlist, Track } from '@/interfaces'
|
||||||
|
import { get_find_on_social, getAddToCollectionOptions, getAddToPlaylistOptions } from './utils'
|
||||||
|
|
||||||
export default async (album?: Album) => {
|
export default async (album?: Album) => {
|
||||||
const albumStore = useAlbum()
|
const albumStore = useAlbum()
|
||||||
@@ -66,15 +66,15 @@ export default async (album?: Album) => {
|
|||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
const addToPageAction = (page: Page) => {
|
const addToPageAction = (page: Collection) => {
|
||||||
addOrRemoveItemFromPage(page.id, album, 'album', 'add')
|
addOrRemoveItemFromCollection(page.id, album, 'album', 'add')
|
||||||
}
|
}
|
||||||
|
|
||||||
const add_to_page: Option = {
|
const add_to_page: Option = {
|
||||||
label: 'Add to Page',
|
label: 'Add to Collection',
|
||||||
children: () =>
|
children: () =>
|
||||||
getAddToPageOptions(addToPageAction, {
|
getAddToCollectionOptions(addToPageAction, {
|
||||||
page: null,
|
collection: null,
|
||||||
hash: album.albumhash,
|
hash: album.albumhash,
|
||||||
type: 'album',
|
type: 'album',
|
||||||
extra: {},
|
extra: {},
|
||||||
@@ -83,17 +83,17 @@ export default async (album?: Album) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const remove_from_page: Option = {
|
const remove_from_page: Option = {
|
||||||
label: 'Remove from Page',
|
label: 'Remove item',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
const success = await addOrRemoveItemFromPage(
|
const success = await addOrRemoveItemFromCollection(
|
||||||
parseInt(router.currentRoute.value.params.page as string),
|
parseInt(router.currentRoute.value.params.collection as string),
|
||||||
album,
|
album,
|
||||||
'album',
|
'album',
|
||||||
'remove'
|
'remove'
|
||||||
)
|
)
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
usePage().removeLocalItem(album, 'album')
|
useCollection().removeLocalItem(album, 'album')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: DeleteIcon,
|
icon: DeleteIcon,
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { Routes } from '@/router'
|
import { Routes, router } from '@/router'
|
||||||
import { router } from '@/router'
|
|
||||||
|
|
||||||
import usePage from '@/stores/pages/page'
|
import useCollection from '@/stores/pages/collections'
|
||||||
import useTracklist from '@/stores/queue/tracklist'
|
import useTracklist from '@/stores/queue/tracklist'
|
||||||
|
|
||||||
import { getArtistTracks } from '@/requests/artists'
|
import { getArtistTracks } from '@/requests/artists'
|
||||||
|
import { addOrRemoveItemFromCollection } from '@/requests/collections'
|
||||||
import { addArtistToPlaylist } from '@/requests/playlists'
|
import { addArtistToPlaylist } from '@/requests/playlists'
|
||||||
import { addOrRemoveItemFromPage } from '@/requests/pages'
|
|
||||||
|
|
||||||
import { Artist, Option, Page, Playlist } from '@/interfaces'
|
|
||||||
import { AddToQueueIcon, DeleteIcon, PlayNextIcon, PlusIcon } from '@/icons'
|
import { AddToQueueIcon, DeleteIcon, PlayNextIcon, PlusIcon } from '@/icons'
|
||||||
import { getAddToPageOptions, getAddToPlaylistOptions, get_find_on_social } from './utils'
|
import { Artist, Collection, Option, Playlist } from '@/interfaces'
|
||||||
|
import { getAddToCollectionOptions, getAddToPlaylistOptions, get_find_on_social } from './utils'
|
||||||
|
|
||||||
export default async (artisthash: string, artistname: string) => {
|
export default async (artisthash: string, artistname: string) => {
|
||||||
const play_next = <Option>{
|
const play_next = <Option>{
|
||||||
@@ -50,9 +49,9 @@ export default async (artisthash: string, artistname: string) => {
|
|||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
const addToPageAction = (page: Page) => {
|
const addToCollectionAction = (collection: Collection) => {
|
||||||
addOrRemoveItemFromPage(
|
addOrRemoveItemFromCollection(
|
||||||
page.id,
|
collection.id,
|
||||||
{
|
{
|
||||||
artisthash,
|
artisthash,
|
||||||
} as Artist,
|
} as Artist,
|
||||||
@@ -62,10 +61,10 @@ export default async (artisthash: string, artistname: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const add_to_page: Option = {
|
const add_to_page: Option = {
|
||||||
label: 'Add to Page',
|
label: 'Add to Collection',
|
||||||
children: () =>
|
children: () =>
|
||||||
getAddToPageOptions(addToPageAction, {
|
getAddToCollectionOptions(addToCollectionAction, {
|
||||||
page: null,
|
collection: null,
|
||||||
hash: artisthash,
|
hash: artisthash,
|
||||||
type: 'artist',
|
type: 'artist',
|
||||||
extra: {},
|
extra: {},
|
||||||
@@ -73,11 +72,11 @@ export default async (artisthash: string, artistname: string) => {
|
|||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
const remove_from_page: Option = {
|
const remove_from_collection: Option = {
|
||||||
label: 'Remove from Page',
|
label: 'Remove item',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
const success = await addOrRemoveItemFromPage(
|
const success = await addOrRemoveItemFromCollection(
|
||||||
parseInt(router.currentRoute.value.params.page as string),
|
parseInt(router.currentRoute.value.params.collection as string),
|
||||||
{
|
{
|
||||||
artisthash,
|
artisthash,
|
||||||
} as Artist,
|
} as Artist,
|
||||||
@@ -86,7 +85,7 @@ export default async (artisthash: string, artistname: string) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
usePage().removeLocalItem({ artisthash } as Artist, 'artist')
|
useCollection().removeLocalItem({ artisthash } as Artist, 'artist')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: DeleteIcon,
|
icon: DeleteIcon,
|
||||||
@@ -96,7 +95,7 @@ export default async (artisthash: string, artistname: string) => {
|
|||||||
play_next,
|
play_next,
|
||||||
add_to_queue,
|
add_to_queue,
|
||||||
add_to_playlist,
|
add_to_playlist,
|
||||||
...[router.currentRoute.value.name === Routes.Page ? remove_from_page : add_to_page],
|
...[router.currentRoute.value.name === Routes.Page ? remove_from_collection : add_to_page],
|
||||||
get_find_on_social('artist'),
|
get_find_on_social('artist'),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import modal from '@/stores/modal'
|
import modal from '@/stores/modal'
|
||||||
import useAlbum from '@/stores/pages/album'
|
|
||||||
import useArtist from '@/stores/pages/artist'
|
import useArtist from '@/stores/pages/artist'
|
||||||
|
|
||||||
import { SearchIcon } from '@/icons'
|
import { SearchIcon } from '@/icons'
|
||||||
import { Album, Option, Page, Playlist } from '@/interfaces'
|
import { Album, Collection, Option, Playlist } from '@/interfaces'
|
||||||
import { getAllPages } from '@/requests/pages'
|
import { getAllCollections } from '@/requests/collections'
|
||||||
import { getAllPlaylists } from '@/requests/playlists'
|
import { getAllPlaylists } from '@/requests/playlists'
|
||||||
|
|
||||||
export const separator: Option = {
|
export const separator: Option = {
|
||||||
@@ -20,11 +19,11 @@ export function get_new_playlist_option(new_playlist_modal_props: any = {}): Opt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_new_page_option(new_playlist_modal_props: any = {}): Option {
|
export function get_new_collection_option(new_collection_modal_props: any = {}): Option {
|
||||||
return {
|
return {
|
||||||
label: 'New page',
|
label: 'New Collection',
|
||||||
action: () => {
|
action: () => {
|
||||||
modal().showPageModal(new_playlist_modal_props)
|
modal().showCollectionModal(new_collection_modal_props)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,28 +66,31 @@ export async function getAddToPlaylistOptions(addToPlaylist: action, new_playlis
|
|||||||
* @param new_playlist_modal_props Props to be passed to the modal when creating a new playlist
|
* @param new_playlist_modal_props Props to be passed to the modal when creating a new playlist
|
||||||
* @returns A list of options to be used in a context menu
|
* @returns A list of options to be used in a context menu
|
||||||
*/
|
*/
|
||||||
export async function getAddToPageOptions(addToPage: (page: Page) => void, new_page_modal_props: any = {}) {
|
export async function getAddToCollectionOptions(
|
||||||
const new_page = get_new_page_option(new_page_modal_props)
|
addToCollection: (collection: Collection) => void,
|
||||||
const p = await getAllPages()
|
new_page_modal_props: any = {}
|
||||||
|
) {
|
||||||
|
const new_page = get_new_collection_option(new_page_modal_props)
|
||||||
|
const data = await getAllCollections()
|
||||||
|
|
||||||
let items = [new_page]
|
let items = [new_page]
|
||||||
|
|
||||||
if (p.length === 0) {
|
if (data.length === 0) {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
let pages = <Option[]>[]
|
let collections = <Option[]>[]
|
||||||
|
|
||||||
pages = p.map(playlist => {
|
collections = data.map(collection => {
|
||||||
return <Option>{
|
return <Option>{
|
||||||
label: playlist.name,
|
label: collection.name,
|
||||||
action: () => {
|
action: () => {
|
||||||
addToPage(playlist)
|
addToCollection(collection)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return [...items, separator, ...pages]
|
return [...items, separator, ...collections]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const get_find_on_social = (page = 'album', query = '', album?: Album) => {
|
export const get_find_on_social = (page = 'album', query = '', album?: Album) => {
|
||||||
|
|||||||
@@ -105,4 +105,5 @@ export interface DBSettings {
|
|||||||
lastfmApiKey: string;
|
lastfmApiKey: string;
|
||||||
lastfmApiSecret: string;
|
lastfmApiSecret: string;
|
||||||
lastfmSessionKey: string;
|
lastfmSessionKey: string;
|
||||||
|
showPlaylistsInFolderView: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,12 +84,10 @@ export async function playFromFolderCard(folderpath: string) {
|
|||||||
export async function playFromFavorites(track: Track | undefined) {
|
export async function playFromFavorites(track: Track | undefined) {
|
||||||
const queue = useQueue()
|
const queue = useQueue()
|
||||||
const tracklist = useTracklist()
|
const tracklist = useTracklist()
|
||||||
console.log(track)
|
|
||||||
|
|
||||||
// if our tracklist is not from favorites, we need to fetch the favorites
|
// if our tracklist is not from favorites, we need to fetch the favorites
|
||||||
if (tracklist.from.type !== FromOptions.favorite) {
|
if (tracklist.from.type !== FromOptions.favorite) {
|
||||||
const res = await getFavTracks(0, -1)
|
const res = await getFavTracks(0, -1)
|
||||||
console.log(res)
|
|
||||||
tracklist.setFromFav(res.tracks)
|
tracklist.setFromFav(res.tracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +97,6 @@ export async function playFromFavorites(track: Track | undefined) {
|
|||||||
index = tracklist.tracklist.findIndex(t => t.trackhash === track?.trackhash)
|
index = tracklist.tracklist.findIndex(t => t.trackhash === track?.trackhash)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(tracklist.tracklist)
|
|
||||||
queue.play(index)
|
queue.play(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface Track extends AlbumDisc {
|
|||||||
filetype: string
|
filetype: string
|
||||||
is_favorite: boolean
|
is_favorite: boolean
|
||||||
explicit: boolean
|
explicit: boolean
|
||||||
|
type?: string
|
||||||
|
|
||||||
og_title: string
|
og_title: string
|
||||||
og_album: string
|
og_album: string
|
||||||
@@ -133,6 +134,7 @@ export interface Artist {
|
|||||||
help_text?: string
|
help_text?: string
|
||||||
time?: string
|
time?: string
|
||||||
genres: Genre[]
|
genres: Genre[]
|
||||||
|
type?: string
|
||||||
|
|
||||||
// available in charts
|
// available in charts
|
||||||
trend?: {
|
trend?: {
|
||||||
@@ -180,7 +182,7 @@ export interface Playlist {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Page {
|
export interface Collection {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
items: (Album | Artist | Mix | Playlist)[]
|
items: (Album | Artist | Mix | Playlist)[]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Album, Artist, Genre, StatItem, Track } from '@/interfaces'
|
|||||||
import { NotifType, useToast } from '@/stores/notification'
|
import { NotifType, useToast } from '@/stores/notification'
|
||||||
import useAxios from './useAxios'
|
import useAxios from './useAxios'
|
||||||
|
|
||||||
export const getArtistData = async (hash: string, limit: number = 5, albumlimit: number = 7) => {
|
export const getArtistData = async (hash: string, limit: number = 15, albumlimit: number = 7) => {
|
||||||
interface ArtistData {
|
interface ArtistData {
|
||||||
artist: Artist
|
artist: Artist
|
||||||
tracks: Track[]
|
tracks: Track[]
|
||||||
@@ -19,7 +19,7 @@ export const getArtistData = async (hash: string, limit: number = 5, albumlimit:
|
|||||||
|
|
||||||
const { data, error, status } = await useAxios({
|
const { data, error, status } = await useAxios({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: paths.api.artist + `/${hash}?limit=${limit}&albumlimit=${albumlimit}`,
|
url: paths.api.artist + `/${hash}?tracklimit=${limit}&albumlimit=${albumlimit}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (status == 404) {
|
if (status == 404) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import useAxios from './useAxios'
|
|||||||
import { User, UserSimplified } from '@/interfaces'
|
import { User, UserSimplified } from '@/interfaces'
|
||||||
|
|
||||||
export async function getAllUsers<T extends boolean>(simple: T = true as T) {
|
export async function getAllUsers<T extends boolean>(simple: T = true as T) {
|
||||||
interface res {
|
interface Response {
|
||||||
users: T extends true ? UserSimplified[] : User[]
|
users: T extends true ? UserSimplified[] : User[]
|
||||||
settings: { [key: string]: any }
|
settings: { [key: string]: any }
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ export async function getAllUsers<T extends boolean>(simple: T = true as T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data as res
|
return res.data as Response
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
import { paths } from '@/config'
|
import { paths } from '@/config'
|
||||||
import { Album, Artist, Mix, Page, Playlist } from '@/interfaces'
|
import { Album, Artist, Collection, Mix, Playlist } from '@/interfaces'
|
||||||
import { Notification, NotifType } from '@/stores/notification'
|
import { Notification, NotifType } from '@/stores/notification'
|
||||||
import useAxios from './useAxios'
|
import useAxios from './useAxios'
|
||||||
|
|
||||||
const { base: basePageUrl } = paths.api.pages
|
const { base: baseCollectionUrl } = paths.api.collections
|
||||||
|
|
||||||
export async function getAllPages() {
|
export async function getAllCollections() {
|
||||||
const { data, status } = await useAxios({
|
const { data, status } = await useAxios({
|
||||||
url: basePageUrl,
|
url: baseCollectionUrl,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (status == 200) {
|
if (status == 200) {
|
||||||
return data as Page[]
|
return data as Collection[]
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPage(page_id: string) {
|
export async function getCollection(collection_id: string) {
|
||||||
const { data, status } = await useAxios({
|
const { data, status } = await useAxios({
|
||||||
url: basePageUrl + `/${page_id}`,
|
url: baseCollectionUrl + `/${collection_id}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
|
|
||||||
return data as Page
|
return data as Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNewPage(
|
export async function createNewCollection(
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
items?: { hash: string; type: string; extra: any }[]
|
items?: { hash: string; type: string; extra: any }[]
|
||||||
) {
|
) {
|
||||||
const { data, status } = await useAxios({
|
const { data, status } = await useAxios({
|
||||||
url: basePageUrl,
|
url: baseCollectionUrl,
|
||||||
props: {
|
props: {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@@ -49,9 +49,9 @@ export async function createNewPage(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePage(page: Page, name: string, description: string) {
|
export async function updateCollection(collection: Collection, name: string, description: string) {
|
||||||
const { data, status } = await useAxios({
|
const { data, status } = await useAxios({
|
||||||
url: basePageUrl + `/${page.id}`,
|
url: baseCollectionUrl + `/${collection.id}`,
|
||||||
props: {
|
props: {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@@ -60,14 +60,14 @@ export async function updatePage(page: Page, name: string, description: string)
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (status == 200) {
|
if (status == 200) {
|
||||||
return data.page as Page
|
return data as Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addOrRemoveItemFromPage(
|
export async function addOrRemoveItemFromCollection(
|
||||||
page_number: number,
|
collection_id: number,
|
||||||
item: Album | Artist | Mix | Playlist,
|
item: Album | Artist | Mix | Playlist,
|
||||||
type: string,
|
type: string,
|
||||||
command: 'add' | 'remove'
|
command: 'add' | 'remove'
|
||||||
@@ -94,11 +94,11 @@ export async function addOrRemoveItemFromPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (payload.hash === '') {
|
if (payload.hash === '') {
|
||||||
throw new Error('Invalid item type. Item not added to page.')
|
throw new Error('Invalid item type. Item not added to collection.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, status } = await useAxios({
|
const { data, status } = await useAxios({
|
||||||
url: basePageUrl + `/${page_number}/items`,
|
url: baseCollectionUrl + `/${collection_id}/items`,
|
||||||
props: {
|
props: {
|
||||||
item: payload,
|
item: payload,
|
||||||
},
|
},
|
||||||
@@ -116,7 +116,7 @@ export async function addOrRemoveItemFromPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status == 400) {
|
if (status == 400) {
|
||||||
new Notification(`${payload.type[0].toUpperCase() + payload.type.slice(1)} already in page`, NotifType.Error)
|
new Notification(`${payload.type[0].toUpperCase() + payload.type.slice(1)} already in collection`, NotifType.Error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,9 +124,9 @@ export async function addOrRemoveItemFromPage(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePage(page_number: number) {
|
export async function deleteCollection(collection_id: number) {
|
||||||
const { data, status } = await useAxios({
|
const { data, status } = await useAxios({
|
||||||
url: basePageUrl + `/${page_number}`,
|
url: baseCollectionUrl + `/${collection_id}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ export async function getBackups() {
|
|||||||
playlists: number
|
playlists: number
|
||||||
scrobbles: number
|
scrobbles: number
|
||||||
favorites: number
|
favorites: number
|
||||||
|
collections: number
|
||||||
date: string
|
date: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
import { FetchProps } from '@/interfaces'
|
import { FetchProps } from '@/interfaces'
|
||||||
import axios, { AxiosError, AxiosResponse } from 'axios'
|
import axios from 'axios'
|
||||||
import useModal from '@/stores/modal'
|
import useModal from '@/stores/modal'
|
||||||
|
|
||||||
import useLoaderStore from '@/stores/loader'
|
import useLoaderStore from '@/stores/loader'
|
||||||
import { logoutUser } from './auth'
|
import { logoutUser } from './auth'
|
||||||
|
|
||||||
const development = import.meta.env.DEV
|
if (window.location.protocol === 'https:') {
|
||||||
|
const meta = document.createElement('meta');
|
||||||
export function getBaseUrl() {
|
meta.httpEquiv = 'Content-Security-Policy';
|
||||||
const base_url = window.location.origin
|
meta.content = 'upgrade-insecure-requests';
|
||||||
|
document.head.appendChild(meta);
|
||||||
if (!development) {
|
|
||||||
return base_url
|
|
||||||
}
|
|
||||||
|
|
||||||
const splits = base_url.split(':')
|
|
||||||
return base_url.replace(splits[splits.length - 1], '1980')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.defaults.baseURL = getBaseUrl()
|
|
||||||
|
|
||||||
export default async (args: FetchProps, withCredentials: boolean = true) => {
|
export default async (args: FetchProps, withCredentials: boolean = true) => {
|
||||||
const on_ngrok = args.url.includes('ngrok')
|
const on_ngrok = args.url.includes('ngrok')
|
||||||
const ngrok_config = {
|
const ngrok_config = {
|
||||||
@@ -61,7 +53,7 @@ export default async (args: FetchProps, withCredentials: boolean = true) => {
|
|||||||
try {
|
try {
|
||||||
isSignatureError = error.response.data.msg == 'Signature verification failed'
|
isSignatureError = error.response.data.msg == 'Signature verification failed'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error:', error)
|
console.error('Error:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.response?.status === 422 && isSignatureError) {
|
if (error.response?.status === 422 && isSignatureError) {
|
||||||
|
|||||||
@@ -1,213 +1,212 @@
|
|||||||
import { createRouter, createWebHashHistory, RouterOptions } from "vue-router";
|
import { createRouter, createWebHashHistory, RouterOptions } from 'vue-router'
|
||||||
|
|
||||||
import state from "@/composables/state";
|
import state from '@/composables/state'
|
||||||
import useAlbumPageStore from "@/stores/pages/album";
|
import useAlbumPageStore from '@/stores/pages/album'
|
||||||
import useFolderPageStore from "@/stores/pages/folder";
|
import useFolderPageStore from '@/stores/pages/folder'
|
||||||
import usePlaylistPageStore from "@/stores/pages/playlist";
|
import usePlaylistPageStore from '@/stores/pages/playlist'
|
||||||
import usePlaylistListPageStore from "@/stores/pages/playlists";
|
import usePlaylistListPageStore from '@/stores/pages/playlists'
|
||||||
import useArtistPageStore from "@/stores/pages/artist";
|
import useArtistPageStore from '@/stores/pages/artist'
|
||||||
|
|
||||||
|
import HomeView from '@/views/HomeView'
|
||||||
import HomeView from "@/views/HomeView";
|
const Lyrics = () => import('@/views/LyricsView')
|
||||||
const Lyrics = () => import("@/views/LyricsView");
|
const ArtistView = () => import('@/views/ArtistView')
|
||||||
const ArtistView = () => import("@/views/ArtistView");
|
const NotFound = () => import('@/views/NotFound.vue')
|
||||||
const NotFound = () => import("@/views/NotFound.vue");
|
const NowPlaying = () => import('@/views/NowPlaying')
|
||||||
const NowPlaying = () => import("@/views/NowPlaying");
|
const SearchView = () => import('@/views/SearchView')
|
||||||
const SearchView = () => import("@/views/SearchView");
|
const AlbumList = () => import('@/views/AlbumListView')
|
||||||
const AlbumList = () => import("@/views/AlbumListView");
|
const FolderView = () => import('@/views/FolderView.vue')
|
||||||
const FolderView = () => import("@/views/FolderView.vue");
|
const FavoritesView = () => import('@/views/Favorites.vue')
|
||||||
const FavoritesView = () => import("@/views/Favorites.vue");
|
const SettingsView = () => import('@/views/SettingsView.vue')
|
||||||
const SettingsView = () => import("@/views/SettingsView.vue");
|
const AlbumView = () => import('@/views/AlbumView/index.vue')
|
||||||
const AlbumView = () => import("@/views/AlbumView/index.vue");
|
const ArtistTracksView = () => import('@/views/ArtistTracks.vue')
|
||||||
const ArtistTracksView = () => import("@/views/ArtistTracks.vue");
|
const PlaylistListView = () => import('@/views/PlaylistList.vue')
|
||||||
const PlaylistListView = () => import("@/views/PlaylistList.vue");
|
const FavoriteTracks = () => import('@/views/FavoriteTracks.vue')
|
||||||
const FavoriteTracks = () => import("@/views/FavoriteTracks.vue");
|
const PlaylistView = () => import('@/views/PlaylistView/index.vue')
|
||||||
const PlaylistView = () => import("@/views/PlaylistView/index.vue");
|
const ArtistDiscographyView = () => import('@/views/ArtistDiscography.vue')
|
||||||
const ArtistDiscographyView = () => import("@/views/ArtistDiscography.vue");
|
const FavoriteCardScroller = () => import('@/views/FavoriteCardScroller.vue')
|
||||||
const FavoriteCardScroller = () => import("@/views/FavoriteCardScroller.vue");
|
const StatsView = () => import('@/views/Stats/main.vue')
|
||||||
const StatsView = () => import("@/views/Stats/main.vue");
|
const MixView = () => import('@/views/MixView.vue')
|
||||||
const MixView = () => import("@/views/MixView.vue");
|
const MixListView = () => import('@/views/MixListView.vue')
|
||||||
const MixListView = () => import("@/views/MixListView.vue");
|
const Collection = () => import('@/views/Collections/Collection.vue')
|
||||||
const Page = () => import("@/views/Pages/Page.vue");
|
|
||||||
|
|
||||||
const folder = {
|
const folder = {
|
||||||
path: "/folder/:path",
|
path: '/folder/:path',
|
||||||
name: "FolderView",
|
name: 'FolderView',
|
||||||
component: FolderView,
|
component: FolderView,
|
||||||
beforeEnter: async (to: any) => {
|
beforeEnter: async (to: any) => {
|
||||||
state.loading.value = true;
|
state.loading.value = true
|
||||||
await useFolderPageStore()
|
await useFolderPageStore()
|
||||||
.fetchAll(to.params.path, true)
|
.fetchAll(to.params.path, true)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
state.loading.value = false;
|
state.loading.value = false
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
const playlists = {
|
const playlists = {
|
||||||
path: "/playlists",
|
path: '/playlists',
|
||||||
name: "PlaylistList",
|
name: 'PlaylistList',
|
||||||
component: PlaylistListView,
|
component: PlaylistListView,
|
||||||
beforeEnter: async () => {
|
beforeEnter: async () => {
|
||||||
state.loading.value = true;
|
state.loading.value = true
|
||||||
await usePlaylistListPageStore()
|
await usePlaylistListPageStore()
|
||||||
.fetchAll()
|
.fetchAll()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
state.loading.value = false;
|
state.loading.value = false
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
const playlistView = {
|
const playlistView = {
|
||||||
path: "/playlist/:pid",
|
path: '/playlist/:pid',
|
||||||
name: "PlaylistView",
|
name: 'PlaylistView',
|
||||||
component: PlaylistView,
|
component: PlaylistView,
|
||||||
beforeEnter: async (to: any) => {
|
beforeEnter: async (to: any) => {
|
||||||
state.loading.value = true;
|
state.loading.value = true
|
||||||
await usePlaylistPageStore()
|
await usePlaylistPageStore()
|
||||||
.fetchAll(to.params.pid)
|
.fetchAll(to.params.pid)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
state.loading.value = false;
|
state.loading.value = false
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
const albumView = {
|
const albumView = {
|
||||||
path: "/albums/:albumhash",
|
path: '/albums/:albumhash',
|
||||||
name: "AlbumView",
|
name: 'AlbumView',
|
||||||
component: AlbumView,
|
component: AlbumView,
|
||||||
beforeEnter: async (to: any) => {
|
beforeEnter: async (to: any) => {
|
||||||
state.loading.value = true;
|
state.loading.value = true
|
||||||
const store = useAlbumPageStore();
|
const store = useAlbumPageStore()
|
||||||
|
|
||||||
await store.fetchTracksAndArtists(to.params.albumhash).then(() => {
|
await store.fetchTracksAndArtists(to.params.albumhash).then(() => {
|
||||||
state.loading.value = false;
|
state.loading.value = false
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
const artistView = {
|
const artistView = {
|
||||||
path: "/artists/:hash",
|
path: '/artists/:hash',
|
||||||
name: "ArtistView",
|
name: 'ArtistView',
|
||||||
component: ArtistView,
|
component: ArtistView,
|
||||||
beforeEnter: async (to: any) => {
|
beforeEnter: async (to: any) => {
|
||||||
state.loading.value = true;
|
state.loading.value = true
|
||||||
|
|
||||||
await useArtistPageStore()
|
await useArtistPageStore()
|
||||||
.getData(to.params.hash)
|
.getData(to.params.hash)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
state.loading.value = false;
|
state.loading.value = false
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
const NowPlayingView = {
|
const NowPlayingView = {
|
||||||
path: "/nowplaying/:tab",
|
path: '/nowplaying/:tab',
|
||||||
name: "NowPlaying",
|
name: 'NowPlaying',
|
||||||
component: NowPlaying,
|
component: NowPlaying,
|
||||||
};
|
}
|
||||||
|
|
||||||
const LyricsView = {
|
const LyricsView = {
|
||||||
path: "/lyrics",
|
path: '/lyrics',
|
||||||
name: "LyricsView",
|
name: 'LyricsView',
|
||||||
component: Lyrics,
|
component: Lyrics,
|
||||||
};
|
}
|
||||||
|
|
||||||
const ArtistTracks = {
|
const ArtistTracks = {
|
||||||
path: "/artists/:hash/tracks",
|
path: '/artists/:hash/tracks',
|
||||||
name: "ArtistTracks",
|
name: 'ArtistTracks',
|
||||||
component: ArtistTracksView,
|
component: ArtistTracksView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const artistDiscography = {
|
const artistDiscography = {
|
||||||
path: "/artists/:hash/discography/:type",
|
path: '/artists/:hash/discography/:type',
|
||||||
name: "ArtistDiscographyView",
|
name: 'ArtistDiscographyView',
|
||||||
component: ArtistDiscographyView,
|
component: ArtistDiscographyView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
path: "/settings/:tab",
|
path: '/settings/:tab',
|
||||||
name: "SettingsView",
|
name: 'SettingsView',
|
||||||
component: SettingsView,
|
component: SettingsView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const search = {
|
const search = {
|
||||||
path: "/search/:page",
|
path: '/search/:page',
|
||||||
name: "SearchView",
|
name: 'SearchView',
|
||||||
component: SearchView,
|
component: SearchView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const favorites = {
|
const favorites = {
|
||||||
path: "/favorites",
|
path: '/favorites',
|
||||||
name: "FavoritesView",
|
name: 'FavoritesView',
|
||||||
component: FavoritesView,
|
component: FavoritesView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const favoriteAlbums = {
|
const favoriteAlbums = {
|
||||||
path: "/favorites/albums",
|
path: '/favorites/albums',
|
||||||
name: "FavoriteAlbums",
|
name: 'FavoriteAlbums',
|
||||||
component: FavoriteCardScroller,
|
component: FavoriteCardScroller,
|
||||||
};
|
}
|
||||||
|
|
||||||
const favoriteArtists = {
|
const favoriteArtists = {
|
||||||
path: "/favorites/artists",
|
path: '/favorites/artists',
|
||||||
name: "FavoriteArtists",
|
name: 'FavoriteArtists',
|
||||||
component: FavoriteCardScroller,
|
component: FavoriteCardScroller,
|
||||||
};
|
}
|
||||||
|
|
||||||
const favoriteTracks = {
|
const favoriteTracks = {
|
||||||
path: "/favorites/tracks",
|
path: '/favorites/tracks',
|
||||||
name: "FavoriteTracks",
|
name: 'FavoriteTracks',
|
||||||
component: FavoriteTracks,
|
component: FavoriteTracks,
|
||||||
};
|
}
|
||||||
|
|
||||||
const notFound = {
|
const notFound = {
|
||||||
name: "NotFound",
|
name: 'NotFound',
|
||||||
path: "/:pathMatch(.*)",
|
path: '/:pathMatch(.*)',
|
||||||
component: NotFound,
|
component: NotFound,
|
||||||
};
|
}
|
||||||
|
|
||||||
const Home = {
|
const Home = {
|
||||||
path: "/",
|
path: '/',
|
||||||
name: "Home",
|
name: 'Home',
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const AlbumListView = {
|
const AlbumListView = {
|
||||||
path: "/albums",
|
path: '/albums',
|
||||||
name: "AlbumListView",
|
name: 'AlbumListView',
|
||||||
component: AlbumList,
|
component: AlbumList,
|
||||||
};
|
}
|
||||||
|
|
||||||
const Stats = {
|
const Stats = {
|
||||||
path: "/stats",
|
path: '/stats',
|
||||||
name: "StatsView",
|
name: 'StatsView',
|
||||||
component: StatsView,
|
component: StatsView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const ArtistListView = {
|
const ArtistListView = {
|
||||||
...AlbumListView,
|
...AlbumListView,
|
||||||
path: "/artists",
|
path: '/artists',
|
||||||
name: "ArtistListView",
|
name: 'ArtistListView',
|
||||||
};
|
}
|
||||||
|
|
||||||
const Mix = {
|
const Mix = {
|
||||||
path: "/mix/:mixid",
|
path: '/mix/:mixid',
|
||||||
name: "MixView",
|
name: 'MixView',
|
||||||
component: MixView,
|
component: MixView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const MixList = {
|
const MixList = {
|
||||||
path: "/mixes/:type",
|
path: '/mixes/:type',
|
||||||
name: "MixListView",
|
name: 'MixListView',
|
||||||
component: MixListView,
|
component: MixListView,
|
||||||
};
|
}
|
||||||
|
|
||||||
const PageView = {
|
const PageView = {
|
||||||
path: "/pages/:page",
|
path: '/collections/:collection',
|
||||||
name: "Page",
|
name: 'Collection',
|
||||||
component: Page,
|
component: Collection,
|
||||||
};
|
}
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
folder,
|
folder,
|
||||||
@@ -233,7 +232,7 @@ const routes = [
|
|||||||
Mix,
|
Mix,
|
||||||
MixList,
|
MixList,
|
||||||
PageView,
|
PageView,
|
||||||
];
|
]
|
||||||
|
|
||||||
const Routes = {
|
const Routes = {
|
||||||
folder: folder.name,
|
folder: folder.name,
|
||||||
@@ -259,12 +258,12 @@ const Routes = {
|
|||||||
Mix: Mix.name,
|
Mix: Mix.name,
|
||||||
MixList: MixList.name,
|
MixList: MixList.name,
|
||||||
Page: PageView.name,
|
Page: PageView.name,
|
||||||
};
|
}
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
mode: "hash",
|
mode: 'hash',
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
routes,
|
routes,
|
||||||
} as RouterOptions);
|
} as RouterOptions)
|
||||||
|
|
||||||
export { router, Routes };
|
export { router, Routes }
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const library = {
|
|||||||
show_if: loggedInUserIsAdmin,
|
show_if: loggedInUserIsAdmin,
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
title: "Root directories",
|
title: "Folders",
|
||||||
icon: FolderSvg,
|
icon: FolderSvg,
|
||||||
desc: rootRootStrings.desc,
|
desc: rootRootStrings.desc,
|
||||||
settings: [...rootDirSettings],
|
settings: [...rootDirSettings],
|
||||||
|
|||||||
@@ -1,26 +1,41 @@
|
|||||||
import { SettingType } from "../enums";
|
import { SettingType } from '../enums'
|
||||||
import { Setting } from "@/interfaces/settings";
|
import { Setting } from '@/interfaces/settings'
|
||||||
|
|
||||||
import useSettingsStore from "@/stores/settings";
|
import useSettingsStore from '@/stores/settings'
|
||||||
|
|
||||||
const settings = useSettingsStore;
|
const settings = useSettingsStore
|
||||||
|
|
||||||
const disable_np_img: Setting = {
|
const disable_np_img: Setting = {
|
||||||
title: "Hide album art from the left sidebar",
|
title: 'Hide album art from the left sidebar',
|
||||||
type: SettingType.binary,
|
type: SettingType.binary,
|
||||||
state: () => !settings().use_np_img,
|
state: () => !settings().use_np_img,
|
||||||
action: () => settings().toggleUseNPImg(),
|
action: () => settings().toggleUseNPImg(),
|
||||||
show_if: () => !settings().is_alt_layout,
|
show_if: () => !settings().is_alt_layout,
|
||||||
};
|
}
|
||||||
|
|
||||||
const showNowPlayingOnTabTitle: Setting = {
|
const showNowPlayingOnTabTitle: Setting = {
|
||||||
title: "Show Now Playing track on tab title",
|
title: 'Show Now Playing track on tab title',
|
||||||
desc: "Replace current page info with Now Playing track info",
|
desc: 'Replace current page info with Now Playing track info',
|
||||||
type: SettingType.binary,
|
type: SettingType.binary,
|
||||||
state: () => settings().nowPlayingTrackOnTabTitle,
|
state: () => settings().nowPlayingTrackOnTabTitle,
|
||||||
action: () => settings().toggleNowPlayingTrackOnTabTitle(),
|
action: () => settings().toggleNowPlayingTrackOnTabTitle(),
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const showInlineFavIcon: Setting = {
|
||||||
|
title: 'Show inline favorite icon',
|
||||||
|
desc: 'Show the favorite button next to the track duration',
|
||||||
|
type: SettingType.binary,
|
||||||
|
state: () => settings().showInlineFavIcon,
|
||||||
|
action: () => settings().toggleShowInlineFavIcon(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlightFavoriteTracks: Setting = {
|
||||||
|
title: 'Highlight favorite tracks',
|
||||||
|
desc: 'Always show the favorite button for favorited tracks',
|
||||||
|
type: SettingType.binary,
|
||||||
|
state: () => settings()._highlightFavoriteTracks,
|
||||||
|
action: () => settings().toggleHighlightFavoriteTracks(),
|
||||||
|
show_if: () => settings().showInlineFavIcon,
|
||||||
|
}
|
||||||
|
|
||||||
export default [disable_np_img, showNowPlayingOnTabTitle];
|
export default [disable_np_img, showNowPlayingOnTabTitle, showInlineFavIcon, highlightFavoriteTracks]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { SettingType } from '../enums'
|
|||||||
import { manageRootDirsStrings as data } from '../strings'
|
import { manageRootDirsStrings as data } from '../strings'
|
||||||
|
|
||||||
import useModalStore from '@/stores/modal'
|
import useModalStore from '@/stores/modal'
|
||||||
import useSettingsStore from '@/stores/settings'
|
import settings from '@/stores/settings'
|
||||||
|
|
||||||
const text = data.settings
|
const text = data.settings
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ const change_root_dirs: Setting = {
|
|||||||
title: text.change,
|
title: text.change,
|
||||||
type: SettingType.button,
|
type: SettingType.button,
|
||||||
state: null,
|
state: null,
|
||||||
button_text: () => `\xa0 \xa0 ${useSettingsStore().root_dirs.length ? 'Modify' : 'Configure'} \xa0 \xa0`,
|
button_text: () => `\xa0 \xa0 ${settings().root_dirs.length ? 'Modify' : 'Configure'} \xa0 \xa0`,
|
||||||
action: () => useModalStore().showRootDirsPromptModal(),
|
action: () => useModalStore().showRootDirsPromptModal(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@ const list_root_dirs: Setting = {
|
|||||||
title: text.list_root_dirs,
|
title: text.list_root_dirs,
|
||||||
type: SettingType.root_dirs,
|
type: SettingType.root_dirs,
|
||||||
state: () =>
|
state: () =>
|
||||||
useSettingsStore().root_dirs.map(d => ({
|
settings().root_dirs.map(d => ({
|
||||||
title: d,
|
title: d,
|
||||||
action: () => {
|
action: () => {
|
||||||
editRootDirs([], [d]).then(all_dirs => {
|
editRootDirs([], [d]).then(all_dirs => {
|
||||||
useSettingsStore().setRootDirs(all_dirs)
|
settings().setRootDirs(all_dirs)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
@@ -32,6 +32,14 @@ const list_root_dirs: Setting = {
|
|||||||
action: () => triggerScan(),
|
action: () => triggerScan(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const show_playlists_in_folders: Setting = {
|
||||||
|
title: 'Show playlists in folder view',
|
||||||
|
desc: 'Browse playlists and favorites in folders screen (meant for mobile app)',
|
||||||
|
type: SettingType.binary,
|
||||||
|
state: () => settings().show_playlists_in_folders,
|
||||||
|
action: () => settings().toggleShowPlaylistsInFolders(),
|
||||||
|
}
|
||||||
|
|
||||||
// const enable_scans: Setting = {
|
// const enable_scans: Setting = {
|
||||||
// title: "Enable periodic scans",
|
// title: "Enable periodic scans",
|
||||||
// type: SettingType.binary,
|
// type: SettingType.binary,
|
||||||
@@ -57,5 +65,6 @@ const list_root_dirs: Setting = {
|
|||||||
export default [
|
export default [
|
||||||
change_root_dirs,
|
change_root_dirs,
|
||||||
list_root_dirs,
|
list_root_dirs,
|
||||||
|
show_playlists_in_folders,
|
||||||
// useWatchdog, enable_scans, periodicScanInterval
|
// useWatchdog, enable_scans, periodicScanInterval
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default defineStore('newModal', {
|
|||||||
showNewPlaylistModal(props: any = {}) {
|
showNewPlaylistModal(props: any = {}) {
|
||||||
this.showModal(ModalOptions.newPlaylist, props)
|
this.showModal(ModalOptions.newPlaylist, props)
|
||||||
},
|
},
|
||||||
showPageModal(props: any = {}) {
|
showCollectionModal(props: any = {}) {
|
||||||
this.showModal(ModalOptions.page, props)
|
this.showModal(ModalOptions.page, props)
|
||||||
},
|
},
|
||||||
showSaveFolderAsPlaylistModal(path: string) {
|
showSaveFolderAsPlaylistModal(path: string) {
|
||||||
|
|||||||
30
src/stores/pages/collections.ts
Normal file
30
src/stores/pages/collections.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Album, Artist, Collection } from '@/interfaces'
|
||||||
|
import { getCollection } from '@/requests/collections'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export default defineStore('collections', {
|
||||||
|
state: () => ({
|
||||||
|
collection: <Collection | null>null,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async fetchCollection(collection_id: string) {
|
||||||
|
this.collection = await getCollection(collection_id)
|
||||||
|
},
|
||||||
|
async removeLocalItem(item: Album | Artist, type: 'album' | 'artist') {
|
||||||
|
if (!this.collection) return
|
||||||
|
|
||||||
|
if (type == 'album') {
|
||||||
|
this.collection.items = this.collection.items.filter(i => {
|
||||||
|
return (i as Album).albumhash != (item as Album).albumhash
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.collection.items = this.collection.items.filter(i => {
|
||||||
|
return (i as Artist).artisthash != (item as Artist).artisthash
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearStore() {
|
||||||
|
this.collection = null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Artist, Album, Page } from '@/interfaces'
|
|
||||||
import { getPage } from '@/requests/pages'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export default defineStore('page', {
|
|
||||||
state: () => ({
|
|
||||||
page: <Page | null>null,
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
async fetchPage(page_no: string) {
|
|
||||||
this.page = await getPage(page_no)
|
|
||||||
},
|
|
||||||
async removeLocalItem(item: Album | Artist, type: 'album' | 'artist') {
|
|
||||||
if (!this.page) return
|
|
||||||
|
|
||||||
if (type == 'album') {
|
|
||||||
this.page.items = this.page.items.filter(i => {
|
|
||||||
return (i as Album).albumhash != (item as Album).albumhash
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.page.items = this.page.items.filter(i => {
|
|
||||||
return (i as Artist).artisthash != (item as Artist).artisthash
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -10,7 +10,7 @@ import useTracklist from './queue/tracklist'
|
|||||||
import useSettings from './settings'
|
import useSettings from './settings'
|
||||||
import useTracker from './tracker'
|
import useTracker from './tracker'
|
||||||
|
|
||||||
import { paths } from '@/config'
|
import { getBaseUrl, paths } from '@/config'
|
||||||
import updateMediaNotif from '@/helpers/mediaNotification'
|
import updateMediaNotif from '@/helpers/mediaNotification'
|
||||||
import { crossFade } from '@/utils/audio/crossFade'
|
import { crossFade } from '@/utils/audio/crossFade'
|
||||||
|
|
||||||
@@ -81,15 +81,12 @@ class AudioSource {
|
|||||||
this.playingSource.pause()
|
this.playingSource.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
async playPlayingSource(
|
async playPlayingSource(trackSilence?: { starting_file: number; ending_file: number }) {
|
||||||
trackSilence?: { starting_file: number; ending_file: number }
|
|
||||||
) {
|
|
||||||
const trackDuration = trackSilence
|
const trackDuration = trackSilence
|
||||||
? Math.floor(trackSilence.ending_file / 1000 - trackSilence.starting_file / 1000)
|
? Math.floor(trackSilence.ending_file / 1000 - trackSilence.starting_file / 1000)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
if(this.requiredAPBlockBypass)
|
if (this.requiredAPBlockBypass) this.applyAPBlockBypass()
|
||||||
this.applyAPBlockBypass()
|
|
||||||
|
|
||||||
await this.playingSource.play().catch(this.handlers.onPlaybackError)
|
await this.playingSource.play().catch(this.handlers.onPlaybackError)
|
||||||
navigator.mediaSession.playbackState = 'playing'
|
navigator.mediaSession.playbackState = 'playing'
|
||||||
@@ -110,11 +107,14 @@ class AudioSource {
|
|||||||
*
|
*
|
||||||
* this workaround plays the `standbySource` along with the `playingSource` to meet the first condition.
|
* this workaround plays the `standbySource` along with the `playingSource` to meet the first condition.
|
||||||
*/
|
*/
|
||||||
private applyAPBlockBypass(){
|
private applyAPBlockBypass() {
|
||||||
this.standbySource.src = ''
|
this.standbySource.src = ''
|
||||||
this.standbySource.play().then(() => {
|
this.standbySource
|
||||||
|
.play()
|
||||||
|
.then(() => {
|
||||||
this.standbySource.pause()
|
this.standbySource.pause()
|
||||||
}).catch(() => {})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
|
||||||
this.requiredAPBlockBypass = false
|
this.requiredAPBlockBypass = false
|
||||||
}
|
}
|
||||||
@@ -127,9 +127,11 @@ export function getUrl(filepath: string, trackhash: string, use_legacy: boolean)
|
|||||||
use_legacy = true
|
use_legacy = true
|
||||||
const { streaming_container, streaming_quality } = useSettings()
|
const { streaming_container, streaming_quality } = useSettings()
|
||||||
|
|
||||||
return `${paths.api.files}/${trackhash + (use_legacy ? '/legacy' : '')}?filepath=${encodeURIComponent(
|
const url = `${paths.api.files}/${trackhash + (use_legacy ? '/legacy' : '')}?filepath=${encodeURIComponent(
|
||||||
filepath
|
filepath
|
||||||
)}&container=${streaming_container}&quality=${streaming_quality}`
|
)}&container=${streaming_container}&quality=${streaming_quality}`
|
||||||
|
|
||||||
|
return getBaseUrl() + url
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioSource = new AudioSource()
|
const audioSource = new AudioSource()
|
||||||
@@ -228,9 +230,12 @@ export const usePlayer = defineStore('player', () => {
|
|||||||
|
|
||||||
const handlePlayErrors = (e: Event | string) => {
|
const handlePlayErrors = (e: Event | string) => {
|
||||||
if (e instanceof DOMException) {
|
if (e instanceof DOMException) {
|
||||||
if(e.name === 'NotAllowedError') {
|
if (e.name === 'NotAllowedError') {
|
||||||
queue.playPause()
|
queue.playPause()
|
||||||
return toast.showNotification('Tap anywhere in the page and try again (autoplay blocked)', NotifType.Error)
|
return toast.showNotification(
|
||||||
|
'Tap anywhere in the page and try again (autoplay blocked)',
|
||||||
|
NotifType.Error
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return toast.showNotification('Player Error: ' + e.message, NotifType.Error)
|
return toast.showNotification('Player Error: ' + e.message, NotifType.Error)
|
||||||
@@ -260,9 +265,9 @@ export const usePlayer = defineStore('player', () => {
|
|||||||
return lyrics.getLyrics()
|
return lyrics.getLyrics()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.use_lyrics_plugin) {
|
// if (!settings.use_lyrics_plugin) {
|
||||||
lyrics.checkExists(queue.currenttrack.filepath, queue.currenttrack.trackhash)
|
// lyrics.checkExists(queue.currenttrack.filepath, queue.currenttrack.trackhash)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
const onAudioCanPlay = () => {
|
const onAudioCanPlay = () => {
|
||||||
@@ -278,12 +283,14 @@ export const usePlayer = defineStore('player', () => {
|
|||||||
const { submitData } = tracker
|
const { submitData } = tracker
|
||||||
submitData()
|
submitData()
|
||||||
|
|
||||||
console.log('audio ended')
|
if (settings.repeat == 'none') {
|
||||||
console.log(nextAudioData)
|
queue.playPause()
|
||||||
|
queue.moveForward()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// INFO: if next audio is not loaded, manually move forward
|
// INFO: if next audio is not loaded, manually move forward
|
||||||
if (nextAudioData.loaded === false) {
|
if (nextAudioData.loaded === false) {
|
||||||
console.log('next audio not loaded')
|
|
||||||
clearNextAudioData()
|
clearNextAudioData()
|
||||||
queue.playNext()
|
queue.playNext()
|
||||||
}
|
}
|
||||||
@@ -343,6 +350,10 @@ export const usePlayer = defineStore('player', () => {
|
|||||||
|
|
||||||
const silence = e.data
|
const silence = e.data
|
||||||
|
|
||||||
|
if (!silence.ending_file){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
nextAudioData.silence.starting_file = silence.starting_file
|
nextAudioData.silence.starting_file = silence.starting_file
|
||||||
currentAudioData.silence.ending_file = silence.ending_file
|
currentAudioData.silence.ending_file = silence.ending_file
|
||||||
nextAudioData.loaded = silence !== null
|
nextAudioData.loaded = silence !== null
|
||||||
@@ -378,7 +389,7 @@ export const usePlayer = defineStore('player', () => {
|
|||||||
currentAudioData.silence = nextAudioData.silence
|
currentAudioData.silence = nextAudioData.silence
|
||||||
currentAudioData.filepath = nextAudioData.filepath
|
currentAudioData.filepath = nextAudioData.filepath
|
||||||
maxSeekPercent.value = 0
|
maxSeekPercent.value = 0
|
||||||
audioSource.playPlayingSource(nextAudioData.silence);
|
audioSource.playPlayingSource(nextAudioData.silence)
|
||||||
|
|
||||||
clearNextAudioData()
|
clearNextAudioData()
|
||||||
queue.moveForward()
|
queue.moveForward()
|
||||||
@@ -389,10 +400,10 @@ export const usePlayer = defineStore('player', () => {
|
|||||||
const initLoadingNextTrackAudio = () => {
|
const initLoadingNextTrackAudio = () => {
|
||||||
const { currentindex } = queue
|
const { currentindex } = queue
|
||||||
const { length } = tracklist
|
const { length } = tracklist
|
||||||
const { repeat_all, repeat_one } = settings
|
const { repeat } = settings
|
||||||
|
|
||||||
// if no repeat && is last track, return
|
// if no repeat && is last track, return
|
||||||
if (currentindex === length - 1 && !repeat_all && !repeat_one) {
|
if (currentindex === length - 1 && repeat == 'none') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,12 +76,12 @@ export default defineStore('Queue', {
|
|||||||
const { tracklist } = useTracklist()
|
const { tracklist } = useTracklist()
|
||||||
const is_last = this.currentindex === tracklist.length - 1
|
const is_last = this.currentindex === tracklist.length - 1
|
||||||
|
|
||||||
if (settings.repeat_one) {
|
if (settings.repeat == 'one') {
|
||||||
this.play(this.currentindex, false)
|
this.play(this.currentindex, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.repeat_all) {
|
if (settings.repeat == 'all') {
|
||||||
this.play(is_last ? 0 : this.currentindex + 1, false)
|
this.play(is_last ? 0 : this.currentindex + 1, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -189,9 +189,9 @@ export default defineStore('Queue', {
|
|||||||
},
|
},
|
||||||
previndex(): number {
|
previndex(): number {
|
||||||
const { tracklist } = useTracklist()
|
const { tracklist } = useTracklist()
|
||||||
const { repeat_one } = useSettings()
|
const { repeat } = useSettings()
|
||||||
|
|
||||||
if (repeat_one) {
|
if (repeat == 'one') {
|
||||||
return this.currentindex
|
return this.currentindex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,9 +199,9 @@ export default defineStore('Queue', {
|
|||||||
},
|
},
|
||||||
nextindex(): number {
|
nextindex(): number {
|
||||||
const { tracklist } = useTracklist()
|
const { tracklist } = useTracklist()
|
||||||
const { repeat_one } = useSettings()
|
const { repeat } = useSettings()
|
||||||
|
|
||||||
if (repeat_one) {
|
if (repeat == 'one') {
|
||||||
return this.currentindex
|
return this.currentindex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,25 +7,9 @@ import useQueue from '@/stores/queue'
|
|||||||
import useSettings from '@/stores/settings'
|
import useSettings from '@/stores/settings'
|
||||||
|
|
||||||
import { FromOptions } from '@/enums'
|
import { FromOptions } from '@/enums'
|
||||||
import {
|
import { fromAlbum, fromArtist, fromFav, fromFolder, fromMix, fromPlaylist, fromSearch, Track } from '@/interfaces'
|
||||||
fromAlbum,
|
|
||||||
fromArtist,
|
|
||||||
fromFav,
|
|
||||||
fromFolder,
|
|
||||||
fromMix,
|
|
||||||
fromPlaylist,
|
|
||||||
fromSearch,
|
|
||||||
Track,
|
|
||||||
} from '@/interfaces'
|
|
||||||
|
|
||||||
export type From =
|
export type From = fromFolder | fromAlbum | fromPlaylist | fromSearch | fromArtist | fromFav | fromMix
|
||||||
| fromFolder
|
|
||||||
| fromAlbum
|
|
||||||
| fromPlaylist
|
|
||||||
| fromSearch
|
|
||||||
| fromArtist
|
|
||||||
| fromFav
|
|
||||||
| fromMix
|
|
||||||
|
|
||||||
function shuffle(tracks: Track[]) {
|
function shuffle(tracks: Track[]) {
|
||||||
const shuffled = tracks.slice()
|
const shuffled = tracks.slice()
|
||||||
@@ -56,12 +40,6 @@ export default defineStore('tracklist', {
|
|||||||
this.tracklist.push(...tracklist)
|
this.tracklist.push(...tracklist)
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = useSettings()
|
|
||||||
|
|
||||||
if (settings.repeat_one) {
|
|
||||||
settings.toggleRepeatMode()
|
|
||||||
}
|
|
||||||
|
|
||||||
const { focusCurrentInSidebar } = useInterface()
|
const { focusCurrentInSidebar } = useInterface()
|
||||||
focusCurrentInSidebar(1000)
|
focusCurrentInSidebar(1000)
|
||||||
usePlayer().clearNextAudio()
|
usePlayer().clearNextAudio()
|
||||||
@@ -95,7 +73,13 @@ export default defineStore('tracklist', {
|
|||||||
|
|
||||||
this.setNewList(tracks)
|
this.setNewList(tracks)
|
||||||
},
|
},
|
||||||
setFromMix(name: string, id: string, tracks: Track[], sourcehash: string, image: { type: 'mix' | 'track', image: string }) {
|
setFromMix(
|
||||||
|
name: string,
|
||||||
|
id: string,
|
||||||
|
tracks: Track[],
|
||||||
|
sourcehash: string,
|
||||||
|
image: { type: 'mix' | 'track'; image: string }
|
||||||
|
) {
|
||||||
this.from = <fromMix>{
|
this.from = <fromMix>{
|
||||||
type: FromOptions.mix,
|
type: FromOptions.mix,
|
||||||
name: name,
|
name: name,
|
||||||
@@ -137,10 +121,7 @@ export default defineStore('tracklist', {
|
|||||||
this.insertAt(tracks, this.tracklist.length)
|
this.insertAt(tracks, this.tracklist.length)
|
||||||
|
|
||||||
const Toast = useToast()
|
const Toast = useToast()
|
||||||
Toast.showNotification(
|
Toast.showNotification(`Added ${tracks.length} tracks to queue`, NotifType.Success)
|
||||||
`Added ${tracks.length} tracks to queue`,
|
|
||||||
NotifType.Success
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
insertAt(tracks: Track[], index: number) {
|
insertAt(tracks: Track[], index: number) {
|
||||||
this.tracklist.splice(index, 0, ...tracks)
|
this.tracklist.splice(index, 0, ...tracks)
|
||||||
@@ -160,14 +141,7 @@ export default defineStore('tracklist', {
|
|||||||
this.tracklist = shuffle(this.tracklist)
|
this.tracklist = shuffle(this.tracklist)
|
||||||
},
|
},
|
||||||
removeByIndex(index: number) {
|
removeByIndex(index: number) {
|
||||||
const {
|
const { currentindex, nextindex, playing, playNext, moveForward, setCurrentIndex } = useQueue()
|
||||||
currentindex,
|
|
||||||
nextindex,
|
|
||||||
playing,
|
|
||||||
playNext,
|
|
||||||
moveForward,
|
|
||||||
setCurrentIndex,
|
|
||||||
} = useQueue()
|
|
||||||
const player = usePlayer()
|
const player = usePlayer()
|
||||||
|
|
||||||
if (this.tracklist.length == 1) {
|
if (this.tracklist.length == 1) {
|
||||||
@@ -207,10 +181,7 @@ export default defineStore('tracklist', {
|
|||||||
this.tracklist.splice(currentindex + 1, 0, ...tracks)
|
this.tracklist.splice(currentindex + 1, 0, ...tracks)
|
||||||
|
|
||||||
const Toast = useToast()
|
const Toast = useToast()
|
||||||
Toast.showNotification(
|
Toast.showNotification(`Added ${tracks.length} tracks to queue`, NotifType.Success)
|
||||||
`Added ${tracks.length} tracks to queue`,
|
|
||||||
NotifType.Success
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ export default defineStore('search', () => {
|
|||||||
const currentTab = ref('top')
|
const currentTab = ref('top')
|
||||||
const top_results = reactive({
|
const top_results = reactive({
|
||||||
query: '',
|
query: '',
|
||||||
top_result: {
|
top_result: <Track | Album | Artist>{},
|
||||||
type: <null | string>null,
|
|
||||||
item: <Track | Album | Artist>{},
|
|
||||||
},
|
|
||||||
tracks: <Track[]>[],
|
tracks: <Track[]>[],
|
||||||
albums: <Album[]>[],
|
albums: <Album[]>[],
|
||||||
artists: <Artist[]>[],
|
artists: <Artist[]>[],
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { content_width } from '../content-width'
|
|||||||
import { getLastFmApiSig } from '@/context_menus/hashing'
|
import { getLastFmApiSig } from '@/context_menus/hashing'
|
||||||
import useAxios from '@/requests/useAxios'
|
import useAxios from '@/requests/useAxios'
|
||||||
import { paths } from '@/config'
|
import { paths } from '@/config'
|
||||||
|
import { router, Routes } from '@/router'
|
||||||
|
|
||||||
export default defineStore('settings', {
|
export default defineStore('settings', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -17,8 +18,9 @@ export default defineStore('settings', {
|
|||||||
extend_width: false,
|
extend_width: false,
|
||||||
contextChildrenShowMode: contextChildrenShowMode.hover,
|
contextChildrenShowMode: contextChildrenShowMode.hover,
|
||||||
artist_top_tracks_count: 5,
|
artist_top_tracks_count: 5,
|
||||||
repeat_all: true,
|
// repeat_all: true,
|
||||||
repeat_one: false,
|
// repeat_one: false,
|
||||||
|
repeat: <'all' | 'one' | 'none'>'all',
|
||||||
root_dir_set: false,
|
root_dir_set: false,
|
||||||
root_dirs: <string[]>[],
|
root_dirs: <string[]>[],
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ export default defineStore('settings', {
|
|||||||
merge_albums: false,
|
merge_albums: false,
|
||||||
show_albums_as_singles: false,
|
show_albums_as_singles: false,
|
||||||
separators: <string[]>[],
|
separators: <string[]>[],
|
||||||
|
show_playlists_in_folders: false,
|
||||||
|
|
||||||
// client
|
// client
|
||||||
useCircularArtistImg: true,
|
useCircularArtistImg: true,
|
||||||
@@ -59,7 +62,7 @@ export default defineStore('settings', {
|
|||||||
// audio
|
// audio
|
||||||
use_silence_skip: true,
|
use_silence_skip: true,
|
||||||
use_crossfade: false,
|
use_crossfade: false,
|
||||||
crossfade_duration: 2000, // milliseconds
|
crossfade_duration: 1000, // milliseconds
|
||||||
use_legacy_streaming_endpoint: false,
|
use_legacy_streaming_endpoint: false,
|
||||||
|
|
||||||
// layout
|
// layout
|
||||||
@@ -71,6 +74,8 @@ export default defineStore('settings', {
|
|||||||
// stats
|
// stats
|
||||||
statsgroup: 'artists',
|
statsgroup: 'artists',
|
||||||
statsperiod: 'week',
|
statsperiod: 'week',
|
||||||
|
showInlineFavIcon: false,
|
||||||
|
_highlightFavoriteTracks: false,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
mapDbSettings(settings: DBSettings) {
|
mapDbSettings(settings: DBSettings) {
|
||||||
@@ -83,6 +88,7 @@ export default defineStore('settings', {
|
|||||||
this.merge_albums = settings.mergeAlbums
|
this.merge_albums = settings.mergeAlbums
|
||||||
this.separators = settings.artistSeparators
|
this.separators = settings.artistSeparators
|
||||||
this.show_albums_as_singles = settings.showAlbumsAsSingles
|
this.show_albums_as_singles = settings.showAlbumsAsSingles
|
||||||
|
this.show_playlists_in_folders = settings.showPlaylistsInFolderView
|
||||||
|
|
||||||
this.enablePeriodicScans = settings.enablePeriodicScans
|
this.enablePeriodicScans = settings.enablePeriodicScans
|
||||||
this.periodicInterval = settings.scanInterval
|
this.periodicInterval = settings.scanInterval
|
||||||
@@ -104,6 +110,12 @@ export default defineStore('settings', {
|
|||||||
toggleUseNPImg() {
|
toggleUseNPImg() {
|
||||||
this.use_np_img = !this.use_np_img
|
this.use_np_img = !this.use_np_img
|
||||||
},
|
},
|
||||||
|
toggleShowInlineFavIcon() {
|
||||||
|
this.showInlineFavIcon = !this.showInlineFavIcon
|
||||||
|
},
|
||||||
|
toggleHighlightFavoriteTracks() {
|
||||||
|
this._highlightFavoriteTracks = !this._highlightFavoriteTracks
|
||||||
|
},
|
||||||
// sidebar 👇
|
// sidebar 👇
|
||||||
toggleDisableSidebar() {
|
toggleDisableSidebar() {
|
||||||
if (this.is_alt_layout) {
|
if (this.is_alt_layout) {
|
||||||
@@ -128,20 +140,18 @@ export default defineStore('settings', {
|
|||||||
},
|
},
|
||||||
// repeat 👇
|
// repeat 👇
|
||||||
toggleRepeatMode() {
|
toggleRepeatMode() {
|
||||||
if (this.repeat_all) {
|
if (this.repeat == 'all') {
|
||||||
this.repeat_all = false
|
this.repeat = 'one'
|
||||||
this.repeat_one = true
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.repeat_one) {
|
if (this.repeat == 'one') {
|
||||||
this.repeat_one = false
|
this.repeat = 'none'
|
||||||
this.repeat_all = false
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.repeat_all && !this.repeat_one) {
|
if (this.repeat == 'none') {
|
||||||
this.repeat_all = true
|
this.repeat = 'all'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setRootDirs(dirs: string[]) {
|
setRootDirs(dirs: string[]) {
|
||||||
@@ -293,6 +303,10 @@ export default defineStore('settings', {
|
|||||||
'show_albums_as_singles'
|
'show_albums_as_singles'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
async toggleShowPlaylistsInFolders() {
|
||||||
|
return await this.genericToggleSetting('showPlaylistsInFolderView', !this.show_playlists_in_folders, 'show_playlists_in_folders'
|
||||||
|
)
|
||||||
|
},
|
||||||
async setLastfmApiKey(key: string) {
|
async setLastfmApiKey(key: string) {
|
||||||
return await this.genericToggleSetting('lastfmApiKey', key, 'lastfm_api_key')
|
return await this.genericToggleSetting('lastfmApiKey', key, 'lastfm_api_key')
|
||||||
},
|
},
|
||||||
@@ -313,7 +327,6 @@ export default defineStore('settings', {
|
|||||||
},
|
},
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
console.log('res: ', data)
|
|
||||||
|
|
||||||
if (data.status !== 200) {
|
if (data.status !== 200) {
|
||||||
return
|
return
|
||||||
@@ -333,8 +346,6 @@ export default defineStore('settings', {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('res: ', res)
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -368,9 +379,6 @@ export default defineStore('settings', {
|
|||||||
can_extend_width(): boolean {
|
can_extend_width(): boolean {
|
||||||
return this.is_default_layout && xxl.value
|
return this.is_default_layout && xxl.value
|
||||||
},
|
},
|
||||||
no_repeat(): boolean {
|
|
||||||
return !this.repeat_all && !this.repeat_one
|
|
||||||
},
|
|
||||||
crossfade_duration_seconds(): number {
|
crossfade_duration_seconds(): number {
|
||||||
return this.crossfade_duration / 1000
|
return this.crossfade_duration / 1000
|
||||||
},
|
},
|
||||||
@@ -379,6 +387,12 @@ export default defineStore('settings', {
|
|||||||
},
|
},
|
||||||
is_default_layout: state => state.layout === '',
|
is_default_layout: state => state.layout === '',
|
||||||
is_alt_layout: state => state.layout === 'alternate' && content_width.value > 900,
|
is_alt_layout: state => state.layout === 'alternate' && content_width.value > 900,
|
||||||
|
highlightFavoriteTracks(): boolean {
|
||||||
|
return (
|
||||||
|
!router.currentRoute.value.name?.toString().toLowerCase().startsWith('favorite') &&
|
||||||
|
this._highlightFavoriteTracks
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
afterRestore: context => {
|
afterRestore: context => {
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ function getArtistAlbumComponents(): ScrollerItem[] {
|
|||||||
function getAlbumVersionsComponent(): ScrollerItem | null {
|
function getAlbumVersionsComponent(): ScrollerItem | null {
|
||||||
if (album.otherVersions.length == 0) return null
|
if (album.otherVersions.length == 0) return null
|
||||||
|
|
||||||
console.log(album.otherVersions)
|
|
||||||
return {
|
return {
|
||||||
id: 'otherVersions',
|
id: 'otherVersions',
|
||||||
component: CardScroller,
|
component: CardScroller,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<CardGridPage :items="page.page?.items || []">
|
<CardGridPage :items="collection.collection?.items || []">
|
||||||
<template #header>
|
<template #header>
|
||||||
<GenericHeader>
|
<GenericHeader v-if="collection.collection?.id">
|
||||||
<template #name>
|
<template #name>
|
||||||
<span @click="updatePage">
|
<span @click="updatePage">
|
||||||
{{ page.page?.name }} <span><PencilSvg height="0.8rem" width="0.8rem" /></span
|
{{ collection.collection?.name }} <span><PencilSvg height="0.8rem" width="0.8rem" /></span
|
||||||
></span>
|
></span>
|
||||||
</template>
|
</template>
|
||||||
<template #description v-if="page.page?.extra.description">
|
<template #description v-if="collection.collection?.extra.description">
|
||||||
<span @click="updatePage"> {{ page.page?.extra.description }} </span>
|
<span @click="updatePage"> {{ collection.collection?.extra.description }} </span>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<button @click="deletePage"><DeleteSvg height="1.2rem" width="1.2rem" /> Delete</button>
|
<button @click="deletePage"><DeleteSvg height="1.2rem" width="1.2rem" /> Delete</button>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { onBeforeUnmount, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import DeleteSvg from '@/assets/icons/delete.svg'
|
import DeleteSvg from '@/assets/icons/delete.svg'
|
||||||
@@ -28,30 +28,33 @@ import GenericHeader from '@/components/shared/GenericHeader.vue'
|
|||||||
import CardGridPage from '@/views/SearchView/CardGridPage.vue'
|
import CardGridPage from '@/views/SearchView/CardGridPage.vue'
|
||||||
|
|
||||||
import useModal from '@/stores/modal'
|
import useModal from '@/stores/modal'
|
||||||
import usePage from '@/stores/pages/page'
|
import useCollection from '@/stores/pages/collections'
|
||||||
|
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
const page = usePage()
|
const collection = useCollection()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const page_id = route.params.page as string
|
const collection_id = route.params.collection as string
|
||||||
page.fetchPage(page_id)
|
collection.fetchCollection(collection_id)
|
||||||
})
|
})
|
||||||
|
|
||||||
function updatePage() {
|
function updatePage() {
|
||||||
console.log('update page')
|
modal.showCollectionModal({
|
||||||
modal.showPageModal({
|
collection: collection.collection,
|
||||||
page: page.page,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function deletePage() {
|
function deletePage() {
|
||||||
modal.showPageModal({
|
modal.showCollectionModal({
|
||||||
page: page.page,
|
collection: collection.collection,
|
||||||
delete: true,
|
delete: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
collection.clearStore()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -26,14 +26,14 @@
|
|||||||
<br />
|
<br />
|
||||||
<CardScroller
|
<CardScroller
|
||||||
v-if="favAlbums.length"
|
v-if="favAlbums.length"
|
||||||
:items="favAlbums.map((i) => ({ type: 'album', item: i }))"
|
:items="favAlbums.map(i => ({ type: 'album', item: i }))"
|
||||||
:title="'Albums'"
|
:title="'Albums'"
|
||||||
:route="'/favorites/albums'"
|
:route="'/favorites/albums'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CardScroller
|
<CardScroller
|
||||||
v-if="favArtists.length"
|
v-if="favArtists.length"
|
||||||
:items="favArtists.map((i) => ({ type: 'artist', item: i }))"
|
:items="favArtists.map(i => ({ type: 'artist', item: i }))"
|
||||||
:title="'Artists'"
|
:title="'Artists'"
|
||||||
:route="'/favorites/artists'"
|
:route="'/favorites/artists'"
|
||||||
/>
|
/>
|
||||||
@@ -43,60 +43,60 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick, onMounted, Ref, ref } from "vue";
|
import { nextTick, onMounted, Ref, ref } from 'vue'
|
||||||
|
|
||||||
import { maxAbumCards, updateCardWidth } from "@/stores/content-width";
|
import { maxAbumCards, updateCardWidth } from '@/stores/content-width'
|
||||||
|
|
||||||
import { dropSources, playSources } from "@/enums";
|
import { dropSources, playSources } from '@/enums'
|
||||||
import { playFromFavorites } from "@/helpers/usePlayFrom";
|
import { playFromFavorites } from '@/helpers/usePlayFrom'
|
||||||
import { Album, Artist, RecentFavResult, Track } from "@/interfaces";
|
import { Album, Artist, RecentFavResult, Track } from '@/interfaces'
|
||||||
import { getAllFavs } from "@/requests/favorite";
|
import { getAllFavs } from '@/requests/favorite'
|
||||||
import updatePageTitle from "@/utils/updatePageTitle";
|
import updatePageTitle from '@/utils/updatePageTitle'
|
||||||
|
|
||||||
import HeartSvg from "@/assets/icons/heart-no-color.svg";
|
import HeartSvg from '@/assets/icons/heart-no-color.svg'
|
||||||
import TopTracks from "@/components/ArtistView/TopTracks.vue";
|
import TopTracks from '@/components/ArtistView/TopTracks.vue'
|
||||||
import CardScroller from "@/components/shared/CardScroller.vue";
|
import CardScroller from '@/components/shared/CardScroller.vue'
|
||||||
import GenericHeader from "@/components/shared/GenericHeader.vue";
|
import GenericHeader from '@/components/shared/GenericHeader.vue'
|
||||||
import NoItems from "@/components/shared/NoItems.vue";
|
import NoItems from '@/components/shared/NoItems.vue'
|
||||||
|
|
||||||
const description = `You can add tracks, albums and artists to your favorites by clicking the heart icon`;
|
const description = `You can add tracks, albums and artists to your favorites by clicking the heart icon`
|
||||||
|
|
||||||
const recentFavs: Ref<RecentFavResult[]> = ref([]);
|
const recentFavs: Ref<RecentFavResult[]> = ref([])
|
||||||
const favAlbums: Ref<Album[]> = ref([]);
|
const favAlbums: Ref<Album[]> = ref([])
|
||||||
const favTracks: Ref<Track[]> = ref([]);
|
const favTracks: Ref<Track[]> = ref([])
|
||||||
const favArtists: Ref<Artist[]> = ref([]);
|
const favArtists: Ref<Artist[]> = ref([])
|
||||||
const count = ref({
|
const count = ref({
|
||||||
albums: 0,
|
albums: 0,
|
||||||
tracks: 0,
|
tracks: 0,
|
||||||
artists: 0,
|
artists: 0,
|
||||||
});
|
})
|
||||||
const noFavs = ref(false);
|
const noFavs = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updatePageTitle("Favorites");
|
updatePageTitle('Favorites')
|
||||||
const max = maxAbumCards.value;
|
const max = maxAbumCards.value
|
||||||
|
|
||||||
getAllFavs(6, max, max)
|
getAllFavs(6, max, max)
|
||||||
.then((favs) => {
|
.then(favs => {
|
||||||
recentFavs.value = favs.recents;
|
recentFavs.value = favs.recents
|
||||||
favAlbums.value = favs.albums;
|
favAlbums.value = favs.albums
|
||||||
favTracks.value = favs.tracks;
|
favTracks.value = favs.tracks
|
||||||
favArtists.value = favs.artists;
|
favArtists.value = favs.artists
|
||||||
count.value = favs.count;
|
count.value = favs.count
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
noFavs.value = !favAlbums.value.length && !favTracks.value.length && !favArtists.value.length;
|
noFavs.value = !favAlbums.value.length && !favTracks.value.length && !favArtists.value.length
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await nextTick();
|
await nextTick()
|
||||||
updateCardWidth();
|
updateCardWidth()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
function handlePlay(index: number) {
|
function handlePlay(index: number) {
|
||||||
const track = favTracks.value[index];
|
const track = favTracks.value[index]
|
||||||
if (!track) return;
|
if (!track) return
|
||||||
playFromFavorites(track);
|
playFromFavorites(track)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<PageItem
|
<PageItem
|
||||||
v-for="item in home.homepageItems"
|
v-for="item in home.homepageItems"
|
||||||
|
:key="item.path"
|
||||||
:title="item.title || ''"
|
:title="item.title || ''"
|
||||||
:description="item.description"
|
:description="item.description"
|
||||||
:items="item.items"
|
:items="item.items"
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const scrollerItems = computed(() => {
|
|||||||
props: {
|
props: {
|
||||||
items: props.items.slice(i * maxCards, (i + 1) * maxCards),
|
items: props.items.slice(i * maxCards, (i + 1) * maxCards),
|
||||||
},
|
},
|
||||||
|
key: i,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ const scrollerItems = computed(() => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
if (search.tracks.more) {
|
if (search.tracks.more) {
|
||||||
console.log('more tracks')
|
|
||||||
items.push({
|
items.push({
|
||||||
// set to random to force re-render
|
// set to random to force re-render
|
||||||
id: Math.random(),
|
id: Math.random(),
|
||||||
|
|||||||
Reference in New Issue
Block a user