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:
geoffrey45 2023-01-03 19:43:52 +03:00 committed by Mungai Njoroge
parent 68f990aada
commit c1fb05e2a0
11 changed files with 67 additions and 59 deletions

View File

@ -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 =======

View File

@ -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,
}, },
{ {

View File

@ -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;

View File

@ -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";

View File

@ -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,
}, },
}); });

View File

@ -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

View File

@ -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;

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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>