diff --git a/frontend/src/components/VCollectionHeader/VCollectionHeader.vue b/frontend/src/components/VCollectionHeader/VCollectionHeader.vue index 0d8c44ebebf..b7d99e38643 100644 --- a/frontend/src/components/VCollectionHeader/VCollectionHeader.vue +++ b/frontend/src/components/VCollectionHeader/VCollectionHeader.vue @@ -36,11 +36,18 @@ import { computed, defineComponent, PropType } from "vue" import { useUiStore } from "~/stores/ui" -import type { Collection } from "~/types/search" +import type { CollectionParams } from "~/types/search" import { useAnalytics } from "~/composables/use-analytics" -import VButton from "~/components/VButton.vue" +import { useProviderStore } from "~/stores/provider" +import { SupportedMediaType } from "~/constants/media" + +import { useI18nResultsCount } from "~/composables/use-i18n-utilities" + +import { useMediaStore } from "~/stores/media" + import VIcon from "~/components/VIcon/VIcon.vue" +import VButton from "~/components/VButton.vue" const icons = { tag: "tag", @@ -55,55 +62,96 @@ export default defineComponent({ name: "VCollectionHeader", components: { VIcon, VButton }, props: { - collection: { - type: String as PropType, - required: true, - }, - /** - * The name of the tag/creator/source. The source name should be the display - * name, not the code. - */ - title: { - type: String, + collectionParams: { + type: Object as PropType, required: true, }, - slug: { + creatorUrl: { type: String, }, - url: { - type: String, - }, - /** - * The label showing the result count, to display below the title. - * Should be built by the parent component. - */ - resultsLabel: { - type: String, + mediaType: { + type: String as PropType, required: true, }, }, setup(props) { + const providerStore = useProviderStore() const uiStore = useUiStore() - const iconName = computed(() => icons[props.collection]) + const iconName = computed(() => icons[props.collectionParams.collection]) + const collection = computed(() => props.collectionParams.collection) + + const sourceName = computed(() => { + if (props.collectionParams.collection === "tag") { + return "" + } + return providerStore.getProviderName( + props.collectionParams.source, + props.mediaType + ) + }) + + const title = computed(() => { + if (props.collectionParams.collection === "tag") { + return props.collectionParams.tag + } else if (props.collectionParams.collection === "creator") { + return props.collectionParams.creator + } + return sourceName.value + }) + + const url = computed(() => { + if (props.collectionParams.collection === "tag") { + return undefined + } else if (props.collectionParams.collection === "creator") { + return props.creatorUrl + } + return providerStore.getSourceUrl( + props.collectionParams.source, + props.mediaType + ) + }) + const { getI18nCollectionResultCountLabel } = useI18nResultsCount() + + const resultsLabel = computed(() => { + const resultsCount = useMediaStore().results[props.mediaType].count + if (props.collectionParams.collection === "creator") { + return getI18nCollectionResultCountLabel( + resultsCount, + props.mediaType, + "creator", + { source: sourceName.value } + ) + } + return getI18nCollectionResultCountLabel( + resultsCount, + props.mediaType, + props.collectionParams.collection + ) + }) const isMd = computed(() => uiStore.isBreakpoint("md")) const { sendCustomEvent } = useAnalytics() const sendAnalyticsEvent = () => { - if (!props.url || !props.slug) return + if (props.collectionParams.collection === "tag") return + const eventName = - props.collection === "creator" + props.collectionParams.collection === "creator" ? "VISIT_CREATOR_LINK" : "VISIT_SOURCE_LINK" sendCustomEvent(eventName, { - url: props.url, - source: props.slug, + url: url.value, + source: props.collectionParams.source, }) } return { + collection, + title, + resultsLabel, + url, iconName, isMd, sendAnalyticsEvent, diff --git a/frontend/src/components/VCollectionHeader/meta/VCollectionHeader.stories.mdx b/frontend/src/components/VCollectionHeader/meta/VCollectionHeader.stories.mdx index 79596c0cc7b..a209ca866fc 100644 --- a/frontend/src/components/VCollectionHeader/meta/VCollectionHeader.stories.mdx +++ b/frontend/src/components/VCollectionHeader/meta/VCollectionHeader.stories.mdx @@ -5,11 +5,44 @@ import { Meta, Story, } from "@storybook/addon-docs" +import { useProviderStore } from "~/stores/provider" import VCollectionHeader from "~/components/VCollectionHeader/VCollectionHeader.vue" +import { useMediaStore } from "@/stores/media" +export const imageProviders = [ + { + source_name: "smithsonian_african_american_history_museum", + display_name: + "Smithsonian Institution: National Museum of African American History and Culture", + source_url: "https://nmaahc.si.edu", + logo_url: null, + media_count: 10895, + }, + { + source_name: "flickr", + display_name: "Flickr", + source_url: "https://www.flickr.com", + logo_url: null, + media_count: 505849755, + }, + { + source_name: "met", + display_name: "Metropolitan Museum of Art", + source_url: "https://www.metmuseum.org", + logo_url: null, + media_count: 396650, + }, +] + +export const imageProviderNames = [ + "smithsonian_african_american_history_museum", + "flickr", + "met", +] + export const AllCollectionsTemplate = (args) => ({ template: `
@@ -17,6 +50,15 @@ export const AllCollectionsTemplate = (args) => ({
`, components: { VCollectionHeader }, setup() { + const providerStore = useProviderStore() + providerStore.$patch({ + providers: { image: imageProviders }, + sourceNames: { image: imageProviderNames }, + }) + const mediaStore = useMediaStore() + mediaStore.$patch({ + results: { image: { count: 10000 } }, + }) return { args } }, }) @@ -24,31 +66,37 @@ export const AllCollectionsTemplate = (args) => ({ export const collections = [ { collectionName: "tag", - collection: "tag", - title: "cat", - resultsLabel: "10000 audio files with the selected tag", + collectionParams: { + collection: "tag", + tag: "cat", + }, + mediaType: "image", }, { collectionName: "source", - collection: "source", - title: "Metropolitan Museum of Art", - url: "https://www.metmuseum.org/", - resultsLabel: "10000 images provided by this source", + collectionParams: { + collection: "source", + source: "met", + }, + mediaType: "image", }, { collectionName: "creator", - collection: "creator", - title: "iocyoungreporters", - url: "https://www.flickr.com/photos/126018610@N05", - resultsLabel: "10000 images. iocyoungreporters, Flickr", + collectionParams: { + collection: "creator", + source: "flickr", + creator: "iocyoungreporters", + }, + mediaType: "image", + creatorUrl: "https://www.flickr.com/photos/126018610@N05", }, { collectionName: "source-with-long-name", - collection: "source", - title: - "Smithsonian Institution: National Museum of African American History and Culture", - url: "https://nmaahc.si.edu", - resultsLabel: "10000 images provided by this source", + collectionParams: { + collection: "source", + source: "smithsonian_african_american_history_museum", + }, + mediaType: "image", }, ] diff --git a/frontend/src/composables/use-i18n-utilities.ts b/frontend/src/composables/use-i18n-utilities.ts index 4b7b0347872..eb6c4637115 100644 --- a/frontend/src/composables/use-i18n-utilities.ts +++ b/frontend/src/composables/use-i18n-utilities.ts @@ -2,12 +2,13 @@ import { useGetLocaleFormattedNumber } from "~/composables/use-get-locale-format import { useI18n } from "~/composables/use-i18n" import type { SupportedMediaType, SupportedSearchType } from "~/constants/media" import { ALL_MEDIA, AUDIO, IMAGE } from "~/constants/media" +import { Collection } from "~/types/search" /** * Not using dynamically-generated keys to ensure that * correct line is shown in the 'po' locale files */ -const i18nKeys = { +const searchResultKeys = { [ALL_MEDIA]: { noResult: "browsePage.allNoResults", result: "browsePage.allResultCount", @@ -24,6 +25,52 @@ const i18nKeys = { more: "browsePage.contentLink.audio.countMore", }, } +const collectionKeys = { + source: { + [IMAGE]: { + noResult: "collection.resultCountLabel.source.image.zero", + result: "collection.resultCountLabel.source.image.count", + more: "collection.resultCountLabel.source.image.countMore", + }, + [AUDIO]: { + noResult: "collection.resultCountLabel.source.audio.zero", + result: "collection.resultCountLabel.source.audio.count", + more: "collection.resultCountLabel.source.audio.countMore", + }, + }, + creator: { + [IMAGE]: { + noResult: "collection.resultCountLabel.creator.image.zero", + result: "collection.resultCountLabel.creator.image.count", + more: "collection.resultCountLabel.creator.image.countMore", + }, + [AUDIO]: { + noResult: "collection.resultCountLabel.creator.audio.zero", + result: "collection.resultCountLabel.creator.audio.count", + more: "collection.resultCountLabel.creator.audio.countMore", + }, + }, + tag: { + [IMAGE]: { + noResult: "collection.resultCountLabel.tag.image.zero", + result: "collection.resultCountLabel.tag.image.count", + more: "collection.resultCountLabel.tag.image.countMore", + }, + [AUDIO]: { + noResult: "collection.resultCountLabel.tag.audio.zero", + result: "collection.resultCountLabel.tag.audio.count", + more: "collection.resultCountLabel.tag.audio.countMore", + }, + }, +} + +function getCountKey(resultsCount: number) { + return resultsCount === 0 + ? "noResult" + : resultsCount >= 10000 + ? "more" + : "result" +} /** * Returns the localized text for the number of search results. @@ -38,13 +85,8 @@ export function useI18nResultsCount() { resultsCount: number, searchType: SupportedSearchType ) => { - const countKey = - resultsCount === 0 - ? "noResult" - : resultsCount >= 10000 - ? "more" - : "result" - return i18nKeys[searchType][countKey] + const countKey = getCountKey(resultsCount) + return searchResultKeys[searchType][countKey] } /** @@ -62,6 +104,20 @@ export function useI18nResultsCount() { mediaType, }) } + const getI18nCollectionResultCountLabel = ( + resultCount: number, + mediaType: SupportedMediaType, + collectionType: Collection, + params: Record | undefined = undefined + ) => { + const key = + collectionKeys[collectionType][mediaType][getCountKey(resultCount)] + return i18n.tc(key, resultCount, { + localeCount: getLocaleFormattedNumber(resultCount), + ...params, + }) + } + /** * Returns the localized text for the number of search results, using corresponding * pluralization rules and decimal separators. @@ -76,6 +132,7 @@ export function useI18nResultsCount() { return { getI18nCount, getI18nContentLinkLabel, + getI18nCollectionResultCountLabel, getLoading, } } diff --git a/frontend/src/locales/scripts/en.json5 b/frontend/src/locales/scripts/en.json5 index 007b2eecad9..c1c2ef6e18c 100644 --- a/frontend/src/locales/scripts/en.json5 +++ b/frontend/src/locales/scripts/en.json5 @@ -826,5 +826,43 @@ source: "Open source site", creator: "Open creator page", }, + resultCountLabel: { + creator: { + audio: { + zero: "No audio files by this creator in {source}", + count: "{count} audio file by this creator in {source}|{count} audio files by this creator in {source}", + countMore: "Over {count} audio files by this creator in {source}", + }, + image: { + zero: "No images by this creator in {source}", + count: "{count} image by this creator in {source}|{count} images by this creator in {source}", + countMore: "Over {count} images by this creator in {source}", + }, + }, + source: { + audio: { + zero: "No audio files provided by this source", + count: "{count} audio file provided by this source.|{count} audio files provided by this source", + countMore: "Over {count} audio files provided by this source", + }, + image: { + zero: "No images provided by this source", + count: "{count} image provided by this source.|{count} images provided by this source", + countMore: "Over {count} images provided by this source", + }, + }, + tag: { + audio: { + zero: "No audio files with the selected tag", + count: "{count} audio file with the selected tag.|{count} audio files with the selected tag", + countMore: "Over {count} audio files with the selected tag", + }, + image: { + zero: "No images with the selected tag", + count: "{count} image with the selected tag.|{count} images with the selected tag", + countMore: "Over {count} images with the selected tag", + }, + }, + }, }, } diff --git a/frontend/src/types/search.ts b/frontend/src/types/search.ts index 8dc7b92fb17..b74f90c6a0f 100644 --- a/frontend/src/types/search.ts +++ b/frontend/src/types/search.ts @@ -39,3 +39,16 @@ export type SearchQuery = SearchFilterQuery & SearchRequestQuery export type PaginatedSearchQuery = SearchRequestQuery & PaginatedParams & SearchFilterQuery + +export type TagCollection = { collection: "tag"; tag: string } +export type CreatorCollection = { + collection: "creator" + source: string + creator: string +} +export type SourceCollection = { collection: "source"; source: string } + +export type CollectionParams = + | TagCollection + | CreatorCollection + | SourceCollection diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts index 4a24400d8f2..bac1f2202e5 100644 --- a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts +++ b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts @@ -2,6 +2,8 @@ import { test } from "@playwright/test" import breakpoints from "~~/test/playwright/utils/breakpoints" +test.describe.configure({ mode: "parallel" }) + test.describe("VCollectionHeader", () => { breakpoints.describeEvery(({ expectSnapshot }) => { for (const languageDirection of ["ltr", "rtl"]) { diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-2xl-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-2xl-linux.png index 75c3cf27d1c..aae423033a9 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-2xl-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-2xl-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-lg-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-lg-linux.png index 6eeacf52eb0..07058110eb4 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-lg-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-lg-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-linux.png index 9420d6ad1dc..ef44fee76df 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-sm-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-sm-linux.png index 63a416d2bde..b14d6f4700a 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-sm-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-sm-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xl-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xl-linux.png index 8903d78aaf8..72063074c46 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xl-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xl-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-linux.png index 683ddafe76c..c188bb443d2 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-linux.png index 75c3cf27d1c..aae423033a9 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-linux.png index 6eeacf52eb0..07058110eb4 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-linux.png index 9420d6ad1dc..ef44fee76df 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-linux.png index 63a416d2bde..b14d6f4700a 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-linux.png index 8903d78aaf8..72063074c46 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-linux.png index 683ddafe76c..c188bb443d2 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-linux.png differ