fix #1579 issue added debounced in search (#1600)

* add debounced in search

* updated iconsCategoryOverview

* fix site loading issue

* apply suggestion
This commit is contained in:
Sukomal Dutta
2023-10-13 14:32:34 +02:00
committed by GitHub
parent c05c7e333f
commit a9c1dca801
3 changed files with 113 additions and 127 deletions
@@ -1,93 +1,92 @@
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue'
import type { IconEntity, Category } from '../../types'
import useSearch from '../../composables/useSearch'
import InputSearch from '../base/InputSearch.vue'
import useSearchInput from '../../composables/useSearchInput'
import StickyBar from './StickyBar.vue'
import IconsCategory from './IconsCategory.vue'
import { useFetch } from '@vueuse/core'
import useFetchTags from '../../composables/useFetchTags'
import useFetchCategories from '../../composables/useFetchCategories'
import { ref, computed, defineAsyncComponent } from 'vue';
import type { IconEntity, Category } from '../../types';
import useSearch from '../../composables/useSearch';
import InputSearch from '../base/InputSearch.vue';
import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue';
import IconsCategory from './IconsCategory.vue';
import { useFetch } from '@vueuse/core';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
const props = defineProps<{
icons: IconEntity[]
categories: Category[]
iconCategories: Record<string, string[]>
}>()
icons: IconEntity[];
categories: Category[];
iconCategories: Record<string, string[]>;
}>();
const activeIconName = ref(null)
const { searchInput, searchQuery, searchQueryThrottled } = useSearchInput()
const activeIconName = ref(null);
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const isSearching = computed(() => !!searchQuery.value)
const isSearching = computed(() => !!searchQuery.value);
function setActiveIconName(name: string) {
activeIconName.value = name
activeIconName.value = name;
}
const { execute: fetchTags, data: tags } = useFetchTags()
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories()
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
const mappedIcons = computed(() => {
if(tags.value == null) {
return props.icons
if (tags.value == null) {
return props.icons;
}
return props.icons.map((icon) => {
const iconTags = tags.value[icon.name]
const iconCategories = categoriesMap.value?.[icon.name] ?? []
const iconTags = tags.value[icon.name];
const iconCategories = categoriesMap.value?.[icon.name] ?? [];
return {
...icon,
tags: iconTags,
categories: iconCategories,
}
})
})
};
});
});
const searchResults = useSearch(searchQuery, mappedIcons, [
{ name: 'name', weight: 2 },
{ name: 'tags', weight: 1 },
])
]);
const categories = computed(() => {
if( !props.categories?.length || !props.icons?.length ) return []
if (!props.categories?.length || !props.icons?.length) return [];
return props.categories.map(({ name, title }) => {
const categoryIcons = props.icons.filter((icon) => {
const iconCategories = props.iconCategories[icon.name]
return props.categories
.map(({ name, title }) => {
const categoryIcons = props.icons.filter((icon) => {
const iconCategories = props.iconCategories[icon.name];
return iconCategories?.includes(name)
return iconCategories?.includes(name);
});
const searchedCategoryIcons = isSearching
? categoryIcons.filter((icon) =>
searchResults.value.some((item) => item?.name === icon?.name)
)
: categoryIcons;
return {
title,
name,
icons: searchedCategoryIcons,
};
})
const searchedCategoryIcons = isSearching
? categoryIcons.filter(icon => searchResults.value.some((item) => item?.name === icon?.name))
: categoryIcons;
return {
title,
name,
icons: searchedCategoryIcons,
};
})
.filter(({ icons }) => icons.length)
})
.filter(({ icons }) => icons.length);
});
function onFocusSearchInput() {
if (tags.value == null) {
fetchTags()
fetchTags();
}
if (categoriesMap.value == null) {
fetchCategories()
fetchCategories();
}
}
const NoResults = defineAsyncComponent(() =>
import('./NoResults.vue')
)
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() =>
import('./IconDetailOverlay.vue')
)
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
</script>
<template>
@@ -100,11 +99,7 @@ const IconDetailOverlay = defineAsyncComponent(() =>
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="categories.length === 0"
:searchQuery="searchQuery"
@clear="searchQuery = ''"
/>
<NoResults v-if="categories.length === 0" :searchQuery="searchQuery" @clear="searchQuery = ''" />
<IconsCategory
v-for="category in categories"
:key="category.name"
@@ -1,21 +1,21 @@
<script setup lang="ts">
import { ref, computed, watch, defineAsyncComponent } from 'vue'
import type { IconEntity } from '../../types'
import { useMediaQuery, useOffsetPagination } from '@vueuse/core'
import IconGrid from './IconGrid.vue'
import InputSearch from '../base/InputSearch.vue'
import useSearch from '../../composables/useSearch'
import EndOfPage from '../base/EndOfPage.vue'
import useSearchInput from '../../composables/useSearchInput'
import StickyBar from './StickyBar.vue'
import useFetchTags from '../../composables/useFetchTags'
import useFetchCategories from '../../composables/useFetchCategories'
import { ref, computed, watch, defineAsyncComponent } from 'vue';
import type { IconEntity } from '../../types';
import { useMediaQuery, useOffsetPagination } from '@vueuse/core';
import IconGrid from './IconGrid.vue';
import InputSearch from '../base/InputSearch.vue';
import useSearch from '../../composables/useSearch';
import EndOfPage from '../base/EndOfPage.vue';
import useSearchInput from '../../composables/useSearchInput';
import StickyBar from './StickyBar.vue';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
const props = defineProps<{
icons: IconEntity[]
}>()
icons: IconEntity[];
}>();
const activeIconName = ref(null)
const activeIconName = ref(null);
const isExtraLargeScreen = useMediaQuery('(min-width: 1440px)');
const isLargeScreen = useMediaQuery('(min-width: 1280px)');
@@ -23,84 +23,78 @@ const isMediumScreen = useMediaQuery('(min-width: 960px)');
const isSmallScreen = useMediaQuery('(min-width: 640px)');
const pageSize = computed(() => {
if(isExtraLargeScreen.value) {
if (isExtraLargeScreen.value) {
return 16 * 20;
}
if(isLargeScreen.value) {
if (isLargeScreen.value) {
return 16 * 12;
}
if(isMediumScreen.value) {
if (isMediumScreen.value) {
return 13 * 12;
}
if(isSmallScreen.value) {
if (isSmallScreen.value) {
return 10 * 10;
}
return 10 * 5;
})
});
const { execute: fetchTags, data: tags } = useFetchTags()
const { execute: fetchCategories, data: categories } = useFetchCategories()
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categories } = useFetchCategories();
const mappedIcons = computed(() => {
if(tags.value == null) {
return props.icons
if (tags.value == null) {
return props.icons;
}
return props.icons.map((icon) => {
const iconTags = tags.value[icon.name]
const iconCategories = categories.value?.[icon.name] ?? []
const iconTags = tags.value[icon.name];
const iconCategories = categories.value?.[icon.name] ?? [];
return {
...icon,
tags: iconTags,
categories: iconCategories,
}
})
})
};
});
});
const { searchInput, searchQuery, searchQueryThrottled } = useSearchInput()
const searchResults = useSearch(searchQueryThrottled, mappedIcons, [
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 },
{ name: 'tags', weight: 2 },
{ name: 'categories', weight: 1 },
])
const { next, currentPage } = useOffsetPagination( { pageSize })
]);
const { next, currentPage } = useOffsetPagination({ pageSize });
const paginatedIcons = computed(() => {
const end = pageSize.value * currentPage.value
const end = pageSize.value * currentPage.value;
return searchResults.value.slice(0, end)
})
return searchResults.value.slice(0, end);
});
function setActiveIconName(name: string) {
activeIconName.value = name
activeIconName.value = name;
}
watch(searchQueryThrottled, (searchString) => {
currentPage.value = 1
})
watch(searchQueryDebounced, (searchString) => {
currentPage.value = 1;
});
function onFocusSearchInput() {
if (tags.value == null) {
fetchTags()
fetchTags();
}
if (categories.value == null) {
fetchCategories()
fetchCategories();
}
}
const NoResults = defineAsyncComponent(() =>
import('./NoResults.vue')
)
const IconDetailOverlay = defineAsyncComponent(() =>
import('./IconDetailOverlay.vue')
)
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
</script>
<template>
@@ -124,7 +118,7 @@ const IconDetailOverlay = defineAsyncComponent(() =>
:icons="paginatedIcons"
@setActiveIcon="setActiveIconName"
/>
<EndOfPage @end-of-page="next" class="bottom-page"/>
<EndOfPage @end-of-page="next" class="bottom-page" />
<IconDetailOverlay
v-if="activeIconName != null"
:iconName="activeIconName"
@@ -1,43 +1,40 @@
import { refThrottled } from '@vueuse/core';
import { useDebounce } from '@vueuse/core';
import { nextTick, onMounted, ref, watch } from 'vue';
const useSearchInput = () => {
const searchInput = ref()
const searchInput = ref();
const searchQuery = ref(
typeof window === 'undefined'
? ''
: (
new URLSearchParams(window.location.search).get('search')
|| ''
)
)
const searchQueryThrottled = refThrottled(searchQuery, 400)
? ''
: new URLSearchParams(window.location.search).get('search') || ''
);
const searchQueryDebounced = useDebounce(searchQuery, 250);
watch(searchQueryThrottled, (searchString) => {
watch(searchQueryDebounced, (searchString) => {
const newUrl = new URL(window.location.href);
if(searchString === '') {
if (searchString === '') {
newUrl.searchParams.delete('search');
} else {
newUrl.searchParams.set('search', searchString);
}
nextTick(() => {
window.history.replaceState({}, '', newUrl)
})
})
window.history.replaceState({}, '', newUrl);
});
});
onMounted(() => {
const searchParams = new URLSearchParams(window.location.search);
if(searchParams.has('focus')) {
searchInput.value.focus()
if (searchParams.has('focus')) {
searchInput.value.focus();
}
})
});
return {
searchInput,
searchQuery,
searchQueryThrottled
searchQueryDebounced,
};
};