diff --git a/frontend/src/components/VCollectionPage.vue b/frontend/src/components/VCollectionPage.vue deleted file mode 100644 index 7467ccc3485..00000000000 --- a/frontend/src/components/VCollectionPage.vue +++ /dev/null @@ -1,110 +0,0 @@ - - diff --git a/frontend/src/components/VLoadMore.vue b/frontend/src/components/VLoadMore.vue index 2d17512136a..cdf8453af58 100644 --- a/frontend/src/components/VLoadMore.vue +++ b/frontend/src/components/VLoadMore.vue @@ -5,7 +5,7 @@ class="label-bold lg:description-bold h-16 w-full lg:h-18" variant="filled-gray" size="disabled" - :disabled="fetchState.isFetching" + :disabled="isFetching" data-testid="load-more" @click="onLoadMore" > @@ -14,16 +14,25 @@ diff --git a/frontend/src/components/VSearchResultsGrid/VCollectionResults.vue b/frontend/src/components/VSearchResultsGrid/VCollectionResults.vue new file mode 100644 index 00000000000..40b66f70fa5 --- /dev/null +++ b/frontend/src/components/VSearchResultsGrid/VCollectionResults.vue @@ -0,0 +1,76 @@ + + diff --git a/frontend/src/components/VSearchResultsGrid/VImageGrid.vue b/frontend/src/components/VSearchResultsGrid/VImageGrid.vue deleted file mode 100644 index bd17866ebf9..00000000000 --- a/frontend/src/components/VSearchResultsGrid/VImageGrid.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - - - diff --git a/frontend/src/components/VSearchResultsGrid/VMediaCollection.vue b/frontend/src/components/VSearchResultsGrid/VMediaCollection.vue index 062e1639a03..23bfda46e13 100644 --- a/frontend/src/components/VSearchResultsGrid/VMediaCollection.vue +++ b/frontend/src/components/VSearchResultsGrid/VMediaCollection.vue @@ -15,7 +15,7 @@ :class="{ 'pt-2 sm:pt-0': results.type === 'image' }" /> - + import { computed, defineComponent, inject, type PropType, ref } from "vue" -import { IsSidebarVisibleKey, ShowScrollButtonKey } from "~/types/provides" import type { Results } from "~/types/result" - +import { IsSidebarVisibleKey, ShowScrollButtonKey } from "~/types/provides" import { defineEvent } from "~/types/emits" import VGridSkeleton from "~/components/VSkeleton/VGridSkeleton.vue" @@ -68,6 +67,10 @@ export default defineComponent({ type: String as PropType, default: null, }, + /** + * Overrides the value from the media store. + * Used for the related media which uses a different store. + */ isFetching: { type: Boolean, required: true, diff --git a/frontend/src/components/VSearchResultsGrid/VSearchResults.vue b/frontend/src/components/VSearchResultsGrid/VSearchResults.vue new file mode 100644 index 00000000000..1205d2f1666 --- /dev/null +++ b/frontend/src/components/VSearchResultsGrid/VSearchResults.vue @@ -0,0 +1,117 @@ + + diff --git a/frontend/src/components/meta/CustomButtonComponents.stories.mdx b/frontend/src/components/meta/CustomButtonComponents.stories.mdx index 81933fafbf2..d93effa84a1 100644 --- a/frontend/src/components/meta/CustomButtonComponents.stories.mdx +++ b/frontend/src/components/meta/CustomButtonComponents.stories.mdx @@ -6,7 +6,6 @@ import { Story, } from "@storybook/addon-docs" import VLoadMore from "~/components/VLoadMore.vue" -import { useSearchStore } from "~/stores/search" import { useMediaStore } from "~/stores/media" @@ -17,9 +16,8 @@ export const Template = (args) => ({ `, components: { VLoadMore }, setup() { - const searchStore = useSearchStore() - searchStore.setSearchTerm("cat") const mediaStore = useMediaStore() + mediaStore._startFetching("image") mediaStore.results.image.count = 1 return { args } }, @@ -37,6 +35,12 @@ export const Template = (args) => ({ parameters={{ viewport: { defaultViewport: "sm" }, }} + args={{ + kind: "search", + searchType: "image", + searchTerm: "cat", + isFetching: false, + }} > {Template.bind({})} diff --git a/frontend/src/composables/use-search-type.ts b/frontend/src/composables/use-search-type.ts index 970b888a0c5..4e0d4f218e2 100644 --- a/frontend/src/composables/use-search-type.ts +++ b/frontend/src/composables/use-search-type.ts @@ -11,7 +11,6 @@ import { SearchType, } from "~/constants/media" -import { useMediaStore } from "~/stores/media" import { useSearchStore } from "~/stores/search" import { useFeatureFlagStore } from "~/stores/feature-flag" @@ -65,8 +64,14 @@ export default function useSearchType() { next: searchType, component: componentName, }) + + // `setActiveType` is called after the search middleware + // ran and updated the search store state + // TODO: Figure out why + if (activeType.value === searchType) { + return + } useSearchStore().setSearchType(searchType) - useMediaStore().clearMedia() previousSearchType.value = searchType } diff --git a/frontend/src/middleware/search.ts b/frontend/src/middleware/search.ts index ba337051ee9..4a100a678e3 100644 --- a/frontend/src/middleware/search.ts +++ b/frontend/src/middleware/search.ts @@ -47,7 +47,7 @@ export const searchMiddleware: Middleware = async ({ const results = await mediaStore.fetchMedia() const fetchingError = mediaStore.fetchState.fetchingError - if (!results && fetchingError && !handledClientSide(fetchingError)) { + if (!results.length && fetchingError && !handledClientSide(fetchingError)) { nuxtError(fetchingError) } } diff --git a/frontend/src/pages/audio/collection.vue b/frontend/src/pages/audio/collection.vue index 973d89cd617..5c26b87bc55 100644 --- a/frontend/src/pages/audio/collection.vue +++ b/frontend/src/pages/audio/collection.vue @@ -1,27 +1,100 @@ diff --git a/frontend/src/pages/image/collection.vue b/frontend/src/pages/image/collection.vue index bf4028df62e..dc58c0584f3 100644 --- a/frontend/src/pages/image/collection.vue +++ b/frontend/src/pages/image/collection.vue @@ -1,27 +1,100 @@ diff --git a/frontend/src/pages/search.vue b/frontend/src/pages/search.vue index b95cb25f87c..29134ae2e18 100644 --- a/frontend/src/pages/search.vue +++ b/frontend/src/pages/search.vue @@ -10,39 +10,22 @@ class="w-full py-10" />
-
- {{ - searchTerm - }} -
- -
diff --git a/frontend/src/pages/search/image.vue b/frontend/src/pages/search/image.vue index 68db3350917..d6a63742966 100644 --- a/frontend/src/pages/search/image.vue +++ b/frontend/src/pages/search/image.vue @@ -1,38 +1,40 @@ diff --git a/frontend/src/pages/search/index.vue b/frontend/src/pages/search/index.vue index 079473bc73e..46defa4de09 100644 --- a/frontend/src/pages/search/index.vue +++ b/frontend/src/pages/search/index.vue @@ -1,14 +1,40 @@ diff --git a/frontend/src/stores/media/index.ts b/frontend/src/stores/media/index.ts index 71c37fac05e..5c35ee48e6a 100644 --- a/frontend/src/stores/media/index.ts +++ b/frontend/src/stores/media/index.ts @@ -24,6 +24,10 @@ import { isSearchTypeSupported, useSearchStore } from "~/stores/search" import { useRelatedMediaStore } from "~/stores/media/related-media" import { deepFreeze } from "~/utils/deep-freeze" +interface SearchFetchState extends Omit { + hasStarted: boolean +} + export type MediaStoreResult = { count: number pageCount: number @@ -37,8 +41,8 @@ export interface MediaState { image: MediaStoreResult } mediaFetchState: { - audio: FetchState - image: FetchState + audio: SearchFetchState + image: SearchFetchState } currentPage: number } @@ -143,12 +147,12 @@ export const useMediaStore = defineStore("media", { * Search fetching state for selected search type. For 'All content', aggregates * the values for supported media types. */ - fetchState(): FetchState { + fetchState(): SearchFetchState { if (this._searchType === ALL_MEDIA) { /** * For all_media, we return 'All media fetching error' if all types have some kind of error. */ - const atLeastOne = (property: keyof FetchState) => + const atLeastOne = (property: keyof SearchFetchState) => supportedMediaTypes.some( (type) => this.mediaFetchState[type][property] ) @@ -286,6 +290,15 @@ export const useMediaStore = defineStore("media", { !this.mediaFetchState[type].isFinished ) }, + + canLoadMore(): boolean { + return ( + this.fetchState.hasStarted && + !this.fetchState.fetchingError && + !this.fetchState.isFinished && + this.resultCount > 0 + ) + }, }, actions: { @@ -408,26 +421,23 @@ export const useMediaStore = defineStore("media", { async fetchMedia(payload: { shouldPersistMedia?: boolean } = {}) { const mediaType = this._searchType const shouldPersistMedia = Boolean(payload.shouldPersistMedia) - if (!shouldPersistMedia) { - this.clearMedia() - } const mediaToFetch = this._fetchableMediaTypes - const resultCounts = await Promise.all( + await Promise.allSettled( mediaToFetch.map((mediaType) => this.fetchSingleMediaType({ mediaType, shouldPersistMedia }) ) ) - const resultCount = resultCounts.includes(null) - ? null - : (resultCounts as number[]).reduce((a, b) => a + b, 0) this.currentPage = mediaType === ALL_MEDIA ? this.currentPage + 1 : this.results[mediaType].page - return resultCount + + return mediaType === ALL_MEDIA + ? this.allMedia + : this.resultItems[mediaType] }, clearMedia() { diff --git a/frontend/src/stores/search.ts b/frontend/src/stores/search.ts index 8ac22e8f21e..9ba0975c397 100644 --- a/frontend/src/stores/search.ts +++ b/frontend/src/stores/search.ts @@ -311,9 +311,6 @@ export const useSearchStore = defineStore("search", { this.strategy = "default" this.addRecentSearch(formattedTerm) - - const mediaStore = useMediaStore() - mediaStore.clearMedia() }, /** * Sets the collectionParams and mediaType for the collection page. @@ -329,7 +326,8 @@ export const useSearchStore = defineStore("search", { this.clearFilters() }, /** - * Called when a /search path is server-rendered. + * Called before navigating to a `/search` path, and when the + * path after `/search` or query parameters change. */ setSearchStateFromUrl({ path, @@ -343,6 +341,9 @@ export const useSearchStore = defineStore("search", { this.strategy = "default" this.collectionParams = null + const mediaStore = useMediaStore() + mediaStore.clearMedia() + this.setSearchTerm(query.q) this.searchType = pathToSearchType(path) diff --git a/frontend/test/unit/specs/components/v-image-grid.spec.js b/frontend/test/unit/specs/components/v-image-grid.spec.js deleted file mode 100644 index fb2ad0d730d..00000000000 --- a/frontend/test/unit/specs/components/v-image-grid.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import { screen } from "@testing-library/vue" - -import { render } from "~~/test/unit/test-utils/render" - -import VImageGrid from "~/components/VSearchResultsGrid/VImageGrid.vue" - -const propsData = { - results: [ - { id: "i1", url: "http://localhost:8080/i1.png", title: "image1" }, - { id: "i2", url: "http://localhost:8080/i2.jpg", title: "image2" }, - { id: "i3", url: "http://localhost:8080/i3.svg", title: "image3" }, - ], - fetchState: { - isSinglePage: true, - isFetching: false, - fetchingError: null, - }, - kind: "related", - imageGridLabel: "Image Results", -} - -describe("VImageGrid", () => { - let options - beforeEach(() => { - options = { - props: propsData, - stubs: ["VLicense"], - } - }) - it("renders images without load more button for related images", () => { - const imageCount = propsData.results.length - render(VImageGrid, options) - expect(screen.queryAllByRole("img").length).toEqual(imageCount) - expect(screen.queryAllByRole("figure").length).toEqual(imageCount) - expect(screen.queryByTestId("load-more")).toBeNull() - }) -}) diff --git a/frontend/test/unit/specs/pages/browse-page.spec.js b/frontend/test/unit/specs/pages/browse-page.spec.js deleted file mode 100644 index 409ae111be9..00000000000 --- a/frontend/test/unit/specs/pages/browse-page.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import { screen } from "@testing-library/vue" -import { ref } from "vue" - -import { render } from "~~/test/unit/test-utils/render" - -import { IMAGE } from "~/constants/media" -import { useSearchStore } from "~/stores/search" - -import SearchIndex from "~/pages/search.vue" - -import { - IsHeaderScrolledKey, - IsSidebarVisibleKey, - ShowScrollButtonKey, -} from "~/types/provides" - -describe("SearchIndex", () => { - let options - const defaultProvideOptions = { - showScrollButton: ref(false), - [IsHeaderScrolledKey]: ref(false), - [IsSidebarVisibleKey]: ref(false), - [ShowScrollButtonKey]: ref(false), - } - let searchStore - - beforeEach(() => { - options = { - provide: defaultProvideOptions, - mocks: { - $router: { path: { name: "search-image" } }, - $route: { path: "/search/image" }, - }, - stubs: { - NuxtChild: true, - VSearchGrid: true, - }, - } - }) - - it("hides the scroll button when injected value is false", () => { - options.provide.showScrollButton.value = false - - render(SearchIndex, options, (localVue, options) => { - searchStore = useSearchStore(options.pinia) - searchStore.setSearchTerm("cat") - searchStore.setSearchType(IMAGE) - }) - - expect(screen.queryByLabelText(/scroll/i)).not.toBeVisible() - }) - - it("shows the scroll button when injected value is true", () => { - options.provide[ShowScrollButtonKey].value = true - render(SearchIndex, options, (localVue, options) => { - searchStore = useSearchStore(options.pinia) - searchStore.setSearchTerm("cat") - searchStore.setSearchType(IMAGE) - }) - - expect(screen.queryByLabelText(/scroll/i)).toBeVisible() - }) -})