mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-09 12:37:22 +00:00
fix layout shift caused by scrollbar
+ append search query to url (to support reload) + fix right sidebar search tabs bug + redesign the load more button on the search page
This commit is contained in:
parent
68f990aada
commit
c1fb05e2a0
@ -32,9 +32,7 @@ $g-border: solid 1px $gray5;
|
|||||||
|
|
||||||
.vue-recycle-scroller {
|
.vue-recycle-scroller {
|
||||||
padding-left: 1.25rem;
|
padding-left: 1.25rem;
|
||||||
// padding-right: 1rem;
|
scrollbar-gutter: stable;
|
||||||
|
|
||||||
// margin-right: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.r-sidebar {
|
.r-sidebar {
|
||||||
@ -62,8 +60,9 @@ $g-border: solid 1px $gray5;
|
|||||||
|
|
||||||
.content-page {
|
.content-page {
|
||||||
margin-left: 1.25rem;
|
margin-left: 1.25rem;
|
||||||
// margin-right: -$medium;
|
margin-right: -$medium;
|
||||||
padding-right: 1.25rem;
|
padding-right: $medium;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== MODIFIERS =======
|
// ====== MODIFIERS =======
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
<router-link
|
<router-link
|
||||||
v-for="menu in menus"
|
v-for="menu in menus"
|
||||||
:key="menu.name"
|
:key="menu.name"
|
||||||
:to="{ name: menu.route_name, params: menu?.params }"
|
:to="{
|
||||||
|
name: menu.route_name,
|
||||||
|
params: menu?.params,
|
||||||
|
query: menu.query && menu.query(),
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="menu.separator"
|
v-if="menu.separator"
|
||||||
@ -34,6 +38,7 @@ import QueueSvg from "../../assets/icons/queue.svg";
|
|||||||
import SearchSvg from "../../assets/icons/search.svg";
|
import SearchSvg from "../../assets/icons/search.svg";
|
||||||
import SettingsSvg from "../../assets/icons/settings.svg";
|
import SettingsSvg from "../../assets/icons/settings.svg";
|
||||||
import HeartSvg from "../../assets/icons/heart.svg";
|
import HeartSvg from "../../assets/icons/heart.svg";
|
||||||
|
import useSearchStore from "@/stores/search";
|
||||||
|
|
||||||
const menus = [
|
const menus = [
|
||||||
{
|
{
|
||||||
@ -65,6 +70,7 @@ const menus = [
|
|||||||
name: "search",
|
name: "search",
|
||||||
route_name: Routes.search,
|
route_name: Routes.search,
|
||||||
params: { page: "tracks" },
|
params: { page: "tracks" },
|
||||||
|
query: () => ({ q: useSearchStore().query }),
|
||||||
icon: SearchSvg,
|
icon: SearchSvg,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -40,6 +40,8 @@ function switchTab(tab: string) {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: max-content 1fr;
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
padding: $medium;
|
padding: $medium;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="nav-search-input">
|
<div class="nav-search-input">
|
||||||
<SearchInput :on_nav="true" />
|
<SearchInput :on_nav="true" />
|
||||||
<TabsWrapper
|
<Tabs
|
||||||
:tabs="tabs"
|
:tabs="tabs"
|
||||||
:currentTab="($route.params.page as string)"
|
:currentTab="($route.params.page as string)"
|
||||||
@switchTab="(tab: string) => {
|
@switchTab="(tab: string) => {
|
||||||
$router.push({ name: Routes.search, params: { page: tab } });
|
$router.replace({ name: Routes.search, params: { page: tab }, query: {
|
||||||
|
q: search.query,
|
||||||
|
} });
|
||||||
search.switchTab(tab);
|
search.switchTab(tab);
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
@ -13,7 +15,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TabsWrapper from "@/components/RightSideBar/Search/TabsWrapper.vue";
|
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
|
||||||
import SearchInput from "@/components/RightSideBar/SearchInput.vue";
|
import SearchInput from "@/components/RightSideBar/SearchInput.vue";
|
||||||
import { Routes } from "@/router/routes";
|
import { Routes } from "@/router/routes";
|
||||||
|
|
||||||
|
@ -49,12 +49,11 @@ async function loadMoreTracks(index: number) {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadMoreAlbums(index: number, count: number) {
|
async function loadMoreAlbums(index: number) {
|
||||||
const response = await axios.get(loadMoreUrl, {
|
const response = await axios.get(loadMoreUrl, {
|
||||||
params: {
|
params: {
|
||||||
type: "albums",
|
type: "albums",
|
||||||
index: index,
|
index: index,
|
||||||
count: count,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { defineStore } from "pinia";
|
|||||||
import { watch } from "vue";
|
import { watch } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { Routes } from "@/router/routes";
|
import { Routes } from "@/router/routes";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
loadMoreAlbums,
|
loadMoreAlbums,
|
||||||
@ -128,11 +129,11 @@ export default defineStore("search", () => {
|
|||||||
.then(() => scrollOnLoad());
|
.then(() => scrollOnLoad());
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAlbums(count = 6) {
|
function loadAlbums() {
|
||||||
loadCounter.albums += count;
|
loadCounter.albums += RESULT_COUNT;
|
||||||
|
|
||||||
startLoading();
|
startLoading();
|
||||||
loadMoreAlbums(loadCounter.albums, count)
|
loadMoreAlbums(loadCounter.albums)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
albums.value = [...albums.value, ...res.albums];
|
albums.value = [...albums.value, ...res.albums];
|
||||||
albums.more = res.more;
|
albums.more = res.more;
|
||||||
@ -165,6 +166,15 @@ export default defineStore("search", () => {
|
|||||||
watch(
|
watch(
|
||||||
() => debouncedQuery.value,
|
() => debouncedQuery.value,
|
||||||
(newQuery) => {
|
(newQuery) => {
|
||||||
|
if (route.name === Routes.search) {
|
||||||
|
router.replace({
|
||||||
|
name: Routes.search,
|
||||||
|
params: {
|
||||||
|
page: route.params.page,
|
||||||
|
},
|
||||||
|
query: { q: newQuery },
|
||||||
|
});
|
||||||
|
}
|
||||||
// reset all counters
|
// reset all counters
|
||||||
for (const key in loadCounter) {
|
for (const key in loadCounter) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -60,8 +60,9 @@ async function handlePlay(index: number) {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.favorites-page {
|
.favorites-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: scroll;
|
overflow: auto;
|
||||||
padding-bottom: 4rem;
|
padding-bottom: 4rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search-albums-view grid-page" v-auto-animate>
|
<div class="search-albums-view grid-page">
|
||||||
<AlbumCard
|
<AlbumCard
|
||||||
v-for="album in search.albums.value"
|
v-for="album in search.albums.value"
|
||||||
:key="album.albumhash"
|
:key="Math.random()"
|
||||||
:album="album"
|
:album="album"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-auto-animate class="search-artists-view grid-page">
|
<div class="grid-page">
|
||||||
<ArtistCard
|
<ArtistCard
|
||||||
v-for="artist in search.artists.value"
|
v-for="artist in search.artists.value"
|
||||||
:key="artist.image"
|
:key="artist.image"
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search-view content-page" style="padding-right: 0;">
|
<div class="search-view content-page" style="padding-right: 0">
|
||||||
<div ref="page" class="page no-scroll" v-auto-animate>
|
<div ref="page" class="page no-scroll" v-auto-animate>
|
||||||
<component :is="component" />
|
<component :is="component" />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
v-if="$route.params.page !== 'tracks'"
|
||||||
class="load-more"
|
class="load-more"
|
||||||
:class="{ 'btn-disabled': !canLoadMore }"
|
:class="{ 'btn-disabled': !canLoadMore }"
|
||||||
@click="loadMore"
|
@click="canLoadMore && loadMore()"
|
||||||
>
|
>
|
||||||
Load more
|
Load more
|
||||||
</button>
|
</button>
|
||||||
@ -14,20 +15,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Routes } from "@/router/routes";
|
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
|
||||||
import { focusElemByClass } from "@/utils";
|
|
||||||
import useSearchStore from "@/stores/search";
|
import useSearchStore from "@/stores/search";
|
||||||
|
|
||||||
import AlbumPage from "./albums.vue";
|
import AlbumPage from "./albums.vue";
|
||||||
import ArtistPage from "./artists.vue";
|
import ArtistPage from "./artists.vue";
|
||||||
import TracksPage from "./tracks.vue";
|
import TracksPage from "./tracks.vue";
|
||||||
|
|
||||||
// width of album and artist cards
|
|
||||||
const defaultItemCount = 6;
|
|
||||||
const gridItemWidth = 160;
|
|
||||||
const page = ref<HTMLElement>();
|
const page = ref<HTMLElement>();
|
||||||
|
|
||||||
const search = useSearchStore();
|
const search = useSearchStore();
|
||||||
@ -57,44 +53,24 @@ const component = computed(() => {
|
|||||||
|
|
||||||
function loadTracks() {
|
function loadTracks() {
|
||||||
search.loadTracks();
|
search.loadTracks();
|
||||||
focusElemByClass("track-11", 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGridRowItemCount() {
|
|
||||||
if (page.value?.offsetWidth === undefined) return defaultItemCount;
|
|
||||||
const page_width = page.value?.offsetWidth - 16;
|
|
||||||
return Math.floor(page_width / gridItemWidth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToGridPageBottom() {
|
function scrollToGridPageBottom() {
|
||||||
const elem = document.getElementsByClassName("grid-page")[0] as HTMLElement;
|
const elem = document.getElementsByClassName("grid-page")[0] as HTMLElement;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
elem.scrollTo(0, elem.scrollHeight);
|
elem.scroll({
|
||||||
|
top: elem.scrollHeight,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
// const elemWidth = elem.offsetWidth;
|
|
||||||
// console.log(Math.floor(elemWidth / 160));
|
|
||||||
// elem.scroll({
|
|
||||||
// top: elem.scrollHeight,
|
|
||||||
// behavior: "smooth",
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAlbums() {
|
function loadAlbums() {
|
||||||
|
search.loadAlbums();
|
||||||
scrollToGridPageBottom();
|
scrollToGridPageBottom();
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// search.loadAlbums();
|
|
||||||
const itemCount = getGridRowItemCount();
|
|
||||||
search.loadAlbums(itemCount);
|
|
||||||
|
|
||||||
scrollToGridPageBottom();
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadArtists() {
|
function loadArtists() {
|
||||||
// const itemCount = getGridRowItemCount();
|
|
||||||
search.loadArtists();
|
search.loadArtists();
|
||||||
|
|
||||||
scrollToGridPageBottom();
|
scrollToGridPageBottom();
|
||||||
@ -132,17 +108,16 @@ const canLoadMore = computed(() => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search.switchTab(route.params.page as string);
|
search.switchTab(route.params.page as string);
|
||||||
|
search.query = route.query.q as string;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.search-view {
|
.search-view {
|
||||||
height: calc(100% - 1rem);
|
height: calc(100%);
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
|
||||||
grid-template-rows: 1fr max-content;
|
|
||||||
|
|
||||||
margin-right: -0.75rem;
|
margin-right: -0.75rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.page.no-scroll {
|
.page.no-scroll {
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
@ -151,17 +126,31 @@ onMounted(() => {
|
|||||||
.grid-page {
|
.grid-page {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||||
gap: 1.75rem 0;
|
gap: 1.75rem 0;
|
||||||
|
|
||||||
padding-bottom: 4rem;
|
padding-bottom: 4rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding-right: $medium;
|
padding-right: $medium;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.load-more {
|
button.load-more {
|
||||||
width: 10rem;
|
position: absolute;
|
||||||
margin: 0 auto;
|
bottom: 0;
|
||||||
|
height: 3rem;
|
||||||
|
left: -1.25rem;
|
||||||
|
width: calc(100% + 1.25rem);
|
||||||
|
border-radius: 0;
|
||||||
|
background: $darkestblue;
|
||||||
|
// margin: 0 auto;
|
||||||
|
// margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-disabled {
|
||||||
|
pointer-events: all;
|
||||||
|
background: $gray !important;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="settingspage content-page">
|
<div class="settingspage content-page" style="height: 100%; overflow: auto">
|
||||||
<Content :current="0" />
|
<Content :current="0" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Content from "../components/SettingsView/Content.vue";
|
import Content from "../components/SettingsView/Content.vue";
|
||||||
</script>z
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user