Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use query parameters for additional search views in Nuxt #3866

Merged
merged 6 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/VMediaInfo/VMediaDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<div class="flex w-full flex-grow flex-col gap-6">
<p v-if="media.description">{{ media.description }}</p>
<VMediaTags :tags="media.tags" />
<VMediaTags :tags="media.tags" :media-type="media.frontendMediaType" />
<VMetadata v-if="metadata" :metadata="metadata" />
</div>
</div>
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/components/VMediaInfo/VMediaTags.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from "vue"
import { useContext } from "@nuxtjs/composition-api"

import type { Tag } from "~/types/media"
import type { SupportedMediaType } from "~/constants/media"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useSearchStore } from "~/stores/search"

import VMediaTag from "~/components/VMediaTag/VMediaTag.vue"
import VTag from "~/components/VTag/VTag.vue"
Expand All @@ -31,17 +32,24 @@ export default defineComponent({
type: Array as PropType<Tag[]>,
required: true,
},
mediaType: {
type: String as PropType<SupportedMediaType>,
required: true,
},
},
setup() {
const { app } = useContext()
setup(props) {
const searchStore = useSearchStore()
const featureFlagStore = useFeatureFlagStore()

const additionalSearchViews = computed(() =>
featureFlagStore.isOn("additional_search_views")
)

const localizedTagPath = (tag: Tag) => {
return app.localePath({ path: `tag/${tag.name}` })
return searchStore.getCollectionPath({
type: props.mediaType,
collectionParams: { collection: "tag", tag: tag.name },
})
}

return { additionalSearchViews, localizedTagPath }
Expand Down
17 changes: 11 additions & 6 deletions frontend/src/components/VSourcesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@
</template>

<script lang="ts">
import { computed, defineComponent, PropType, reactive } from "vue"

import { useContext } from "@nuxtjs/composition-api"
import { computed, defineComponent, type PropType, reactive } from "vue"

import { useProviderStore } from "~/stores/provider"
import { useGetLocaleFormattedNumber } from "~/composables/use-get-locale-formatted-number"
Expand All @@ -71,6 +69,7 @@ import type { SupportedMediaType } from "~/constants/media"
import type { MediaProvider } from "~/types/media-provider"

import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useSearchStore } from "~/stores/search"

import TableSortIcon from "~/components/TableSortIcon.vue"
import VLink from "~/components/VLink.vue"
Expand All @@ -88,8 +87,6 @@ export default defineComponent({
},
},
setup(props) {
const { app } = useContext()

const sorting = reactive({
direction: "asc",
field: "display_name" as keyof Omit<MediaProvider, "logo_url">,
Expand Down Expand Up @@ -148,8 +145,16 @@ export default defineComponent({
const additionalSearchViews = computed(() => {
return featureFlagStore.isOn("additional_search_views")
})

const searchStore = useSearchStore()
const providerViewUrl = (provider: MediaProvider) => {
return app.localePath(`/${props.media}/source/${provider.source_name}`)
return searchStore.getCollectionPath({
type: props.media,
collectionParams: {
collection: "source",
source: provider.source_name,
},
})
}
return {
getLocaleFormattedNumber,
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/data/api-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export interface ApiService {
client: AxiosInstance
query<T = unknown>(
resource: string,
slug: string,
params: Record<string, string>
): Promise<AxiosResponse<T>>
get<T = unknown>(
Expand Down Expand Up @@ -139,16 +138,14 @@ export const createApiService = ({

/**
* @param resource - The endpoint of the resource
* @param slug - the optional additional endpoint, used for collections.
* @param params - Url parameter object
* @returns response The API response object
*/
query<T = unknown>(
resource: string,
slug: string = "",
params: Record<string, string> = {}
): Promise<AxiosResponse<T>> {
return client.get(`${getResourceSlug(resource)}${slug}`, { params })
return client.get(`${getResourceSlug(resource)}`, { params })
obulat marked this conversation as resolved.
Show resolved Hide resolved
},

/**
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/data/media-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,9 @@ class MediaService<T extends Media> {
/**
* Search for media items by keyword.
* @param params - API search query parameters
* @param slug - optional slug to get a collection
*/
async search(
params: PaginatedSearchQuery | PaginatedCollectionQuery,
slug: string = ""
params: PaginatedSearchQuery | PaginatedCollectionQuery
): Promise<MediaResult<Record<string, Media>>> {
// Add the `peaks` param to all audio searches automatically
if (this.mediaType === AUDIO) {
Expand All @@ -64,7 +62,6 @@ class MediaService<T extends Media> {

const res = await this.apiService.query<MediaResult<T[]>>(
this.mediaType,
slug,
params as unknown as Record<string, string>
)
return this.transformResults(res.data)
Expand Down
86 changes: 85 additions & 1 deletion frontend/src/middleware/collection.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,99 @@
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useProviderStore } from "~/stores/provider"
import { useSearchStore } from "~/stores/search"

import type { Middleware } from "@nuxt/types"
import { queryDictionaryToQueryParams } from "~/utils/search-query-transform"
import {
isSupportedMediaType,
type SupportedMediaType,
} from "~/constants/media"
import type {
CollectionParams,
CreatorCollection,
SourceCollection,
TagCollection,
} from "~/types/search"

import type { Context, Middleware } from "@nuxt/types"
import type { Dictionary } from "vue-router/types/router"

const queryToCollectionParams = (
obulat marked this conversation as resolved.
Show resolved Hide resolved
query: Dictionary<string | (string | null)[]>
): CollectionParams | undefined => {
query = queryDictionaryToQueryParams(query)
if ("tag" in query) {
return {
collection: "tag",
tag: query.tag,
} as TagCollection
}

if ("creator" in query && "source" in query) {
return {
collection: "creator",
creator: query.creator,
source: query.source,
} as CreatorCollection
}

if ("source" in query) {
return {
collection: "source",
source: query.source,
} as SourceCollection
}
return undefined
}

const routeNameToMediaType = (
route: Context["route"]
): SupportedMediaType | null => {
const firstPart = route.name?.split("-")[0]
return firstPart && isSupportedMediaType(firstPart) ? firstPart : null
}

/**
* Middleware for the collection routes.
* Checks that the feature flag is enabled and that the route (name and query) is valid.
* Extracts the collectionParams from the route and updates the search store.
* If the source name does not exist in the provider store, it will throw a 404 error.
*/

export const collectionMiddleware: Middleware = async ({
$pinia,
error: nuxtError,
route,
}) => {
if (!useFeatureFlagStore($pinia).isOn("additional_search_views")) {
nuxtError({
statusCode: 404,
message: "Additional search views are not enabled",
})
return
}

const searchStore = useSearchStore($pinia)
// Route name has the locale in it, e.g. `audio-collection__en`
const mediaType = routeNameToMediaType(route)
const collectionParams = queryToCollectionParams(route.query)

if (mediaType === null || collectionParams === undefined) {
nuxtError({
statusCode: 404,
message: "Invalid collection route",
})
return
}

if ("source" in collectionParams) {
const providerStore = useProviderStore($pinia)
if (!providerStore.isSourceNameValid(mediaType, collectionParams.source)) {
nuxtError({
statusCode: 404,
message: `Invalid source name ${collectionParams.source} for media type ${mediaType}`,
})
}
}

searchStore.setCollectionState(collectionParams, mediaType)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,19 @@
</template>

<script lang="ts">
import { defineComponent, useFetch, useRoute } from "@nuxtjs/composition-api"
import { defineComponent, useFetch } from "@nuxtjs/composition-api"

import { useMediaStore } from "~/stores/media"
import { useSearchStore } from "~/stores/search"
import { AUDIO } from "~/constants/media"
import type { TagCollection } from "~/types/search"
import { collectionMiddleware } from "~/middleware/collection"

import VCollectionPage from "~/components/VCollectionPage.vue"

export default defineComponent({
name: "VAudioTagPage",
name: "VAudioCollectionPage",
components: { VCollectionPage },
layout: "content-layout",
middleware: collectionMiddleware,
setup() {
const route = useRoute()
const collectionParams: TagCollection = {
tag: route.value.params.tag,
collection: "tag",
}
useSearchStore().setCollectionState(collectionParams, AUDIO)

const mediaStore = useMediaStore()

useFetch(async () => {
Expand Down
37 changes: 0 additions & 37 deletions frontend/src/pages/audio/source/_.vue

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,19 @@
</template>

<script lang="ts">
import { defineComponent, useFetch, useRoute } from "@nuxtjs/composition-api"
import { defineComponent, useFetch } from "@nuxtjs/composition-api"

import { useMediaStore } from "~/stores/media"
import { useSearchStore } from "~/stores/search"
import { IMAGE } from "~/constants/media"
import { collectionMiddleware } from "~/middleware/collection"
import type { TagCollection } from "~/types/search"

import VCollectionPage from "~/components/VCollectionPage.vue"

export default defineComponent({
name: "VImageTagPage",
name: "VImageCollectionPage",
components: { VCollectionPage },
layout: "content-layout",
middleware: collectionMiddleware,
setup() {
const route = useRoute()
const collectionParams: TagCollection = {
tag: route.value.params.tag,
collection: "tag",
}
useSearchStore().setCollectionState(collectionParams, IMAGE)

const mediaStore = useMediaStore()

useFetch(async () => {
Expand Down
36 changes: 0 additions & 36 deletions frontend/src/pages/image/source/_.vue

This file was deleted.

Loading