mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-05 04:53:01 +00:00
use tabs to seperate search results
This commit is contained in:
@@ -1,152 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="right-search">
|
<div class="right-search">
|
||||||
<Options />
|
<TabsWrapper>
|
||||||
<div class="scrollable rounded" ref="search_thing">
|
<Tab name="Tracks">
|
||||||
<TracksGrid
|
<TracksGrid />
|
||||||
v-if="tracks.tracks.length"
|
</Tab>
|
||||||
:more="tracks.more"
|
<Tab name="Albums">
|
||||||
:tracks="tracks.tracks"
|
<AlbumGrid />
|
||||||
:query="search.query"
|
</Tab>
|
||||||
@loadMore="loadMoreTracks"
|
<Tab name="Artists">
|
||||||
/>
|
<ArtistGrid />
|
||||||
<div class="separator no-border" v-if="tracks.tracks.length"></div>
|
</Tab>
|
||||||
|
</TabsWrapper>
|
||||||
<AlbumGrid
|
|
||||||
v-if="albums.albums.length"
|
|
||||||
:albums="albums.albums"
|
|
||||||
:more="albums.more"
|
|
||||||
@loadMore="loadMoreAlbums"
|
|
||||||
/>
|
|
||||||
<div class="separator no-border" v-if="albums.albums.length"></div>
|
|
||||||
<ArtistGrid
|
|
||||||
v-if="artists.artists.length"
|
|
||||||
:artists="artists.artists"
|
|
||||||
:more="artists.more"
|
|
||||||
@loadMore="loadMoreArtists"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="search.query.trim().length === 0"
|
|
||||||
class="no-res border rounded"
|
|
||||||
>
|
|
||||||
<div class="no-res-text">🦋 Find your music</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else-if="
|
|
||||||
!artists.artists.length &&
|
|
||||||
!tracks.tracks.length &&
|
|
||||||
!albums.albums.length
|
|
||||||
"
|
|
||||||
class="no-res border rounded"
|
|
||||||
>
|
|
||||||
<div class="no-res-text">
|
|
||||||
No results for
|
|
||||||
<span class="highlight rounded">{{ search.query }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from "@vue/reactivity";
|
import { reactive, ref } from "@vue/reactivity";
|
||||||
import state from "../../composables/state";
|
|
||||||
import searchMusic from "@/composables/searchMusic.js";
|
|
||||||
import useDebouncedRef from "@/composables/useDebouncedRef";
|
|
||||||
import AlbumGrid from "@/components/Search/AlbumGrid.vue";
|
|
||||||
import ArtistGrid from "@/components/Search/ArtistGrid.vue";
|
|
||||||
import TracksGrid from "@/components/Search/TracksGrid.vue";
|
|
||||||
import Options from "@/components/Search/Options.vue";
|
|
||||||
import loadMore from "../../composables/loadmore";
|
|
||||||
import useSearchStore from "../../stores/gsearch";
|
import useSearchStore from "../../stores/gsearch";
|
||||||
import useTabStore from "../../stores/tabs";
|
import useTabStore from "../../stores/tabs";
|
||||||
|
import TabsWrapper from "./TabsWrapper.vue";
|
||||||
|
import Tab from "./Tab.vue";
|
||||||
|
import TracksGrid from "./Search/TracksGrid.vue";
|
||||||
|
import AlbumGrid from "./Search/AlbumGrid.vue";
|
||||||
import "@/assets/css/Search/Search.scss";
|
import "@/assets/css/Search/Search.scss";
|
||||||
|
import ArtistGrid from "./Search/ArtistGrid.vue";
|
||||||
|
|
||||||
const search = useSearchStore();
|
const search = useSearchStore();
|
||||||
const tabs = useTabStore();
|
const tabs = useTabStore();
|
||||||
|
|
||||||
const search_thing = ref(null);
|
const search_thing = ref(null);
|
||||||
|
|
||||||
const tracks = reactive({
|
|
||||||
tracks: [],
|
|
||||||
more: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let albums = reactive({
|
|
||||||
albums: [],
|
|
||||||
more: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const artists = reactive({
|
const artists = reactive({
|
||||||
artists: [],
|
artists: [],
|
||||||
more: false,
|
more: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
function scrollSearchThing() {
|
|
||||||
search_thing.value.scroll({
|
|
||||||
top: search_thing.value.scrollTop + 330,
|
|
||||||
left: 0,
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMoreTracks(start) {
|
|
||||||
scrollSearchThing();
|
|
||||||
loadMore.loadMoreTracks(start).then((response) => {
|
|
||||||
tracks.tracks = [...tracks.tracks, ...response.tracks];
|
|
||||||
tracks.more = response.more;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMoreAlbums(start) {
|
|
||||||
loadMore.loadMoreAlbums(start).then((response) => {
|
|
||||||
albums.albums = [...albums.albums, ...response.albums];
|
|
||||||
albums.more = response.more;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMoreArtists(start) {
|
|
||||||
scrollSearchThing();
|
|
||||||
loadMore.loadMoreArtists(start).then((response) => {
|
|
||||||
artists.artists = [...artists.artists, ...response.artists];
|
|
||||||
artists.more = response.more;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
search.$subscribe((mutation, state) => {
|
|
||||||
if (state.query.trim() == "") {
|
|
||||||
tracks.tracks = [];
|
|
||||||
albums.albums = [];
|
|
||||||
artists.artists = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchMusic(state.query).then((res) => {
|
|
||||||
if (tabs.current !== tabs.tabs.search) {
|
|
||||||
tabs.switchToSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
albums.albums = res.albums.albums;
|
|
||||||
albums.more = res.albums.more;
|
|
||||||
|
|
||||||
artists.artists = res.artists.artists;
|
|
||||||
artists.more = res.artists.more;
|
|
||||||
|
|
||||||
tracks.tracks = res.tracks.tracks;
|
|
||||||
tracks.more = res.tracks.more;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.right-search {
|
.right-search {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
|
||||||
grid-template-rows: min-content 1fr;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: $small $small 0 0;
|
|
||||||
|
|
||||||
.no-res {
|
.no-res {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
+12
-19
@@ -1,36 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="albums-results border">
|
<div class="albums-results border">
|
||||||
<div class="heading">Albums</div>
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<AlbumCard v-for="album in albums" :key="album" :album="album" />
|
<AlbumCard
|
||||||
|
v-for="album in search.albums.value"
|
||||||
|
:key="album.image"
|
||||||
|
:album="album"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<LoadMore v-if="more" @loadMore="loadMore" />
|
<LoadMore v-if="search.albums.more" @loadMore="loadMore()" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import AlbumCard from "@/components/shared/AlbumCard.vue";
|
import AlbumCard from "../../shared/AlbumCard.vue";
|
||||||
import LoadMore from "./LoadMore.vue";
|
import LoadMore from "./LoadMore.vue";
|
||||||
|
import useSearchStore from "../../../stores/search";
|
||||||
|
|
||||||
|
const search = useSearchStore();
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ["albums", "more"],
|
|
||||||
components: {
|
|
||||||
AlbumCard,
|
|
||||||
LoadMore,
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
|
||||||
function loadMore() {
|
function loadMore() {
|
||||||
counter += 6;
|
counter += 6;
|
||||||
emit("loadMore", counter);
|
search.loadAlbums(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
loadMore,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div class="artists-results border">
|
||||||
|
<div class="grid">
|
||||||
|
<ArtistCard
|
||||||
|
v-for="artist in search.artists.value"
|
||||||
|
:key="artist.image"
|
||||||
|
:artist="artist"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<LoadMore v-if="search.artists.more" @loadMore="loadMore" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ArtistCard from "../../shared/ArtistCard.vue";
|
||||||
|
import LoadMore from "./LoadMore.vue";
|
||||||
|
import useSearchStore from "../../../stores/search";
|
||||||
|
const search = useSearchStore();
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
function loadMore() {
|
||||||
|
counter += 6;
|
||||||
|
search.loadArtists(counter);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.right-search .artists-results {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: $small;
|
||||||
|
margin-bottom: $small;
|
||||||
|
|
||||||
|
|
||||||
|
.xartist {
|
||||||
|
background-color: $gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+14
-21
@@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tracks-results border" v-if="tracks">
|
<div id="tracks-results" v-if="search.tracks.value">
|
||||||
<div class="heading">Tracks</div>
|
<TransitionGroup name="list">
|
||||||
<TransitionGroup class="items" name="list">
|
|
||||||
<TrackItem
|
<TrackItem
|
||||||
v-for="track in tracks"
|
v-for="track in search.tracks.value"
|
||||||
:key="track.trackid"
|
:key="track.trackid"
|
||||||
:track="track"
|
:track="track"
|
||||||
:isPlaying="queue.playing"
|
:isPlaying="queue.playing"
|
||||||
@@ -12,44 +11,38 @@
|
|||||||
@PlayThis="updateQueue"
|
@PlayThis="updateQueue"
|
||||||
/>
|
/>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
<LoadMore v-if="more" @loadMore="loadMore" />
|
<LoadMore v-if="search.tracks.more" @loadMore="loadMore" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LoadMore from "./LoadMore.vue";
|
import LoadMore from "./LoadMore.vue";
|
||||||
import TrackItem from "../shared/TrackItem.vue";
|
import TrackItem from "../../shared/TrackItem.vue";
|
||||||
import useQStore from "../../stores/queue";
|
import useQStore from "../../../stores/queue";
|
||||||
import { Track } from "../../interfaces";
|
import { Track } from "../../../interfaces";
|
||||||
|
import useSearchStore from "../../../stores/search";
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
|
const search = useSearchStore();
|
||||||
const props = defineProps<{
|
|
||||||
tracks: Track[];
|
|
||||||
more: boolean;
|
|
||||||
query: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits(["loadMore"]);
|
|
||||||
|
|
||||||
function loadMore() {
|
function loadMore() {
|
||||||
counter += 5;
|
counter += 5;
|
||||||
console.log("load more", counter);
|
search.loadTracks(counter);
|
||||||
emit("loadMore", counter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateQueue(track: Track) {
|
function updateQueue(track: Track) {
|
||||||
console.log(props.query);
|
queue.playFromSearch(search.query, search.tracks.value);
|
||||||
queue.playFromSearch(props.query, props.tracks);
|
|
||||||
queue.play(track);
|
queue.play(track);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.right-search .tracks-results {
|
.right-search #tracks-results {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: $small;
|
padding: $small;
|
||||||
|
height: 100% !important;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.list-enter-active,
|
.list-enter-active,
|
||||||
.list-leave-active {
|
.list-leave-active {
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="gsearch-input">
|
<div class="gsearch-input">
|
||||||
<Filters :filters="search.filters" @removeFilter="removeFilter" />
|
|
||||||
<div class="input-loader">
|
<div class="input-loader">
|
||||||
<input
|
<input
|
||||||
id="search"
|
id="search"
|
||||||
@@ -8,36 +7,16 @@
|
|||||||
v-model="search.query"
|
v-model="search.query"
|
||||||
placeholder="Search your library"
|
placeholder="Search your library"
|
||||||
type="text"
|
type="text"
|
||||||
@keyup.backspace="removeLastFilter"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import Filters from "../Search/Filters.vue";
|
import useSearchStore from "../../stores/search";
|
||||||
import Loader from "../shared/Loader.vue";
|
|
||||||
import useSearchStore from "../../stores/gsearch";
|
|
||||||
|
|
||||||
const search = useSearchStore();
|
const search = useSearchStore();
|
||||||
|
|
||||||
function removeFilter(filter) {
|
|
||||||
search.removeFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
function removeLastFilter() {
|
|
||||||
if (search.query === "") {
|
|
||||||
counter++;
|
|
||||||
|
|
||||||
if (counter > 0) {
|
|
||||||
search.removeLastFilter();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
counter = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="name == selectedTab">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { inject } from "vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
name: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const selectedTab = inject("currentTab");
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div id="right-tabs">
|
||||||
|
<div id="tabheaders">
|
||||||
|
<div
|
||||||
|
class="tab rounded"
|
||||||
|
v-for="slot in $slots.default()"
|
||||||
|
:key="slot.key"
|
||||||
|
@click="currentTab = slot.props.name"
|
||||||
|
>
|
||||||
|
{{ slot.props.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="tab-content">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, provide } from "vue";
|
||||||
|
|
||||||
|
const currentTab = ref("Tracks");
|
||||||
|
provide("currentTab", currentTab);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#right-tabs {
|
||||||
|
height: 100%;
|
||||||
|
margin-right: $small;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: min-content 1fr;
|
||||||
|
|
||||||
|
#tabheaders {
|
||||||
|
border: solid 1px rgb(0, 68, 255);
|
||||||
|
display: flex;
|
||||||
|
gap: $small;
|
||||||
|
margin: $small 0;
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
background-color: $gray3;
|
||||||
|
padding: $small;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-content {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: $small;
|
||||||
|
background-color: $gray;
|
||||||
|
// overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="artists-results border">
|
|
||||||
<div class="heading">Artists</div>
|
|
||||||
<div class="grid">
|
|
||||||
<ArtistCard v-for="artist in artists" :key="artist" :artist="artist" />
|
|
||||||
</div>
|
|
||||||
<LoadMore v-if="more" @loadMore="loadMore" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ArtistCard from "@/components/shared/ArtistCard.vue";
|
|
||||||
import LoadMore from "./LoadMore.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ["artists", "more"],
|
|
||||||
components: {
|
|
||||||
ArtistCard,
|
|
||||||
LoadMore,
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
function loadMore() {
|
|
||||||
counter += 6;
|
|
||||||
emit("loadMore", counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
loadMore,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.right-search .artists-results {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: $small;
|
|
||||||
margin-bottom: $small;
|
|
||||||
|
|
||||||
.xartist {
|
|
||||||
background-color: $gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -17,7 +17,7 @@ const imguri = paths.images.artist;
|
|||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
artist: any;
|
artist: any;
|
||||||
color: string;
|
color?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ const playThis = (track: Track) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.track-item {
|
.track-item {
|
||||||
width: 26.55rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
|
|
||||||
const url = "http://127.0.0.1:9876/search/loadmore";
|
|
||||||
|
|
||||||
async function loadMoreTracks(start) {
|
|
||||||
const response = await axios.get(url, {
|
|
||||||
params: {
|
|
||||||
type: "tracks",
|
|
||||||
start: start,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadMoreAlbums(start) {
|
|
||||||
const response = await axios.get(url, {
|
|
||||||
params: {
|
|
||||||
type: "albums",
|
|
||||||
start: start,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadMoreArtists(start) {
|
|
||||||
const response = await axios.get(url, {
|
|
||||||
params: {
|
|
||||||
type: "artists",
|
|
||||||
start: start,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
loadMoreTracks,
|
|
||||||
loadMoreAlbums,
|
|
||||||
loadMoreArtists,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import state from "./state";
|
|
||||||
|
|
||||||
const base_url = `${state.settings.uri}/search?q=`;
|
|
||||||
|
|
||||||
async function search(query) {
|
|
||||||
state.loading.value = true;
|
|
||||||
const url = base_url + encodeURIComponent(query.trim());
|
|
||||||
|
|
||||||
const res = await fetch(url);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const message = `An error has occured: ${res.status}`;
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
state.loading.value = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
tracks: data.data[0],
|
|
||||||
albums: data.data[1],
|
|
||||||
artists: data.data[2],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default search;
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import state from "./state";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const base_url = `${state.settings.uri}/search`;
|
||||||
|
|
||||||
|
const uris = {
|
||||||
|
tracks: `${base_url}/tracks?q=`,
|
||||||
|
albums: `${base_url}/albums?q=`,
|
||||||
|
artists: `${base_url}/artists?q=`,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function search(query: string) {
|
||||||
|
state.loading.value = true;
|
||||||
|
|
||||||
|
const url = base_url + encodeURIComponent(query.trim());
|
||||||
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const message = `An error has occured: ${res.status}`;
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
state.loading.value = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tracks: data.data[0],
|
||||||
|
albums: data.data[1],
|
||||||
|
artists: data.data[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchTracks(query: string) {
|
||||||
|
const url = uris.tracks + encodeURIComponent(query.trim());
|
||||||
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const message = `An error has occured: ${res.status}`;
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchAlbums(query: string) {
|
||||||
|
const url = uris.albums + encodeURIComponent(query.trim());
|
||||||
|
|
||||||
|
const res = await axios.get(url);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchArtists(query: string) {
|
||||||
|
const url = uris.artists + encodeURIComponent(query.trim());
|
||||||
|
|
||||||
|
const res = await axios.get(url);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = state.settings.uri + "/search/loadmore";
|
||||||
|
|
||||||
|
async function loadMoreTracks(index: number) {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
params: {
|
||||||
|
type: "tracks",
|
||||||
|
index: index,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMoreAlbums(index: number) {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
params: {
|
||||||
|
type: "albums",
|
||||||
|
index: index,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMoreArtists(index: number) {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
params: {
|
||||||
|
type: "artists",
|
||||||
|
index: index,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
searchTracks,
|
||||||
|
searchAlbums,
|
||||||
|
searchArtists,
|
||||||
|
loadMoreTracks,
|
||||||
|
loadMoreAlbums,
|
||||||
|
loadMoreArtists,
|
||||||
|
};
|
||||||
@@ -1,33 +1,49 @@
|
|||||||
import {customRef, ref} from 'vue'
|
import { customRef, ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounces a function
|
||||||
|
*
|
||||||
|
* @param {*} fn The function to debounce
|
||||||
|
* @param {*} delay The delay in milliseconds
|
||||||
|
* @param {*} immediate whether to debounce immediately
|
||||||
|
* @returns {Function} The debounced function
|
||||||
|
*/
|
||||||
const debounce = (fn, delay = 0, immediate = false) => {
|
const debounce = (fn, delay = 0, immediate = false) => {
|
||||||
let timeout
|
let timeout;
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
if (immediate && !timeout) fn(...args)
|
if (immediate && !timeout) fn(...args);
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout);
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
fn(...args)
|
fn(...args);
|
||||||
}, delay)
|
}, delay);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const useDebouncedRef = (initialValue, delay, immediate) => {
|
/**
|
||||||
const state = ref(initialValue)
|
* Emits the ref updated value after the given delay.
|
||||||
|
*
|
||||||
|
* @param {*} initialValue The default value of the ref
|
||||||
|
* @param {*} delay The delay in milliseconds
|
||||||
|
* @param {*} immediate Whether to call the function immediately
|
||||||
|
* @returns {Object} The ref and a function to call to update the ref
|
||||||
|
*/
|
||||||
|
const useDebouncedRef = (initialValue, delay, immediate = false) => {
|
||||||
|
const state = ref(initialValue);
|
||||||
return customRef((track, trigger) => ({
|
return customRef((track, trigger) => ({
|
||||||
get() {
|
get() {
|
||||||
track()
|
track();
|
||||||
return state.value
|
return state.value;
|
||||||
},
|
},
|
||||||
set: debounce(
|
set: debounce(
|
||||||
value => {
|
(value) => {
|
||||||
state.value = value
|
state.value = value;
|
||||||
trigger()
|
trigger();
|
||||||
},
|
},
|
||||||
delay,
|
delay,
|
||||||
immediate
|
immediate
|
||||||
),
|
),
|
||||||
}))
|
}));
|
||||||
}
|
};
|
||||||
|
|
||||||
export default useDebouncedRef
|
export default useDebouncedRef;
|
||||||
|
|||||||
+18
-10
@@ -12,7 +12,8 @@ import {
|
|||||||
import notif from "../composables/mediaNotification";
|
import notif from "../composables/mediaNotification";
|
||||||
import { FromOptions } from "../composables/enums";
|
import { FromOptions } from "../composables/enums";
|
||||||
|
|
||||||
function addQToLocalStorage(
|
|
||||||
|
function writeQueue(
|
||||||
from: fromFolder | fromAlbum | fromPlaylist,
|
from: fromFolder | fromAlbum | fromPlaylist,
|
||||||
tracks: Track[]
|
tracks: Track[]
|
||||||
) {
|
) {
|
||||||
@@ -25,11 +26,11 @@ function addQToLocalStorage(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCurrentToLocalStorage(track: Track) {
|
function writeCurrent(track: Track) {
|
||||||
localStorage.setItem("current", JSON.stringify(track));
|
localStorage.setItem("current", JSON.stringify(track));
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCurrentFromLocalStorage(): Track {
|
function readCurrent(): Track {
|
||||||
const current = localStorage.getItem("current");
|
const current = localStorage.getItem("current");
|
||||||
if (current) {
|
if (current) {
|
||||||
return JSON.parse(current);
|
return JSON.parse(current);
|
||||||
@@ -114,7 +115,7 @@ export default defineStore("Queue", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readQueueFromLocalStorage() {
|
readQueue() {
|
||||||
const queue = localStorage.getItem("queue");
|
const queue = localStorage.getItem("queue");
|
||||||
|
|
||||||
if (queue) {
|
if (queue) {
|
||||||
@@ -123,7 +124,7 @@ export default defineStore("Queue", {
|
|||||||
this.tracks = parsed.tracks;
|
this.tracks = parsed.tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateCurrent(readCurrentFromLocalStorage());
|
this.updateCurrent(readCurrent());
|
||||||
},
|
},
|
||||||
updateCurrent(track: Track) {
|
updateCurrent(track: Track) {
|
||||||
this.current = track;
|
this.current = track;
|
||||||
@@ -131,7 +132,7 @@ export default defineStore("Queue", {
|
|||||||
this.updateNext(this.current);
|
this.updateNext(this.current);
|
||||||
this.updatePrev(this.current);
|
this.updatePrev(this.current);
|
||||||
|
|
||||||
addCurrentToLocalStorage(track);
|
writeCurrent(track);
|
||||||
},
|
},
|
||||||
updateNext(track: Track) {
|
updateNext(track: Track) {
|
||||||
const index = this.tracks.findIndex(
|
const index = this.tracks.findIndex(
|
||||||
@@ -161,8 +162,9 @@ export default defineStore("Queue", {
|
|||||||
},
|
},
|
||||||
setNewQueue(tracklist: Track[]) {
|
setNewQueue(tracklist: Track[]) {
|
||||||
if (this.tracks !== tracklist) {
|
if (this.tracks !== tracklist) {
|
||||||
this.tracks = tracklist;
|
this.tracks = [];
|
||||||
addQToLocalStorage(this.from, this.tracks);
|
this.tracks.push(...tracklist);
|
||||||
|
writeQueue(this.from, this.tracks);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
playFromFolder(fpath: string, tracks: Track[]) {
|
playFromFolder(fpath: string, tracks: Track[]) {
|
||||||
@@ -201,7 +203,8 @@ export default defineStore("Queue", {
|
|||||||
},
|
},
|
||||||
addTrackToQueue(track: Track) {
|
addTrackToQueue(track: Track) {
|
||||||
this.tracks.push(track);
|
this.tracks.push(track);
|
||||||
addQToLocalStorage(this.from, this.tracks);
|
writeQueue(this.from, this.tracks);
|
||||||
|
this.updateNext(this.current);
|
||||||
},
|
},
|
||||||
playTrackNext(track: Track) {
|
playTrackNext(track: Track) {
|
||||||
const Toast = useNotifStore();
|
const Toast = useNotifStore();
|
||||||
@@ -209,19 +212,24 @@ export default defineStore("Queue", {
|
|||||||
(t: Track) => t.trackid === this.current.trackid
|
(t: Track) => t.trackid === this.current.trackid
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (currentid == this.tracks.length - 1) {
|
||||||
|
this.tracks.push(track);
|
||||||
|
} else {
|
||||||
const next: Track = this.tracks[currentid + 1];
|
const next: Track = this.tracks[currentid + 1];
|
||||||
|
|
||||||
if (next.trackid === track.trackid) {
|
if (next.trackid === track.trackid) {
|
||||||
Toast.showNotification("Track is already queued", NotifType.Info);
|
Toast.showNotification("Track is already queued", NotifType.Info);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.tracks.splice(currentid + 1, 0, track);
|
this.tracks.splice(currentid + 1, 0, track);
|
||||||
|
this.updateNext(this.current);
|
||||||
Toast.showNotification(
|
Toast.showNotification(
|
||||||
`Added ${track.title} to queue`,
|
`Added ${track.title} to queue`,
|
||||||
NotifType.Success
|
NotifType.Success
|
||||||
);
|
);
|
||||||
addQToLocalStorage(this.from, this.tracks);
|
writeQueue(this.from, this.tracks);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import { ref, reactive } from "@vue/reactivity";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { AlbumInfo, Artist, Track } from "../interfaces";
|
||||||
|
import {
|
||||||
|
searchTracks,
|
||||||
|
searchAlbums,
|
||||||
|
searchArtists,
|
||||||
|
loadMoreTracks,
|
||||||
|
loadMoreAlbums,
|
||||||
|
loadMoreArtists,
|
||||||
|
} from "../composables/searchMusic";
|
||||||
|
import { watch } from "vue";
|
||||||
|
import useDebouncedRef from "../composables/useDebouncedRef";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id The id of the element of the div to scroll
|
||||||
|
* Scrolls on clicking the loadmore button
|
||||||
|
*/
|
||||||
|
function scrollOnLoad(id: string) {
|
||||||
|
const elem = document.getElementById(id);
|
||||||
|
|
||||||
|
elem.scroll({
|
||||||
|
top: elem.scrollHeight,
|
||||||
|
left: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineStore("search", () => {
|
||||||
|
const query = useDebouncedRef("", 600);
|
||||||
|
|
||||||
|
const tracks = reactive({
|
||||||
|
value: <Track[]>[],
|
||||||
|
more: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const albums = reactive({
|
||||||
|
value: <AlbumInfo[]>[],
|
||||||
|
more: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const artists = reactive({
|
||||||
|
value: <Artist[]>[],
|
||||||
|
more: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for tracks, albums and artists
|
||||||
|
* @param query query to search for
|
||||||
|
*/
|
||||||
|
function search(query: string) {
|
||||||
|
searchTracks(query).then((res) => {
|
||||||
|
tracks.value = res.tracks;
|
||||||
|
tracks.more = res.more;
|
||||||
|
});
|
||||||
|
|
||||||
|
searchAlbums(query).then((res) => {
|
||||||
|
albums.value = res.albums;
|
||||||
|
albums.more = res.more;
|
||||||
|
});
|
||||||
|
|
||||||
|
searchArtists(query).then((res) => {
|
||||||
|
artists.value = res.artists;
|
||||||
|
artists.more = res.more;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads more search tracks results
|
||||||
|
*
|
||||||
|
* @param index The starting index of the tracks to load
|
||||||
|
*/
|
||||||
|
function loadTracks(index: number) {
|
||||||
|
loadMoreTracks(index)
|
||||||
|
.then((res) => {
|
||||||
|
tracks.value = [...tracks.value, ...res.tracks];
|
||||||
|
tracks.more = res.more;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
scrollOnLoad("tab-content");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads more search albums results
|
||||||
|
*
|
||||||
|
* @param index The starting index of the albums to load
|
||||||
|
*/
|
||||||
|
function loadAlbums(index: number) {
|
||||||
|
loadMoreAlbums(index)
|
||||||
|
.then((res) => {
|
||||||
|
albums.value = [...albums.value, ...res.albums];
|
||||||
|
albums.more = res.more;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
scrollOnLoad("tab-content");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads more search artists results
|
||||||
|
*
|
||||||
|
* @param index The starting index of the artists to load
|
||||||
|
*/
|
||||||
|
function loadArtists(index: number) {
|
||||||
|
loadMoreArtists(index)
|
||||||
|
.then((res) => {
|
||||||
|
artists.value = [...artists.value, ...res.artists];
|
||||||
|
artists.more = res.more;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
scrollOnLoad("tab-content");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => query.value,
|
||||||
|
(newQuery) => {
|
||||||
|
search(newQuery);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
tracks,
|
||||||
|
albums,
|
||||||
|
artists,
|
||||||
|
query,
|
||||||
|
search,
|
||||||
|
loadTracks,
|
||||||
|
loadAlbums,
|
||||||
|
loadArtists,
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user