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"
/>
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()
- })
-})