Implement fuzzy page search using fuse.js (#86)

This commit is contained in:
Mungai Njoroge 2022-09-10 10:46:45 -04:00 committed by GitHub
parent befdf383b6
commit 5770a66d67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 568 additions and 558 deletions

View File

@ -11,7 +11,9 @@
"dependencies": {
"@popperjs/core": "^2.11.6",
"@vueuse/core": "^8.5.0",
"@vueuse/integrations": "^9.2.0",
"axios": "^0.26.1",
"fuse.js": "^6.6.2",
"pinia": "^2.0.17",
"pinia-plugin-persistedstate": "^2.1.1",
"sass": "^1.49.0",

View File

@ -64,7 +64,12 @@ router.afterEach(() => {
(document.getElementById("acontent") as HTMLElement).scrollTo(0, 0);
});
onStartTyping(() => {
onStartTyping((e) => {
// if control is pressed return
if (e.ctrlKey) {
console.log("ctrl pressed");
};
const elem = document.getElementById("globalsearch") as HTMLInputElement;
elem.focus();
elem.value = "";

View File

@ -16,6 +16,8 @@
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
width: fit-content;
max-width: 100%;
}
.rounded {

View File

@ -1,57 +0,0 @@
.controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
.shuffle {
width: 100%;
display: flex;
align-items: center;
& * {
height: 2rem;
width: 2rem;
background-size: 70%;
cursor: pointer;
border-radius: 0.5rem;
&:hover {
background-color: rgb(170, 50, 50);
}
}
& :first-child {
background-image: url(../../assets/icons/repeat.svg);
}
& :last-child {
background-image: url(../../assets/icons/shuffle.svg);
}
}
.fav {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
& * {
height: 2rem;
width: 2rem;
background-size: 70%;
border-radius: 0.5rem;
cursor: pointer;
&:hover {
background-color: rgb(170, 50, 50);
}
}
& :first-child {
background-image: url(../../assets/icons/plus.svg);
}
& :last-child {
background-image: url(../../assets/icons/heart.svg);
}
}
}

View File

@ -11,6 +11,15 @@
box-sizing: border-box;
}
html {
cursor: default !important;
}
html.loading,
html.loading * {
cursor: progress !important;
}
body {
background-color: $body;
color: $white;
@ -27,4 +36,8 @@ body {
width: 100%;
margin: 0 auto;
}
a {
cursor: default !important;
}
}

View File

@ -17,7 +17,6 @@
justify-content: center;
user-select: none;
cursor: pointer;
transition: all 0.3s ease;
padding: 0 $small;

View File

@ -3,12 +3,17 @@
class="a-header rounded"
ref="albumheaderthing"
:style="{
backgroundImage: `linear-gradient(
backgroundImage: album.colors
? `linear-gradient(
37deg, ${album.colors[0]}, ${album.colors[3]}
)`,
)`
: '',
}"
>
<div class="info" :class="{ nocontrast: isLight(album.colors[0]) }">
<div
class="info"
:class="{ nocontrast: album.colors ? isLight(album.colors[0]) : false }"
>
<div class="art">
<img
:src="imguri.artist + album.artistimg"
@ -31,8 +36,11 @@
</div>
<div class="bottom">
<div class="stats">
{{ album.artist }} {{ album.date }} {{ album.count }} Tracks
{{ formatSeconds(album.duration, true) }}
<div class="border rounded-sm pad-sm">
{{ album.artist }} {{ album.date }} {{ album.count }}
{{ album.count === 1 ? "Track" : "Tracks" }}
{{ formatSeconds(album.duration, true) }}
</div>
</div>
<PlayBtnRect
:source="playSources.album"
@ -62,9 +70,8 @@ import { getButtonColor, isLight } from "../../composables/colors/album";
import PlayBtnRect from "../shared/PlayBtnRect.vue";
const props = defineProps<{
defineProps<{
album: AlbumInfo;
bio: string | null;
}>();
const emit = defineEmits<{
@ -136,12 +143,14 @@ useVisibility(albumheaderthing, handleVisibilityState);
.top {
.h {
font-size: 14px;
opacity: 0.5;
}
.title {
font-size: 2.5rem;
font-weight: 600;
width: fit-content;
cursor: text;
}
.artist {
@ -157,15 +166,19 @@ useVisibility(albumheaderthing, handleVisibilityState);
margin-top: $smaller;
.stats {
border-radius: $small;
font-weight: bold;
font-size: 0.8rem;
margin-bottom: 0.75rem;
cursor: text;
div {
width: fit-content;
}
}
}
}
// grid-template-columns: 1fr !important;
@include for-desktop-down {
.art > img {
height: 6rem;

View File

@ -135,7 +135,7 @@ export default {
padding: $small 0.95rem $small 0.95rem;
margin: $smaller;
transition: all 0.2s ease-in-out;
cursor: pointer;
cursor: default;
float: left;
.play {
@ -146,7 +146,7 @@ export default {
width: 3rem;
background: url(../../assets/icons/play.svg) no-repeat center;
background-size: 60%;
cursor: pointer;
cursor: default;
opacity: 0;
transition: all 0.5s ease-in-out;
}
@ -208,7 +208,7 @@ export default {
input::-webkit-search-cancel-button {
position: relative;
right: 20px;
cursor: pointer;
cursor: default;
width: 50px;
height: 50px;
}

View File

@ -74,7 +74,7 @@ export default {
align-items: center;
grid-template-columns: 7.5rem 1fr;
padding: $small;
cursor: pointer;
cursor: default;
transition: all 0.2s ease-in-out;
&:hover {

View File

@ -68,7 +68,7 @@ export default {
padding: $small 0.95rem $small 0.95rem;
margin: $smaller;
transition: all 0.2s ease-in-out;
cursor: pointer;
cursor: default;
float: left;
&:hover {
@ -116,7 +116,7 @@ export default {
input::-webkit-search-cancel-button {
position: relative;
right: 20px;
cursor: pointer;
cursor: default;
width: 50px;
height: 50px;
}

View File

@ -70,7 +70,7 @@ export default {
align-items: center;
grid-template-columns: 7.5rem 1fr;
padding: $small;
cursor: pointer;
cursor: default;
transition: all 0.2s ease-in-out;
&:hover {

View File

@ -32,7 +32,6 @@ defineProps<{
width: 100%;
display: flex;
align-items: center;
cursor: default;
padding: 0.4rem 1rem;
position: relative;

View File

@ -16,11 +16,17 @@
</div>
<div class="songlist">
<SongItem
v-for="(track, index) in getTrackList()"
v-for="(track, index) in tracks"
:key="track.trackid"
:track="track"
:index="track.index"
@updateQueue="updateQueue(index)"
:index="
on_album_page
? track.tracknumber
: track.index !== undefined
? track.index + 1
: index + 1
"
@playThis="updateQueue(track.index !== undefined ? track.index : index)"
:isPlaying="queue.playing"
:isCurrent="queue.currentid == track.trackid"
/>
@ -37,7 +43,7 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { onUpdated, ref } from "vue";
import { useElementSize } from "@vueuse/core";
import { computed } from "@vue/reactivity";
@ -59,6 +65,10 @@ const props = defineProps<{
copyright?: string | null;
}>();
// onUpdated(() => {
// console.log(props.tracks[1].index);
// });
const emit = defineEmits<{
(e: "playFromPage", index: number): void;
}>();
@ -79,11 +89,11 @@ function updateQueue(index: number) {
}
/**
* Used to show track numbers as indexes in the album page.
* Used to show handle track indexes.
*/
function getTrackList() {
if (props.on_album_page) {
let tracks = props.tracks.map((track) => {
const tracks = props.tracks.map((track) => {
track.index = track.tracknumber;
return track;
});
@ -91,12 +101,7 @@ function getTrackList() {
return tracks;
}
const tracks = props.tracks.map((track, index) => {
track.index = index + 1;
return track;
});
return tracks;
return props.tracks;
}
</script>

View File

@ -34,7 +34,6 @@ const q = useQStore();
height: 2.5rem;
width: 100%;
background-size: 1.5rem !important;
cursor: pointer;
&:hover {
background-color: $darkestblue;

View File

@ -25,34 +25,26 @@
</router-link>
<div class="bottom">
<div class="title ellip">{{ props.track?.title }}</div>
<div
class="artists ellip"
v-if="track?.artists && track?.artists[0] !== ''"
>
<span v-for="artist in putCommas(track.artists)" :key="artist">{{
artist
}}</span>
</div>
<div class="artists" v-else-if="track?.artists">
<span>{{ track.albumartist }}</span>
</div>
<div class="artists" v-else>
<span>Meh</span>
</div>
<div class="title ellip t-center">{{ props.track?.title }}</div>
<ArtistName
:artists="track?.artists || ['Artist']"
:albumartist="track?.albumartist"
class="artists"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { putCommas } from "@/utils";
import { paths } from "../../../config";
import { Track } from "../../../interfaces";
const imguri = paths.images.thumb;
import ArtistName from "@/components/shared/ArtistName.vue";
const props = defineProps<{
track: Track | null;
}>();
const imguri = paths.images.thumb;
</script>
<style lang="scss">
@ -91,12 +83,14 @@ const props = defineProps<{
.title {
font-weight: 900;
margin: 0 auto;
}
.artists {
font-size: 0.85rem;
opacity: 0.75;
margin: 0 auto;
&:hover {
text-decoration: underline 1px !important;
}

View File

@ -1,5 +1,5 @@
<template>
<div class="l-sidebar">
<div class="l-sidebar noscroll">
<div class="withlogo">
<Logo />
<Navigation />

View File

@ -19,9 +19,9 @@
</div>
</template>
<script setup lang="ts">
import { ref } from "@vue/reactivity";
import ArtistCard from "@/components/shared/ArtistCard.vue";
import { Artist } from "@/interfaces";
import { ref } from "@vue/reactivity";
import ArrowSvg from "../../assets/icons/right-arrow.svg";
defineProps<{
@ -82,7 +82,6 @@ const scrollRight = () => {
.icon {
border-radius: $small;
cursor: pointer;
transition: all 0.5s ease;
background-color: rgb(51, 51, 51);
padding: $smaller;

View File

@ -37,20 +37,17 @@
import { ref } from "vue";
import useNavStore from "@/stores/nav";
import useModalStore from "../../stores/modal";
import pContext from "../../contexts/playlist";
import usePStore from "@/stores/pages/playlist";
import useContextStore from "../../stores/context";
import useModalStore from "../../stores/modal";
import { playSources } from "@/composables/enums";
import { formatSeconds, useVisibility } from "@/utils";
import { paths } from "../../config";
import { Playlist } from "../../interfaces";
import { useVisibility, formatSeconds } from "@/utils";
import { ContextSrc, playSources } from "@/composables/enums";
import PlayBtnRect from "../shared/PlayBtnRect.vue";
const imguri = paths.images.playlist;
const context = useContextStore();
const modal = useModalStore();
const nav = useNavStore();
const playlistheader = ref<HTMLElement | null>(null);
@ -64,10 +61,6 @@ const props = defineProps<{
function editPlaylist() {
modal.showEditPlaylistModal(props.info);
}
function showDropdown(e: any) {
context.showContextMenu(e, pContext(), ContextSrc.PHeader);
}
</script>
<style lang="scss">
@ -155,16 +148,7 @@ function showDropdown(e: any) {
font-size: 2.5rem;
font-weight: 900;
text-transform: capitalize;
}
.desc {
overflow: hidden;
text-overflow: ellipsis;
white-space: initial;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
max-width: 50%;
cursor: text;
}
.duration {
@ -173,6 +157,7 @@ function showDropdown(e: any) {
padding: $smaller;
padding-left: 0;
font-weight: 900;
cursor: text;
}
.btns {

View File

@ -1,28 +0,0 @@
<template>
<div
class="p-card new-playlist-card rounded"
@click="Modal.showNewPlaylistModal()"
>
<PlusSvg />
</div>
</template>
<script setup lang="ts">
import PlusSvg from "../../assets/icons/plus.svg";
import useModalStore from "../../stores/modal";
const Modal = useModalStore();
</script>
<style lang="scss">
.new-playlist-card {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
svg {
transform: scale(3);
}
}
</style>

View File

@ -60,7 +60,6 @@ const songs = [
&:hover {
background-color: #3a39393d;
cursor: pointer;
}
}
}

View File

@ -10,10 +10,11 @@
v-for="(t, index) in queue.tracklist"
:key="index"
:track="t"
:index="index + 1"
:index="index"
:isPlaying="queue.playing"
:isHighlighted="false"
:isCurrent="index === queue.currentindex"
:isQueueTrack="true"
@PlayThis="playFromQueue(index)"
/>
</div>

View File

@ -1,150 +0,0 @@
<template>
<div id="playing-from" class="bg-primary rounded" @click="goTo">
<div class="h">
<div class="icon image" :class="from.icon"></div>
Playing from
</div>
<div class="name">
<div id="to">
{{ from.text }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { FromOptions } from "@/composables/enums";
import {
fromAlbum, fromFolder, fromPlaylist,
fromSearch
} from "@/interfaces";
import { computed } from "@vue/reactivity";
import { useRouter } from "vue-router";
const props = defineProps<{
from: fromFolder | fromAlbum | fromPlaylist | fromSearch;
}>();
interface from {
icon: string;
text: string;
}
const from = computed((): from => {
switch (props.from.type) {
case undefined:
return {
icon: "album",
text: "Welcome to Alice",
};
case FromOptions.folder:
return {
icon: "folder",
text: props.from.name,
};
case FromOptions.album:
return {
icon: "album",
text: `${props.from.name} - ${props.from.albumartist}`,
};
case FromOptions.playlist:
return {
icon: "playlist",
text: props.from.name,
};
case FromOptions.search:
return {
icon: "search",
text: `Search results for: "${props.from.query}"`,
};
}
});
const router = useRouter();
function goToAlbum(from: fromAlbum) {
router.push({
name: "AlbumView",
params: {
hash: from.hash,
},
});
}
function goToFolder(from: fromFolder) {
router.push({
name: "FolderView",
params: {
path: from.path,
},
});
}
function goToPlaylist(from: fromPlaylist) {
router.push({
name: "PlaylistView",
params: {
pid: from.playlistid,
},
});
}
function goTo() {
switch (props.from.type) {
case FromOptions.folder:
goToFolder(props.from);
break;
case FromOptions.album:
goToAlbum(props.from);
break;
case FromOptions.playlist:
goToPlaylist(props.from);
break;
}
}
</script>
<style lang="scss">
#playing-from {
background-size: 120%;
padding: 0.75rem;
cursor: pointer;
position: relative;
transition: all 0.2s ease;
background-color: $black;
&:hover {
background-position: -4rem;
}
.name {
font-weight: bolder;
}
.h {
font-size: 0.9rem;
margin-bottom: $small;
display: flex;
align-items: center;
gap: $small;
color: rgba(255, 255, 255, 0.849);
.icon {
height: 1.25rem;
width: 1.25rem;
}
.folder {
background-image: url("../../../assets/icons/folder.fill.svg") !important;
}
.album {
background-image: url("../../../assets/icons/album.svg") !important;
}
.playlist {
background-image: url("../../../assets/icons/playlist.svg") !important;
}
}
}
</style>

View File

@ -50,7 +50,6 @@ function showMenu(e: Event) {
gap: 1rem;
padding: 1rem;
width: 100%;
cursor: pointer;
&:hover {
background-color: $gray4;

View File

@ -8,7 +8,8 @@
:isHighlighted="false"
:isPlaying="queue.playing"
:track="track"
@PlayThis="updateQueue(index)"
@playThis="updateQueue(index)"
:index="index + 1"
/>
</div>
<div v-else class="t-center"><h5>🤷</h5></div>
@ -17,12 +18,13 @@
</template>
<script setup lang="ts">
import { computed } from "vue";
import LoadMore from "./LoadMore.vue";
import TrackItem from "@/components/shared/TrackItem.vue";
import SongItem from "@/components/shared/SongItem.vue";
import useQStore from "../../../stores/queue";
import useSearchStore from "../../../stores/search";
import { computed } from "vue";
import useQStore from "@/stores/queue";
import useSearchStore from "@/stores/search";
const queue = useQStore();
const search = useSearchStore();

View File

@ -5,8 +5,6 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
defineProps<{
state: boolean;
}>();
@ -14,16 +12,16 @@ defineProps<{
<style lang="scss">
.switch {
height: 2rem;
height: 1.5rem;
background-color: $gray;
width: 3.75rem;
width: 2.5rem;
padding: $smaller;
position: relative;
transition: all 0.25s ease;
.circle {
transition: all 0.25s ease;
height: 1.5rem;
height: 1rem;
aspect-ratio: 1;
background-color: $gray1;
position: absolute;
@ -37,7 +35,7 @@ defineProps<{
.circle {
background-color: $white;
left: calc((100% - ($smaller + 1.5rem)));
left: calc((100% - ($smaller + 1rem)));
}
}
</style>

View File

@ -20,7 +20,7 @@ defineProps<{
<style lang="scss">
.settingscontent {
width: 100%;
max-width: 40rem;
// max-width: 40rem;
margin: 0 auto;
}
</style>

View File

@ -1,16 +1,16 @@
<template>
<div class="settingsgroup">
<div>
<h4 v-if="group.title">{{ group.title }}</h4>
<div v-if="group.name || group.desc">
<h4 v-if="group.name">{{ group.name }}</h4>
<div class="desc" v-if="group.desc">{{ group.desc }}</div>
</div>
<div class="setting rounded bg-primary pad-lg">
<div class="setting rounded border pad-lg">
<div
v-for="(setting, index) in group.settings"
:key="index"
:class="{ inactive: setting.inactive && setting.inactive() }"
>
<div class="title">
<div class="title ellip" @click="setting.action()">
{{ setting.title }}
</div>
<div class="options">
@ -69,6 +69,7 @@ defineProps<{
.title {
margin: auto 0;
user-select: none;
}
}
}

View File

@ -1,6 +1,6 @@
<template>
<div class="settingsnav">
<div class="buttongroup rounded bg-primary">
<div class="buttongroup rounded-sm bg-primary">
<button v-for="(group, index) in settingGroups" :key="index">
{{ group.title }}
</button>

View File

@ -108,7 +108,6 @@
button {
padding: 0 $medium;
cursor: pointer;
}
}
}

View File

@ -80,7 +80,6 @@ function hideModal() {
transform: rotate(45deg);
&:hover {
cursor: pointer;
transform: rotate(135deg);
}
}

View File

@ -37,10 +37,10 @@
</template>
<script setup lang="ts">
import usePStore from "@/stores/pages/playlist";
import { onMounted } from "vue";
import { updatePlaylist } from "@/composables/fetch/playlists";
import { Playlist } from "@/interfaces";
import usePStore from "@/stores/pages/playlist";
import { onMounted } from "vue";
const pStore = usePStore();
@ -132,7 +132,6 @@ function update_playlist(e: Event) {
place-items: center;
color: $gray1;
margin: $small 0;
cursor: pointer;
#update-pl-img-preview {
width: 4.5rem;

View File

@ -2,15 +2,19 @@
<div class="topnav">
<div class="left">
<NavButtons />
<div
class="info"
:style="{
overflow: $route.name === Routes.search ? 'visible' : 'hidden',
overflowY: hideOverflow() ? 'visible' : 'hidden',
}"
class="info"
>
<APTitle v-if="showAPTitle" />
<SimpleTitle v-if="$route.name == Routes.settings" :text="'Settings'" />
<Folder v-if="$route.name == Routes.folder" :subPaths="subPaths" />
<APTitle
v-if="$route.name == Routes.album || $route.name == Routes.playlist"
:header_shown="nav.h_visible"
/>
<SettingsTitle v-if="$route.name == Routes.settings" :text="'Settings'" />
<FolderTitle v-if="$route.name == Routes.folder" :subPaths="subPaths" />
<SearchTitle v-if="$route.name == Routes.search" />
<PlaylistsTitle v-if="$route.name == Routes.playlists" />
<QueueTitle v-if="$route.name == Routes.queue" />
@ -26,7 +30,6 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { computed } from "@vue/reactivity";
import { subPath } from "@/interfaces";
import useNavStore from "@/stores/nav";
@ -34,26 +37,26 @@ import { createSubPaths } from "@/utils";
import { Routes } from "@/composables/enums";
import NavButtons from "./NavButtons.vue";
// import Loader from "../shared/Loader.vue";
import Folder from "./Titles/Folder.vue";
import FolderTitle from "./Titles/Folder.vue";
import SimpleTitle from "./Titles/SimpleTitle.vue";
import APTitle from "./Titles/APTitle.vue";
import SearchTitle from "./Titles/SearchTitle.vue";
import PlaylistsTitle from "./Titles/PlaylistsTitle.vue";
import QueueTitle from "./Titles/QueueTitle.vue";
import SettingsTitle from "./Titles/SettingsTitle.vue";
const route = useRoute();
const nav = useNavStore();
const subPaths = ref<subPath[]>([]);
const showAPTitle = computed(() => {
return (
(route.name == Routes.album || route.name == Routes.playlist) &&
!nav.h_visible
);
});
function hideOverflow() {
const { name } = route;
const { album, playlist, search, folder } = Routes;
return (album + playlist + search + folder).includes(name as string);
}
watch(
() => route.name,
@ -88,7 +91,6 @@ watch(
display: grid;
grid-template-columns: 1fr min-content;
width: 100%;
// gap: $small;
.left {
display: grid;

View File

@ -1,19 +1,33 @@
<template>
<div class="title albumnavtitle">
<PlayBtn :source="things.source" :store="things.store" />
<div class="ellip">
{{ things.text }}
<div
class="title grid albumnavtitle"
:class="{
hide_play: header_shown,
}"
>
<div class="first grid">
<PlayBtn :source="things.source" :store="things.store" />
<div class="ellip">
{{ things.text }}
</div>
</div>
<Input :page="($route.name as string)" />
</div>
</template>
<script setup lang="ts">
import { useRoute } from "vue-router";
import { computed } from "@vue/reactivity";
import PlayBtn from "@/components/shared/PlayBtn.vue";
import { playSources, Routes } from "@/composables/enums";
import useAlbumStore from "@/stores/pages/album";
import usePStore from "@/stores/pages/playlist";
import { computed } from "@vue/reactivity";
import { useRoute } from "vue-router";
import Input from "@/components/shared/Input.vue";
defineProps<{
header_shown: boolean;
}>();
const things = computed(() => {
const route = useRoute();
@ -46,10 +60,22 @@ const things = computed(() => {
<style lang="scss">
.albumnavtitle {
display: flex;
grid-template-columns: max-content 1fr;
align-items: center;
justify-content: space-between;
gap: $small;
outline: solid 1px $gray3;
height: 100%;
.first {
grid-template-columns: max-content 1fr;
gap: $small;
align-items: center;
}
}
.albumnavtitle.hide_play {
.first {
visibility: hidden;
}
}
</style>

View File

@ -1,37 +1,43 @@
<template>
<div id="folder-nav-title">
<div class="folder">
<div class="fname">
<div
class="icon image"
@click="
$router.push({ name: Routes.folder, params: { path: '$home' } })
"
></div>
<div class="paths">
<div class="fname-wrapper">
<div class="fname">
<div
class="path"
v-for="path in subPaths.slice(1)"
:key="path.path"
:class="{ inthisfolder: path.active }"
>
<router-link
class="text"
:to="{ name: Routes.folder, params: { path: path.path } }"
>{{ path.name }}</router-link
class="icon image"
@click="
$router.push({ name: Routes.folder, params: { path: '$home' } })
"
></div>
<div class="paths">
<div
class="path"
v-for="path in subPaths.slice(1)"
:key="path.path"
:class="{ inthisfolder: path.active }"
>
<router-link
class="text"
:to="{ name: Routes.folder, params: { path: path.path } }"
>{{ path.name }}</router-link
>
</div>
</div>
</div>
</div>
<div>
<Input :page="Routes.folder" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { focusElem } from "@/utils";
import { subPath } from "@/interfaces";
import { onUpdated } from "vue";
import Input from "@/components/shared/Input.vue";
import { Routes } from "@/composables/enums";
import { subPath } from "@/interfaces";
import { focusElem } from "@/utils";
import { onUpdated } from "vue";
defineProps<{
subPaths: subPath[];
@ -44,24 +50,16 @@ onUpdated(() => {
<style lang="scss">
#folder-nav-title {
overflow: hidden;
width: 100%;
.folder {
display: flex;
display: grid;
grid-template-columns: 1fr max-content;
gap: $small;
.playbtnrect {
height: 2.25rem;
}
.drop-btn {
width: 2.25rem;
.drop-icon {
height: 2.25rem;
width: 2.25rem;
}
.fname-wrapper {
width: 100%;
overflow: auto;
}
.fname {
@ -70,8 +68,9 @@ onUpdated(() => {
height: 2.25rem;
display: flex;
align-items: center;
overflow: auto;
padding-right: $smaller;
width: fit-content;
max-width: 100%;
.icon {
height: 2rem;
@ -79,7 +78,6 @@ onUpdated(() => {
background-image: url("../../../assets/icons/folder.fill.svg");
background-size: 1.5rem;
margin-left: $smaller;
cursor: pointer;
}
.paths {
@ -97,10 +95,6 @@ onUpdated(() => {
white-space: nowrap;
margin: auto 0;
a {
cursor: default;
}
.text {
padding: $smaller;
border-radius: $smaller;

View File

@ -20,10 +20,10 @@ import QueueActions from "@/components/RightSideBar/Queue/QueueActions.vue";
import { FromOptions, Routes } from "@/composables/enums";
import useQueueStore from "@/stores/queue";
import FolderSvg from "@/assets/icons/folder.svg";
import SearchSvg from "@/assets/icons/search.svg";
import AlbumSvg from "@/assets/icons/album.svg";
import FolderSvg from "@/assets/icons/folder.svg";
import PlaylistSvg from "@/assets/icons/playlist.svg";
import SearchSvg from "@/assets/icons/search.svg";
import { RouteLocationRaw } from "vue-router";
@ -120,9 +120,6 @@ const { name, icon: SourceIcon, location } = getSource();
}
}
button {
cursor: pointer;
}
}
.queue-actions {

View File

@ -23,6 +23,7 @@ import SearchInput from "@/components/RightSideBar/SearchInput.vue";
#ginner {
max-width: 30rem;
margin: 0 auto;
border-radius: $small;
}
.buttons {

View File

@ -0,0 +1,18 @@
<template>
<div class="settings-nav">
<SimpleTitle :text="'Settings'" />
<Nav />
</div>
</template>
<script setup lang="ts">
import SimpleTitle from "./SimpleTitle.vue";
import Nav from "@/components/SettingsView/Nav.vue";
</script>
<style lang="scss">
.settings-nav {
display: grid;
grid-template-columns: 1fr max-content;
}
</style>

View File

@ -32,7 +32,6 @@ defineProps<{
border-radius: 0.75rem;
display: grid;
justify-content: center;
cursor: pointer;
.artist-image {
width: 100%;

View File

@ -1,13 +1,13 @@
<template>
<div v-tooltip="returnArtists()" style="width: auto;">
<div class="ellip" v-if="artists[0] !== '' && artists.length > 1">
<div v-tooltip="returnArtists()" style="width: auto">
<div class="ellip" v-if="artists[0] === '' && artists.length === 1">
<span>{{ albumartist }}</span>
</div>
<div class="ellip" v-else>
<span v-for="artist in putCommas(artists)" :key="artist">{{
artist
}}</span>
</div>
<div class="ellip" v-else>
<span>{{ albumartist }}</span>
</div>
</div>
</template>

View File

@ -0,0 +1,59 @@
<template>
<input
type="search"
class="header-input rounded-sm pad-sm"
placeholder="search here"
v-model.trim="source"
id="page-search"
/>
</template>
<script setup lang="ts">
import usePStore from "@/stores/pages/playlist";
import useFolderStore from "@/stores/pages/folder";
import useAlbumStore from "@/stores/pages/album";
import { storeToRefs } from "pinia";
import { Routes } from "@/composables/enums";
const { query: playlistQuery } = storeToRefs(usePStore());
const { query: folderQuery } = storeToRefs(useFolderStore());
const { query: albumQuery } = storeToRefs(useAlbumStore());
const props = defineProps<{
page: Routes | string;
}>();
function getRef() {
switch (props.page) {
case Routes.playlist:
return playlistQuery;
case Routes.folder:
return folderQuery;
case Routes.album:
return albumQuery;
default:
return null;
}
}
const source = getRef();
</script>
<style lang="scss">
.header-input {
background-color: $gray3;
outline: none;
border: none;
color: inherit;
font-size: 1rem;
z-index: 200;
&:focus {
outline: solid;
}
}
</style>

View File

@ -5,7 +5,9 @@
@dblclick="emitUpdate(track)"
@contextmenu.prevent="showMenu"
>
<div class="index t-center ellip">{{ index }}</div>
<div class="index t-center ellip">
{{ index }}
</div>
<div class="flex">
<div @click="emitUpdate(track)" class="thumbnail">
<img
@ -54,6 +56,7 @@
class="options-icon circular"
:class="{ options_button_clicked }"
@click.stop="showMenu"
@dblclick.stop="() => {}"
>
<OptionSvg />
</div>
@ -79,17 +82,17 @@ const artisttitle = ref<HTMLElement | null>(null);
const props = defineProps<{
track: Track;
index?: number;
index: number;
isPlaying: Boolean;
isCurrent: Boolean;
}>();
const emit = defineEmits<{
(e: "updateQueue"): void;
(e: "playThis"): void;
}>();
function emitUpdate(track: Track) {
emit("updateQueue");
emit("playThis");
}
function showMenu(e: Event) {
@ -118,7 +121,7 @@ function showMenu(e: Event) {
.song-album {
max-width: max-content;
cursor: pointer;
cursor: pointer !important;
&:hover {
text-decoration: underline;
@ -128,10 +131,6 @@ function showMenu(e: Event) {
.song-artists {
width: fit-content;
max-width: 100%;
.artist {
cursor: pointer;
}
}
.index {
@ -158,7 +157,6 @@ function showMenu(e: Event) {
svg {
transition: all 0.2s ease-in;
// transform: rotate(90deg);
stroke: $track-btn-svg;
circle {
@ -196,10 +194,6 @@ function showMenu(e: Event) {
left: $small;
top: $small;
}
.title {
cursor: pointer;
}
}
td {

View File

@ -24,14 +24,7 @@
</div>
<hr />
<div class="artist">
<div class="ellip" v-if="track.artists[0] !== ''">
<span v-for="artist in putCommas(track.artists)" :key="artist">{{
artist
}}</span>
</div>
<div class="ellip" v-else>
<span>{{ track.albumartist }}</span>
</div>
<ArtistName :artists="track.artists" :albumartist="track.albumartist" />
</div>
</div>
<div
@ -47,12 +40,12 @@
<script setup lang="ts">
import { ref } from "vue";
import { paths } from "@/config";
import { putCommas } from "@/utils";
import { Track } from "@/interfaces";
import DelSvg from "@/assets/icons/delete.svg";
import { showTrackContextMenu as showContext } from "@/composables/context";
import DelSvg from "@/assets/icons/plus.svg";
import { paths } from "@/config";
import { Track } from "@/interfaces";
import useQueueStore from "@/stores/queue";
import ArtistName from "./ArtistName.vue";
const props = defineProps<{
track: Track;
@ -70,11 +63,11 @@ function showMenu(e: Event) {
}
const emit = defineEmits<{
(e: "PlayThis"): void;
(e: "playThis"): void;
}>();
const playThis = (track: Track) => {
emit("PlayThis");
emit("playThis");
};
</script>
@ -103,7 +96,7 @@ const playThis = (track: Track) => {
.remove-track {
opacity: 0;
transition: all 0.25s ease;
transform: translateX(1rem) rotate(45deg);
transform: scale(0.75) translateY(1rem);
&:hover {
opacity: 1 !important;
@ -113,10 +106,9 @@ const playThis = (track: Track) => {
&:hover {
.remove-track {
opacity: 0.5;
transform: translateX(0) rotate(45deg);
transform: scale(1) translateY(0);
}
cursor: pointer;
background: linear-gradient(37deg, $gray4, $gray3, $gray3);
}
@ -146,6 +138,7 @@ const playThis = (track: Track) => {
.artist {
font-size: small;
opacity: 0.67;
width: fit-content;
}
}
</style>

View File

@ -25,8 +25,14 @@ interface BtnColor {
* @returns {BtnColor} A color to use as the play button background
*/
export function getButtonColor(colors: string[]): BtnColor {
const def = {
color: "#fff",
isDark: true,
};
if (!colors || colors.length === 0) return def;
const base_color = colors[0];
if (colors.length === 0) return { color: "#fff", isDark: true };
for (let i = 0; i < colors.length; i++) {
if (theyContrast(base_color, colors[i])) {
@ -37,10 +43,7 @@ export function getButtonColor(colors: string[]): BtnColor {
}
}
return {
color: "#fff",
isDark: true,
};
return def;
}
/**

View File

@ -37,3 +37,12 @@ export enum Routes {
search = "SearchView",
queue = "QueueView",
}
export const FuseTrackOptions = {
keys: [
{ name: "title", weight: 1 },
{ name: "album", weight: 0.7 },
{ name: "artists", weight: 0.5 },
{ name: "albumartist", weight: 0.25 },
],
};

View File

@ -21,11 +21,7 @@ const getAlbumData = async (hash: string, ToastStore: typeof useNotifStore) => {
if (status == 204) {
ToastStore().showNotification("Album not created yet!", NotifType.Error);
return {
info: {
album: "",
artist: "",
colors: []
},
info: {} as AlbumInfo,
tracks: [],
};
}

View File

@ -2,8 +2,8 @@ import useQStore from "@/stores/queue";
let key_down_fired = false;
function focusSearchBox() {
const elem = document.getElementById("globalsearch");
function focusPageSearchBox() {
const elem = document.getElementById("page-search") as HTMLInputElement;
elem.focus();
}
@ -66,16 +66,11 @@ export default function (queue: typeof useQStore) {
if (!key_down_fired) {
if (!ctrlKey) return;
e.preventDefault();
focusPageSearchBox();
key_down_fired = true;
}
}
case "/": {
{
e.preventDefault();
focusSearchBox();
}
}
}
});
}

View File

@ -24,7 +24,7 @@ export default function play(
store = store as typeof folder;
const f = store();
useQueue.playFromFolder(f.path, f.tracks);
useQueue.playFromFolder(f.path, f.allTracks);
useQueue.play();
break;
case playSources.album:
@ -35,7 +35,7 @@ export default function play(
a_store.info.title,
a_store.info.artist,
a_store.info.hash,
a_store.tracks
a_store.allTracks
);
useQueue.play();
break;

View File

@ -13,9 +13,9 @@ export interface Track {
bitrate?: number;
genre?: string;
image: string;
tracknumber?: number;
discnumber?: number;
index?: number;
tracknumber: number;
discnumber: number;
index: number;
hash: string;
copyright?: string;
}
@ -110,3 +110,10 @@ export interface FetchProps {
put?: boolean;
headers?: {};
}
export interface FuseResult {
item: Track;
refIndex: number;
}

View File

@ -20,7 +20,7 @@ export interface Setting {
}
export interface SettingGroup {
title?: string;
name?: string;
desc?: string;
settings: Setting[];
}

View File

@ -81,7 +81,6 @@ const routes = [
await store
.fetchTracksAndArtists(to.params.hash)
.then(() => store.fetchBio(to.params.hash))
.then(() => {
state.loading.value = false;
});

View File

@ -1,11 +1,12 @@
import { SettingCategory } from "@/interfaces/settings";
import nowPlaying from "./now-playing";
import sidebarSettings from "./sidebar";
export default {
title: "General",
groups: [
{
settings: [...nowPlaying],
settings: [...sidebarSettings, ...nowPlaying],
},
],
} as SettingCategory;

View File

@ -10,11 +10,5 @@ const use_alt_np: Setting = {
inactive: () => settings().disable_show_alt_np,
action: () => settings().toggleUseRightNP(),
};
const use_sidebar: Setting = {
title: "Use right sidebar",
type: SettingType.switch,
source: () => settings().use_sidebar,
action: () => settings().toggleDisableSidebar(),
};
export default [use_sidebar, use_alt_np];
export default [use_alt_np];

View File

@ -0,0 +1,13 @@
import { Setting, SettingType } from "@/interfaces/settings";
import useSettingsStore from "@/stores/settings";
const settings = useSettingsStore;
const use_sidebar: Setting = {
title: "Use right sidebar",
type: SettingType.switch,
source: () => settings().use_sidebar,
action: () => settings().toggleDisableSidebar(),
};
export default [use_sidebar];

View File

@ -4,20 +4,32 @@ export default defineStore("Loader", {
state: () => ({
loading: false,
duration: 0,
page: <HTMLHtmlElement | null>null,
}),
actions: {
startLoading() {
this.loading = true;
this.duration = new Date().getTime();
if (!this.page) {
this.page = document.getElementsByTagName("html")[0] as HTMLHtmlElement;
}
this.page.classList.add("loading");
},
stopLoading() {
const diff = new Date().getTime() - this.duration;
const resetCursor = () => {
this.page ? this.page.classList.remove("loading") : null;
};
if (diff <= 250) {
setTimeout(() => {
resetCursor();
this.loading = false;
}, 250 - diff);
} else {
resetCursor();
this.loading = false;
}
},

View File

@ -1,11 +1,12 @@
import { useFuse } from "@/utils";
import { defineStore } from "pinia";
import { ComputedRef } from "vue";
import { FuseTrackOptions } from "@/composables/enums";
import { getAlbumBio, getAlbumTracks } from "../../composables/fetch/album";
import { AlbumInfo, Artist, FuseResult, Track } from "../../interfaces";
import { useNotifStore } from "../notification";
import { Track, Artist, AlbumInfo } from "../../interfaces";
import {
getAlbumTracks,
getAlbumArtists,
getAlbumBio,
} from "../../composables/fetch/album";
function sortTracks(tracks: Track[]) {
return tracks.sort((a, b) => {
@ -32,9 +33,9 @@ function createDiscs(tracks: Track[]): Discs {
export default defineStore("album", {
state: () => ({
query: "",
info: <AlbumInfo>{},
tracks: <Track[]>[],
discs: <Discs>{},
allTracks: <Track[]>[],
artists: <Artist[]>[],
bio: null,
}),
@ -45,27 +46,38 @@ export default defineStore("album", {
* @param hash title of the album
*/
async fetchTracksAndArtists(hash: string) {
this.tracks = [];
this.allTracks = [];
const album = await getAlbumTracks(hash, useNotifStore);
const artists = await getAlbumArtists(hash);
this.discs = createDiscs(sortTracks(album.tracks));
Object.keys(this.discs).forEach((disc) => {
this.tracks.push(...this.discs[disc]);
const discs = createDiscs(sortTracks(album.tracks));
Object.keys(discs).forEach((disc) => {
this.allTracks.push(...discs[disc]);
});
this.info = album.info;
this.artists = artists;
},
/**
* Fetches the album bio from the server
* @param {string} hash title of the album
*/
fetchBio(hash: string) {
this.bio = null;
getAlbumBio(hash).then((bio) => {
this.bio = bio;
resetQuery() {
this.query = "";
},
},
getters: {
filteredTracks(): ComputedRef<FuseResult[]> {
return useFuse(this.query, this.allTracks, FuseTrackOptions);
},
tracks(): Track[] {
const tracks = this.filteredTracks.value.map((result: FuseResult) => {
const t = {
...result.item,
index: result.refIndex,
};
return t;
});
return tracks;
},
discs(): Discs {
return createDiscs(this.tracks);
},
},
});

View File

@ -1,19 +1,53 @@
import { defineStore } from "pinia";
import { Folder, Track } from "../../interfaces";
import { ComputedRef } from "vue";
import fetchThem from "../../composables/fetch/folders";
import { useFuse } from "@/utils";
import { FuseTrackOptions } from "@/composables/enums";
import fetchThem from "@/composables/fetch/folders";
import { Folder, FuseResult, Track } from "@/interfaces";
export default defineStore("FolderDirs&Tracks", {
state: () => ({
query: "",
path: <string>{},
dirs: <Folder[]>[],
tracks: <Track[]>[],
allDirs: <Folder[]>[],
allTracks: <Track[]>[],
}),
actions: {
async fetchAll(path: string) {
const { tracks, folders } = await fetchThem(path);
[this.path, this.dirs, this.tracks] = [path, folders, tracks];
[this.path, this.allDirs, this.allTracks] = [path, folders, tracks];
},
resetQuery() {
this.query = "";
},
},
getters: {
filteredTracks(): ComputedRef<FuseResult[]> {
return useFuse(this.query, this.allTracks, FuseTrackOptions);
},
tracks(): Track[] {
const tracks = this.filteredTracks.value.map((result: FuseResult) => {
const t = {
...result.item,
index: result.refIndex,
};
return t;
});
return tracks;
},
dirs(): Folder[] {
const dirs = useFuse(this.query, this.allDirs, {
keys: ["name"],
});
return dirs.value.map((result) => {
return result.item;
});
},
},
});

View File

@ -1,15 +1,17 @@
import { Artist } from "./../../interfaces";
import { defineStore } from "pinia";
import {
getPlaylist,
getPlaylistArtists,
} from "../../composables/fetch/playlists";
import { Track, Playlist } from "../../interfaces";
import { ComputedRef } from "vue";
import { useFuse } from "@/utils";
import { FuseTrackOptions } from "@/composables/enums";
import { getPlaylist } from "@/composables/fetch/playlists";
import { Artist, FuseResult, Playlist, Track } from "@/interfaces";
export default defineStore("playlist-tracks", {
state: () => ({
info: <Playlist>{},
tracks: <Track[]>[],
query: "",
allTracks: <Track[]>[],
artists: <Artist[]>[],
}),
actions: {
@ -21,12 +23,12 @@ export default defineStore("playlist-tracks", {
const playlist = await getPlaylist(playlistid);
this.info = playlist?.info || ({} as Playlist);
this.tracks = playlist?.tracks || [];
this.allTracks = playlist?.tracks || [];
},
async fetchArtists(playlistid: string) {
this.artists = await getPlaylistArtists(playlistid);
},
// async fetchArtists(playlistid: string) {
// this.artists = await getPlaylistArtists(playlistid);
// },
/**
* Updates the playlist header info. This is used when the playlist is
* updated.
@ -40,5 +42,26 @@ export default defineStore("playlist-tracks", {
resetArtists() {
this.artists = [];
},
resetQuery() {
this.query = "";
},
},
getters: {
filteredTracks(): ComputedRef<FuseResult[]> {
return useFuse(this.query, this.allTracks, FuseTrackOptions);
},
tracks(): Track[] {
const tracks = this.filteredTracks.value.map((result: FuseResult) => {
const t = {
...result.item,
index: result.refIndex,
};
return t;
});
return tracks;
},
},
});

View File

@ -5,6 +5,7 @@ import formatSeconds from "./useFormatSeconds";
import useDebouncedRef from "./useDebouncedRef";
import createSubPaths from "./useCreateSubPaths";
import { readLocalStorage, writeLocalStorage } from "./useLocalStorage";
import useFuse from "./useFuse";
export {
readLocalStorage,
@ -15,4 +16,5 @@ export {
useVisibility,
formatSeconds,
putCommas,
useFuse,
};

18
src/utils/useFuse.ts Normal file
View File

@ -0,0 +1,18 @@
import { useFuse } from "@vueuse/integrations/useFuse";
import { Ref } from "vue";
/**
* Fuzzy search using Fuse.js
* @param query The query to search for
* @param data The list to search in
* @param fuseOptions Fuse.js options
* @returns A ref containing the search results
*/
export default (query: string, data: any[], fuseOptions: object) => {
const { results } = useFuse(query, data, {
matchAllWhenSearchEmpty: true,
fuseOptions: { ...fuseOptions, threshold: 0.3, ignoreLocation: true },
});
return results;
};

View File

@ -24,7 +24,11 @@
<script setup lang="ts">
import { ref } from "@vue/reactivity";
import { onBeforeRouteUpdate, RouteLocationNormalized } from "vue-router";
import {
onBeforeRouteLeave,
onBeforeRouteUpdate,
RouteLocationNormalized,
} from "vue-router";
import SongList from "@/components/FolderView/SongList.vue";
import FolderList from "@/components/FolderView/FolderList.vue";
@ -47,24 +51,27 @@ function getFolderName(route: RouteLocationNormalized) {
}
function playFromPage(index: number) {
queue.playFromFolder(folder.path, folder.tracks);
queue.playFromFolder(folder.path, folder.allTracks);
queue.play(index);
}
onBeforeRouteUpdate((to, from) => {
if (isSameRoute(to, from)) return;
loader.startLoading();
folder
.fetchAll(to.params.path as string)
.then(() => {
scrollable.value.scrollTop = 0;
folder.resetQuery();
})
.then(() => {
loader.stopLoading();
});
});
onBeforeRouteLeave(() => {
setTimeout(() => folder.resetQuery(), 500);
});
</script>
<style lang="scss">

View File

@ -36,7 +36,7 @@ import Main from "@/components/RightSideBar/Search/Main.vue";
#tracks-results {
margin-right: 1rem;
margin-left: -2.25rem;
// margin-left: -2.25rem;
}
}
</style>

View File

@ -1,28 +1,9 @@
<template>
<div class="settingspage">
<div class="scrollable">
<Content :current="0" />
</div>
<Nav />
<Content :current="0" />
</div>
</template>
<script setup lang="ts">
import Nav from "@/components/SettingsView/Nav.vue";
import Content from "../components/SettingsView/Content.vue";
</script>
<style lang="scss">
.settingspage {
height: 100%;
padding: 1rem;
display: grid;
grid-template-rows: 1fr max-content;
gap: 1rem;
background-color: $black;
.scrollable {
overflow: auto;
}
}
</style>
</script>

View File

@ -37,7 +37,7 @@ const isLastDisc = (disc: string | number) => {
function playFromAlbumPage(index: number) {
const { title, artist, hash } = album.info;
queue.playFromAlbum(title, artist, hash, album.tracks);
queue.playFromAlbum(title, artist, hash, album.allTracks);
queue.play(index);
}
</script>

View File

@ -12,24 +12,26 @@
<script setup lang="ts">
import useAStore from "@/stores/pages/album";
import {
onBeforeRouteLeave,
onBeforeRouteUpdate,
RouteLocationNormalized,
RouteParams,
} from "vue-router";
import Page from "@/layouts/HeaderContentBottom.vue";
import Content from "./Content.vue";
import Header from "./Header.vue";
import Content from "./Content.vue";
import Page from "@/layouts/HeaderContentBottom.vue";
const album = useAStore();
function fetchAlbumBio(params: RouteParams) {
album.fetchBio(params.hash.toString());
}
onBeforeRouteUpdate(async (to: RouteLocationNormalized) => {
await album
.fetchTracksAndArtists(to.params.hash.toString())
.then(() => album.fetchBio(to.params.hash.toString()));
.then(() => album.resetQuery());
});
onBeforeRouteLeave(() => {
setTimeout(() => {
album.resetQuery();
}, 500);
});
</script>

View File

@ -27,8 +27,9 @@ import usePlaylistStore from "@/stores/pages/playlist";
import SongList from "@/components/FolderView/SongList.vue";
import { Track } from "@/interfaces";
import { onUpdated } from "vue";
defineProps<{
const props = defineProps<{
tracks: Track[];
count: number;
name: string;
@ -40,7 +41,7 @@ const playlist = usePlaylistStore();
function playFromPlaylistPage(index: number) {
const { name, playlistid } = playlist.info;
queue.playFromPlaylist(name, playlistid, playlist.tracks);
queue.playFromPlaylist(name, playlistid, playlist.allTracks);
queue.play(index);
}
</script>

View File

@ -1,37 +1,37 @@
<template>
<Page>
<template #header>
<Header :info="playlist.info" />
<Header :info="playlist" />
</template>
<template #content>
<Content
:tracks="playlist.tracks"
:count="playlist.info?.count"
:name="playlist.info.name"
:playlistid="playlist.info.playlistid"
:tracks="tracks"
:count="playlist.count"
:name="playlist.name"
:playlistid="playlist.playlistid"
/>
</template>
</Page>
</template>
<script setup lang="ts">
import { storeToRefs } from "pinia";
import Page from "@/layouts/HeaderContentBottom.vue";
import Header from "@/components/PlaylistView/Header.vue";
import Content from "./Content.vue";
import usePTrackStore from "@/stores/pages/playlist";
import { onMounted, onUnmounted } from "vue";
import { useRoute } from "vue-router";
import usePlaylistStore from "@/stores/pages/playlist";
import { onBeforeRouteLeave } from "vue-router";
const route = useRoute();
const playlist = usePTrackStore();
const store = usePlaylistStore();
const { info: playlist, tracks } = storeToRefs(store);
onMounted(() => {
playlist.fetchArtists(route.params.pid as string);
onBeforeRouteLeave(() => {
setTimeout(() => {
store.resetQuery();
}, 500);
});
onUnmounted(() => playlist.resetArtists());
</script>
<style lang="scss"></style>

View File

@ -87,6 +87,11 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
"@types/web-bluetooth@^0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz#d60330046a6ed8a13b4a53df3813c44942ebdf72"
integrity sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA==
"@vitejs/plugin-vue@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-3.0.1.tgz#b6af8f782485374bbb5fe09edf067a845bf4caae"
@ -187,6 +192,16 @@
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702"
integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==
"@vueuse/core@9.2.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.2.0.tgz#58e3588b9bc5a69010aa9104b00056ee9ebff738"
integrity sha512-/MZ6qpz6uSyaXrtoeBWQzAKRG3N7CvfVWvQxiM3ei3Xe5ydOjjtVbo7lGl9p8dECV93j7W8s63A8H0kFLpLyxg==
dependencies:
"@types/web-bluetooth" "^0.0.15"
"@vueuse/metadata" "9.2.0"
"@vueuse/shared" "9.2.0"
vue-demi "*"
"@vueuse/core@^8.5.0":
version "8.5.0"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-8.5.0.tgz#2b7548e52165c88e1463756c36188e105d806543"
@ -196,11 +211,25 @@
"@vueuse/shared" "8.5.0"
vue-demi "*"
"@vueuse/integrations@^9.2.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.2.0.tgz#fdc53f120ff124e173a9400e634cf5095646eba4"
integrity sha512-0NerkCPUUWnbEb0ZZaJyrO8YKPPClR9+aLLF8yBbG/XRsoEo7pcpVq8d+uMhfHrXABoUpKD+9FZ+Tz/aRb7yFg==
dependencies:
"@vueuse/core" "9.2.0"
"@vueuse/shared" "9.2.0"
vue-demi "*"
"@vueuse/metadata@8.5.0":
version "8.5.0"
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-8.5.0.tgz#1aaa3787922cfda0f38243aaa7779366a669b4db"
integrity sha512-WxsD+Cd+bn+HcjpY6Dl9FJ8ywTRTT9pTwk3bCQpzEhXVYAyNczKDSahk50fCfIJKeWHhyI4B2+/ZEOxQAkUr0g==
"@vueuse/metadata@9.2.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.2.0.tgz#6bf7c9c44b9f5ece405837226a0e04a997994458"
integrity sha512-exN4KE6iquxDCdt72BgEhb3tlOpECtD61AUdXnUqBTIUCl70x1Ar/QXo3bYcvxmdMS2/peQyfeTzBjRTpvL5xw==
"@vueuse/shared@8.5.0":
version "8.5.0"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-8.5.0.tgz#fa01ecd3161933f521dd6428b9ef8015ded1bbd3"
@ -208,6 +237,13 @@
dependencies:
vue-demi "*"
"@vueuse/shared@9.2.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.2.0.tgz#7831051b2c1d01c3413c749468ee53a86024510e"
integrity sha512-NnRp/noSWuXW0dKhZK5D0YLrDi0nmZ18UeEgwXQq7Ul5TTP93lcNnKjrHtd68j2xFB/l59yPGFlCryL692bnrA==
dependencies:
vue-demi "*"
"@webassemblyjs/ast@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
@ -1172,6 +1208,11 @@ functions-have-names@^1.2.2:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
fuse.js@^6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111"
integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598"