mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-05 04:53:01 +00:00
rewrite search tabbing
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
@import "./app-grid.scss", "./controls.scss", "./inputs.scss",
|
@import "./app-grid.scss", "./controls.scss", "./inputs.scss",
|
||||||
"./scrollbars.scss", "./state.scss", "./variants.scss", "./basic.scss";
|
"./scrollbars.scss", "./state.scss", "./variants.scss", "./basic.scss",
|
||||||
|
"./search-tabheaders.scss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--separator: #ffffff46;
|
--separator: #ffffff46;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.tabheaders {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, max-content);
|
||||||
|
justify-content: space-around;
|
||||||
|
margin: 1rem;
|
||||||
|
width: max-content;
|
||||||
|
background: linear-gradient(37deg, $gray1, $gray2, $gray1);
|
||||||
|
height: 2rem;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
border-left: solid 1px $gray3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
padding: 0 $small;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-left: solid 1px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.activetab {
|
||||||
|
background-color: $darkblue;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-left: solid 1px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,9 +38,11 @@ import LoadMore from "./LoadMore.vue";
|
|||||||
|
|
||||||
const search = useSearchStore();
|
const search = useSearchStore();
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
album_grid?: boolean;
|
album_grid?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
console.log(search.albums);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="right-search">
|
<div class="right-search">
|
||||||
<TabsWrapper>
|
<TabsWrapper
|
||||||
<Tab name="tracks">
|
:isOnSearchPage="isOnSearchPage"
|
||||||
<TracksGrid :isOnSearchPage="isOnSearchPage" />
|
:tabs="tabs"
|
||||||
</Tab>
|
@switchTab="switchTab"
|
||||||
<Tab name="albums">
|
:currentTab="currentTab"
|
||||||
<ArtistGrid :album_grid="true" />
|
>
|
||||||
</Tab>
|
<Tab :name="currentTab" :isOnSearchPage="isOnSearchPage" />
|
||||||
<Tab name="artists">
|
|
||||||
<ArtistGrid />
|
|
||||||
</Tab>
|
|
||||||
</TabsWrapper>
|
</TabsWrapper>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ArtistGrid from "./ArtistGrid.vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import Tab from "./Tab.vue";
|
import Tab from "./Tab.vue";
|
||||||
import TabsWrapper from "./TabsWrapper.vue";
|
import TabsWrapper from "./TabsWrapper.vue";
|
||||||
import TracksGrid from "./TracksGrid.vue";
|
import useSearchStore from "@/stores/search";
|
||||||
|
|
||||||
const props = defineProps<{
|
const search = useSearchStore();
|
||||||
|
defineProps<{
|
||||||
isOnSearchPage?: boolean;
|
isOnSearchPage?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const tabs = ["tracks", "albums", "artists"];
|
||||||
|
|
||||||
|
const currentTab = ref("tracks");
|
||||||
|
|
||||||
|
function switchTab(tab: string) {
|
||||||
|
currentTab.value = tab;
|
||||||
|
search.switchTab(tab);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,13 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-show="name == s.currentTab">
|
<component :is="getComponent()?.component" v-bind="getComponent()?.props" />
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useSearchStore from "@/stores/search";
|
import ArtistGrid from "./ArtistGrid.vue";
|
||||||
const s = useSearchStore();
|
import TracksGrid from "./TracksGrid.vue";
|
||||||
defineProps<{
|
|
||||||
|
const props = defineProps<{
|
||||||
name: string;
|
name: string;
|
||||||
|
isOnSearchPage?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
function getComponent() {
|
||||||
|
switch (props.name) {
|
||||||
|
case "tracks":
|
||||||
|
return {
|
||||||
|
component: TracksGrid,
|
||||||
|
props: {
|
||||||
|
isOnSearchPage: props.isOnSearchPage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case "albums":
|
||||||
|
return {
|
||||||
|
component: ArtistGrid,
|
||||||
|
props: {
|
||||||
|
album_grid: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case "artists":
|
||||||
|
return {
|
||||||
|
component: ArtistGrid,
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="right-tabs" class="rounded">
|
<div id="right-tabs" class="rounded">
|
||||||
<div class="tab-buttons-wrapper">
|
<div class="tab-buttons-wrapper">
|
||||||
<div id="tabheaders" class="rounded-sm noscroll">
|
<Teleport :disabled="!isOnSearchPage" to="#nav-tab-headers">
|
||||||
<div
|
<div class="tabheaders rounded-sm noscroll">
|
||||||
class="tab"
|
<div
|
||||||
v-for="slot in $slots.default()"
|
class="tab"
|
||||||
:key="slot.key"
|
v-for="tab in tabs"
|
||||||
@click="s.changeTab(slot.props.name)"
|
:key="tab"
|
||||||
:class="{ activetab: slot.props.name === s.currentTab }"
|
@click="switchTab(tab)"
|
||||||
>
|
:class="{ activetab: tab === currentTab }"
|
||||||
{{ slot.props.name }}
|
>
|
||||||
|
{{ tab }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-content">
|
<div id="tab-content">
|
||||||
@@ -21,9 +23,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useSearchStore from "@/stores/search";
|
defineProps<{
|
||||||
|
isOnSearchPage?: boolean;
|
||||||
|
tabs: string[];
|
||||||
|
currentTab: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
const s = useSearchStore();
|
const emit = defineEmits<{
|
||||||
|
(e: "switchTab", tab: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function switchTab(tab: string) {
|
||||||
|
emit("switchTab", tab);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -38,44 +50,9 @@ const s = useSearchStore();
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tabheaders {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, max-content);
|
|
||||||
justify-content: space-around;
|
|
||||||
margin: 1rem;
|
|
||||||
width: max-content;
|
|
||||||
background: linear-gradient(37deg, $gray1, $gray2, $gray1);
|
|
||||||
height: 2rem;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
border-left: solid 1px $gray3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
padding: 0 $small;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-left: solid 1px transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.activetab {
|
|
||||||
background-color: $darkblue;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border-left: solid 1px transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#tab-content {
|
#tab-content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="tracks-results">
|
<div id="tracks-results" class="noscroll">
|
||||||
<div v-if="search.tracks.value.length">
|
<div v-if="search.tracks.value.length">
|
||||||
<div>
|
<TrackComponent
|
||||||
<TrackComponent
|
v-for="(track, index) in search.tracks.value"
|
||||||
v-for="(track, index) in search.tracks.value"
|
:key="track.trackid"
|
||||||
:key="track.trackid"
|
:isCurrent="queue.currentid == track.trackid"
|
||||||
:isCurrent="queue.currentid == track.trackid"
|
:isHighlighted="false"
|
||||||
:isHighlighted="false"
|
:isPlaying="queue.playing"
|
||||||
:isPlaying="queue.playing"
|
:track="track"
|
||||||
:track="track"
|
@PlayThis="updateQueue(index)"
|
||||||
@PlayThis="updateQueue(index)"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="t-center"><h5>🤷</h5></div>
|
<div v-else class="t-center"><h5>🤷</h5></div>
|
||||||
<LoadMore v-if="search.tracks.more" :loader="search.loadTracks" />
|
<LoadMore v-if="search.tracks.more" :loader="search.loadTracks" />
|
||||||
@@ -52,10 +50,3 @@ if (props.isOnSearchPage) {
|
|||||||
use_song_item = true;
|
use_song_item = true;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.right-search #tracks-results {
|
|
||||||
height: 100% !important;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="nav-search-input">
|
<div class="nav-search-input">
|
||||||
<SearchInput />
|
<SearchInput />
|
||||||
|
<div id="nav-tab-headers"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -10,6 +11,12 @@ import SearchInput from "@/components/RightSideBar/SearchInput.vue";
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.nav-search-input {
|
.nav-search-input {
|
||||||
|
display: flex;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 2rem;
|
||||||
|
|
||||||
#gsearch-input {
|
#gsearch-input {
|
||||||
display: unset;
|
display: unset;
|
||||||
|
|
||||||
@@ -22,5 +29,9 @@ import SearchInput from "@/components/RightSideBar/SearchInput.vue";
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabheaders {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="xartist" :class="{ _is_on_sidebar: alt }">
|
<div class="artist-card" :class="{ _is_on_sidebar: alt }">
|
||||||
<img
|
<img
|
||||||
class="artist-image shadow-sm"
|
class="artist-image shadow-sm"
|
||||||
:src="imguri + artist.image"
|
:src="imguri + artist.image"
|
||||||
@@ -24,7 +24,7 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.xartist {
|
.artist-card {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -41,11 +41,5 @@ defineProps<{
|
|||||||
transition: all 0.5s ease-in-out;
|
transition: all 0.5s ease-in-out;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
// &:hover {
|
|
||||||
// .artist-image {
|
|
||||||
// border-radius: 10%;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { Directive } from "vue";
|
import { Directive } from "vue";
|
||||||
import { createPopper } from "@popperjs/core";
|
import { createPopper } from "@popperjs/core";
|
||||||
|
|
||||||
|
let tooltip: HTMLElement;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
let isHovered = false;
|
let isHovered = false;
|
||||||
const tooltip = document.getElementById("tooltip") as HTMLElement;
|
const tooltip = document.getElementById("tooltip") as HTMLElement;
|
||||||
|
|
||||||
el.addEventListener("mouseenter", () => {
|
el.addEventListener("mouseover", () => {
|
||||||
isHovered = true;
|
isHovered = true;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tooltip.innerText = binding.value;
|
|
||||||
|
|
||||||
if (isHovered) {
|
if (isHovered) {
|
||||||
|
tooltip.innerText = binding.value;
|
||||||
tooltip.style.display = "unset";
|
tooltip.style.display = "unset";
|
||||||
|
|
||||||
createPopper(el, tooltip, {
|
createPopper(el, tooltip, {
|
||||||
@@ -27,16 +28,19 @@ export default {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1500);
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener("mouseleave", () => {
|
el.addEventListener("mouseout", () => {
|
||||||
isHovered = false;
|
isHovered = false;
|
||||||
tooltip.style.display = "none";
|
tooltip.style.display = "none";
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount(el: HTMLElement) {
|
beforeUnmount(el: HTMLElement) {
|
||||||
el.removeEventListener("mouseenter", () => {});
|
const tooltip = document.getElementById("tooltip") as HTMLElement;
|
||||||
el.removeEventListener("mouseleave", () => {});
|
tooltip.style.display = "none";
|
||||||
|
|
||||||
|
el.removeEventListener("mouseover", () => {});
|
||||||
|
el.removeEventListener("mouseout", () => {});
|
||||||
},
|
},
|
||||||
} as Directive;
|
} as Directive;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Routes } from "./../composables/enums";
|
||||||
import { ref, reactive } from "@vue/reactivity";
|
import { ref, reactive } from "@vue/reactivity";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { AlbumInfo, Artist, Playlist, Track } from "../interfaces";
|
import { AlbumInfo, Artist, Playlist, Track } from "../interfaces";
|
||||||
@@ -13,6 +14,7 @@ import { watch } from "vue";
|
|||||||
import useDebouncedRef from "../utils/useDebouncedRef";
|
import useDebouncedRef from "../utils/useDebouncedRef";
|
||||||
import useTabStore from "./tabs";
|
import useTabStore from "./tabs";
|
||||||
import useLoaderStore from "./loader";
|
import useLoaderStore from "./loader";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Scrolls on clicking the loadmore button
|
* Scrolls on clicking the loadmore button
|
||||||
@@ -31,6 +33,7 @@ export default defineStore("search", () => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const query = useDebouncedRef(null, 600);
|
const query = useDebouncedRef(null, 600);
|
||||||
const { startLoading, stopLoading } = useLoaderStore();
|
const { startLoading, stopLoading } = useLoaderStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const currentTab = ref("tracks");
|
const currentTab = ref("tracks");
|
||||||
const RESULT_COUNT = 6;
|
const RESULT_COUNT = 6;
|
||||||
@@ -154,7 +157,7 @@ export default defineStore("search", () => {
|
|||||||
|
|
||||||
const tabs = useTabStore();
|
const tabs = useTabStore();
|
||||||
|
|
||||||
if (tabs.current !== "search") {
|
if (route.name !== Routes.search && tabs.current !== "search") {
|
||||||
tabs.switchToSearch();
|
tabs.switchToSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +205,7 @@ export default defineStore("search", () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function changeTab(tab: string) {
|
function switchTab(tab: string) {
|
||||||
currentTab.value = tab;
|
currentTab.value = tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +220,6 @@ export default defineStore("search", () => {
|
|||||||
loadTracks,
|
loadTracks,
|
||||||
loadAlbums,
|
loadAlbums,
|
||||||
loadArtists,
|
loadArtists,
|
||||||
changeTab,
|
switchTab,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
+15
-2
@@ -13,7 +13,6 @@ import Main from "@/components/RightSideBar/Search/Main.vue";
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.search-view {
|
.search-view {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
// background-color: $black;
|
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
margin-right: -1rem;
|
margin-right: -1rem;
|
||||||
|
|
||||||
@@ -22,8 +21,22 @@ import Main from "@/components/RightSideBar/Search/Main.vue";
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artists-results {
|
||||||
|
margin-right: $small;
|
||||||
|
margin-left: -$small;
|
||||||
|
}
|
||||||
|
|
||||||
.search-results-grid {
|
.search-results-grid {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
|
||||||
|
|
||||||
|
.artist-card {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#tracks-results {
|
||||||
|
margin-right: 1rem;
|
||||||
|
margin-left: -2.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user