diff --git a/src/common/constants/misc.js b/src/common/constants/misc.js index 1157be9d..c51f8221 100644 --- a/src/common/constants/misc.js +++ b/src/common/constants/misc.js @@ -7,6 +7,7 @@ export const DISPLAYABLE_PROPS_MEDIA = [ 'title', 'mimeType', 'acknowledgement', + 'description', ] export const ABOUT_LINK = 'https://firstvoices.atlassian.net/wiki/spaces/FIR1/pages/1704813/About+FirstVoices' diff --git a/src/common/constants/searchParams.js b/src/common/constants/searchParams.js index 4059e314..a3dd5c9f 100644 --- a/src/common/constants/searchParams.js +++ b/src/common/constants/searchParams.js @@ -28,6 +28,10 @@ export const TYPE_STORY = 'story' export const TYPE_WORD = 'word' export const TYPE_ENTRY = 'word,phrase,song,story' export const TYPE_DICTIONARY = 'word,phrase' +export const TYPE_MEDIA = 'audio,image,video' +export const TYPE_AUDIO = 'audio' +export const TYPE_IMAGE = 'image' +export const TYPE_VIDEO = 'video' /* Param Keys Frontend ONLY */ export const CHAR = 'char' diff --git a/src/common/dataAdaptors/index.js b/src/common/dataAdaptors/index.js index e1389ba4..88318200 100644 --- a/src/common/dataAdaptors/index.js +++ b/src/common/dataAdaptors/index.js @@ -3,3 +3,4 @@ export * from './widgetAdaptors' export * from './storyAdaptors' export * from './songAdaptors' export * from './pageAdaptors' +export * from './mediaAdaptors' diff --git a/src/common/dataHooks/useSearchLoader.js b/src/common/dataHooks/useSearchLoader.js index e01fc100..c82f2dcd 100644 --- a/src/common/dataHooks/useSearchLoader.js +++ b/src/common/dataHooks/useSearchLoader.js @@ -12,8 +12,15 @@ import { TYPE_SONG, TYPE_STORY, TYPE_WORD, + TYPE_AUDIO, + TYPE_IMAGE, + TYPE_VIDEO, } from 'common/constants' -import { storySummaryAdaptor, songSummaryAdaptor } from 'common/dataAdaptors' +import { + storySummaryAdaptor, + songSummaryAdaptor, + mediaAdaptor, +} from 'common/dataAdaptors' /** * Calls search API and provides search results and infinite scroll info. @@ -64,6 +71,13 @@ function useSearchLoader({ searchParams }) { ...storySummaryAdaptor({ item: result?.entry }), ...baseObject, } + case TYPE_AUDIO: + case TYPE_IMAGE: + case TYPE_VIDEO: + return { + ...mediaAdaptor({ type: result?.type, data: result?.entry }), + ...baseObject, + } default: return { ...baseObject, diff --git a/src/common/utils/stringHelpers.js b/src/common/utils/stringHelpers.js index d4ee1c84..a76b885e 100644 --- a/src/common/utils/stringHelpers.js +++ b/src/common/utils/stringHelpers.js @@ -20,6 +20,10 @@ import { TYPE_WORD, TYPE_STORY, TYPE_SONG, + TYPE_MEDIA, + TYPE_AUDIO, + TYPE_IMAGE, + TYPE_VIDEO, UUID_REGEX, } from 'common/constants' @@ -235,6 +239,38 @@ export const getPresentationPropertiesForType = (type) => { slug: 'dictionary', color: 'word', } + case TYPE_MEDIA: + return { + uppercase: 'MEDIA', + singular: 'media', + plural: 'media', + slug: 'search', + color: 'primary', + } + case TYPE_AUDIO: + return { + uppercase: 'AUDIO', + singular: 'audio', + plural: 'audio', + slug: 'audio', + color: 'primary', + } + case TYPE_IMAGE: + return { + uppercase: 'IMAGE', + singular: 'image', + plural: 'images', + slug: 'image', + color: 'primary', + } + case TYPE_VIDEO: + return { + uppercase: 'VIDEO', + singular: 'video', + plural: 'videos', + slug: 'video', + color: 'primary', + } case TYPE_ENTRY: default: return { diff --git a/src/components/DashboardMedia/DashboardMediaData.js b/src/components/DashboardMedia/DashboardMediaData.js index 990e6581..c39b9d2b 100644 --- a/src/components/DashboardMedia/DashboardMediaData.js +++ b/src/components/DashboardMedia/DashboardMediaData.js @@ -2,36 +2,39 @@ import { useSearchParams } from 'react-router-dom' // FPCC import { useSiteStore } from 'context/SiteContext' -import { getFriendlyDocType } from 'common/utils/stringHelpers' +import { + TYPE_AUDIO, + TYPE_IMAGE, + TYPE_VIDEO, + TYPES, +} from 'common/constants/searchParams' function DashboardMediaData() { const { site } = useSiteStore() const [searchParams] = useSearchParams() - const docType = searchParams.get('type') - ? getFriendlyDocType({ docType: searchParams.get('type') }) - : null + const docType = searchParams.get(`${TYPES}`) || null const tileContent = [ { icon: 'Microphone', name: 'Audio', description: 'Manage your audio files', - href: 'browser?type=audio', + href: `browser?${TYPES}=${TYPE_AUDIO}`, iconColor: 'songText', }, { icon: 'Images', name: 'Images', description: 'Manage your images', - href: 'browser?type=images', + href: `browser?${TYPES}=${TYPE_IMAGE}`, iconColor: 'wordText', }, { icon: 'Video', name: 'Videos', description: 'Manage your videos', - href: 'browser?type=videos', + href: `browser?${TYPES}=${TYPE_VIDEO}`, iconColor: 'storyText', }, ] diff --git a/src/components/MediaBrowser/MediaBrowserContainerNonModal.js b/src/components/MediaBrowser/MediaBrowserContainerNonModal.js index 97be29bb..de5f1575 100644 --- a/src/components/MediaBrowser/MediaBrowserContainerNonModal.js +++ b/src/components/MediaBrowser/MediaBrowserContainerNonModal.js @@ -20,7 +20,7 @@ function MediaBrowserContainerNonModal({ docType }) { isLoadingEntries, loadRef, loadLabel, - friendlyDocTypeLabel, + docTypePlural, } = MediaBrowserData({ docType }) const hasResults = !!( @@ -36,7 +36,7 @@ function MediaBrowserContainerNonModal({ docType }) { return ( - {friendlyDocTypeLabel} + {docTypePlural} { event.preventDefault() setSearchInputValue(event.target.value) @@ -42,55 +46,24 @@ function MediaBrowserData({ docType }) { setSearchTerm(searchInputValue) if (searchInputValue) { navigate( - `/${sitename}/dashboard/media/browser?type=${friendlyDocTypeLabel}&q=${searchInputValue}`, + `/${sitename}/dashboard/media/browser?types=${docType}&q=${searchInputValue}`, ) } else { - navigate( - `/${sitename}/dashboard/media/browser?type=${friendlyDocTypeLabel}`, - ) + navigate(`/${sitename}/dashboard/media/browser?types=${docType}`) } } - // Data fetch - const { - data, - fetchNextPage, - hasNextPage, - isError, - isFetchingNextPage, - isInitialLoading, - } = useInfiniteQuery( - [`${docType}-search`, searchTerm], - ({ pageParam = 1 }) => - api.media.get({ - sitename: site?.sitename, - docType: friendlyDocTypeLabel, - pageParam, - }), - { - // The query will not execute until the siteId exists - enabled: !!site?.id, - getNextPageParam: (lastPage) => lastPage.nextPage, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - ) - useEffect(() => { if (!currentFile && data?.pages?.[0]?.results) { - const firstFile = mediaAdaptor({ - type: docType, - data: data?.pages?.[0]?.results?.[0], - }) + const firstFile = data?.pages?.[0]?.results?.[0] setCurrentFile(firstFile) } }, [currentFile, data, docType]) - const infiniteScroll = { fetchNextPage, hasNextPage, isFetchingNextPage } useIntersectionObserver({ target: loadRef, - onIntersect: fetchNextPage, - enabled: hasNextPage, + onIntersect: infiniteScroll?.fetchNextPage, + enabled: infiniteScroll?.hasNextPage, }) const getLoadLabel = () => { @@ -115,7 +88,7 @@ function MediaBrowserData({ docType }) { currentFile, setCurrentFile, loadLabel: getLoadLabel(), - friendlyDocTypeLabel, + docTypePlural, } } diff --git a/src/components/MediaCrud/MediaCrudData.js b/src/components/MediaCrud/MediaCrudData.js index ebc2ef53..14984437 100644 --- a/src/components/MediaCrud/MediaCrudData.js +++ b/src/components/MediaCrud/MediaCrudData.js @@ -1,12 +1,8 @@ -import { useState, useRef } from 'react' -import { useNavigate, useLocation, useParams } from 'react-router-dom' -import { useInfiniteQuery } from '@tanstack/react-query' +import { useState } from 'react' +import { useSearchParams } from 'react-router-dom' import PropTypes from 'prop-types' // FPCC -import { useSiteStore } from 'context/SiteContext' -import useIntersectionObserver from 'common/hooks/useIntersectionObserver' -import api from 'services/api' import { getFriendlyDocType } from 'common/utils/stringHelpers' import { SUPPORTED_IMAGE_EXTENSIONS, @@ -15,89 +11,38 @@ import { AUDIO, IMAGE, VIDEO, + TYPES, } from 'common/constants' +import useSearchLoader from 'common/dataHooks/useSearchLoader' +import { useSiteStore } from 'context/SiteContext' function MediaCrudData({ docType, maxFiles }) { const { site } = useSiteStore() - const { search } = useLocation() - const { sitename } = useParams() - const navigate = useNavigate() + const [searchParams] = useSearchParams() - const searchParamsQuery = new URLSearchParams(search).get('q') - ? new URLSearchParams(search).get('q') - : '' - const pluralDocTypeLabel = getFriendlyDocType({ - docType, - plural: true, - }) + const docTypePlural = getFriendlyDocType({ docType, plural: true }) const [selectedMedia, setSelectedMedia] = useState([]) + const searchParamsQuery = searchParams.get('q') || '' const [searchTerm, setSearchTerm] = useState(searchParamsQuery) const [searchInputValue, setSearchInputValue] = useState(searchParamsQuery) - // Data Fetch - const { - data, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - isInitialLoading, - refetch, - } = useInfiniteQuery( - [`${pluralDocTypeLabel}-search`, searchTerm], - ({ pageParam = 1 }) => - api.media.get({ - sitename, - docType: pluralDocTypeLabel, - pageParam, - }), - { - // The query will not execute until the siteId exists - enabled: !!site?.id, - getNextPageParam: (lastPage) => lastPage.nextPage, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - ) - - const infiniteScroll = { fetchNextPage, hasNextPage, isFetchingNextPage } - - const loadRef = useRef(null) - useIntersectionObserver({ - target: loadRef, - onIntersect: fetchNextPage, - enabled: hasNextPage, + const _searchParams = new URLSearchParams({ + q: searchTerm, + [TYPES]: docType, + }) + const { data, infiniteScroll, isInitialLoading, loadRef } = useSearchLoader({ + searchParams: _searchParams, }) - - const getLoadLabel = () => { - if (infiniteScroll?.isFetchingNextPage) { - return 'Loading more...' - } - if (infiniteScroll?.hasNextPage) { - return 'Load more' - } - return 'End of results.' - } const handleTextFieldChange = (event) => { event.preventDefault() setSearchInputValue(event.target.value) } + const handleSearchSubmit = (event) => { event.preventDefault() setSearchTerm(searchInputValue) - if (docType) { - // If in modal trigger refetch NOT navigate - refetch() - } else if (searchInputValue) { - navigate( - `/${sitename}/dashboard/media/browser?type=${pluralDocTypeLabel}&q=${searchInputValue}`, - ) - } else if (!searchInputValue) { - navigate( - `/${sitename}/dashboard/media/browser?type=${pluralDocTypeLabel}`, - ) - } } const mediaSelectHandler = (docId) => { @@ -135,18 +80,21 @@ function MediaCrudData({ docType, maxFiles }) { } })() - const docTypeLabelPlural = getFriendlyDocType({ - docType, - plural: true, - isAnd: true, - }) + const getLoadLabel = () => { + if (infiniteScroll?.isFetchingNextPage) { + return 'Loading more...' + } + if (infiniteScroll?.hasNextPage) { + return 'Load more' + } + return 'End of results.' + } return { site, handleSearchSubmit, handleTextFieldChange, infiniteScroll, - isLoadingEntries: isInitialLoading, loadRef, fetchedMedia: data, searchValue: searchInputValue, @@ -155,7 +103,8 @@ function MediaCrudData({ docType, maxFiles }) { setSelectedMedia, mediaSelectHandler, clearSelectedMedia, - docTypeLabelPlural, + docTypeLabelPlural: docTypePlural, + isLoadingEntries: isInitialLoading, extensionList, } } diff --git a/src/components/MediaDetails/MediaDetailsAudio.js b/src/components/MediaDetails/MediaDetailsAudio.js index 3712615f..9f9dceda 100644 --- a/src/components/MediaDetails/MediaDetailsAudio.js +++ b/src/components/MediaDetails/MediaDetailsAudio.js @@ -37,7 +37,9 @@ function MediaDetailsAudio({ file }) { className="py-3 flex justify-between text-sm font-medium" >
{key}
-
{file[key]}
+
+ {file[key]} +
) } diff --git a/src/components/MediaDetails/MediaDetailsVisual.js b/src/components/MediaDetails/MediaDetailsVisual.js index 67222e99..ae9066b1 100644 --- a/src/components/MediaDetails/MediaDetailsVisual.js +++ b/src/components/MediaDetails/MediaDetailsVisual.js @@ -52,7 +52,9 @@ function MediaDetailsVisual({ file, docType }) {
{key.charAt(0).toUpperCase() + key.slice(1)}
-
{file[key]}
+
+ {file[key]} +
) } diff --git a/src/components/MediaItemsLayout/MediaItemsLayoutAudio.js b/src/components/MediaItemsLayout/MediaItemsLayoutAudio.js index e648a03e..8cae7305 100644 --- a/src/components/MediaItemsLayout/MediaItemsLayoutAudio.js +++ b/src/components/MediaItemsLayout/MediaItemsLayoutAudio.js @@ -2,10 +2,8 @@ import React from 'react' import PropTypes from 'prop-types' // FPCC -import { mediaAdaptor } from 'common/dataAdaptors/mediaAdaptors' import AudioNative from 'components/AudioNative' import getIcon from 'common/utils/getIcon' -import { AUDIO } from 'common/constants' function MediaItemsLayoutAudio({ data, @@ -25,35 +23,31 @@ function MediaItemsLayoutAudio({ return (
- {data?.pages !== undefined && data?.pages?.[0]?.results?.length > 0 && ( -
- - - - {selection && ( - - )} +
+
- {selection} -
+ + + {selection && ( - - - - - - {data?.pages?.map((page) => ( - - {page.results.map((rawAudioDoc) => { - const audioFile = mediaAdaptor({ - type: AUDIO, - data: rawAudioDoc, - }) + )} + + + + + + + {data?.pages?.[0]?.results?.length && + data?.pages?.map((page) => ( + + {page.results.map((audioFile) => { if ( savedMedia?.some((elemId) => elemId === audioFile?.id) ) { @@ -63,7 +57,7 @@ function MediaItemsLayoutAudio({ } return ( - - @@ -101,20 +95,19 @@ function MediaItemsLayoutAudio({ })} ))} - -
- Audio + {selection} - Title - - Description -
+ Audio + + Title + + Description +
+ {audioFile.title} + {audioFile?.description}
-
- -
+ + +
+
- )} +
) } diff --git a/src/components/MediaItemsLayout/MediaItemsLayoutVisual.js b/src/components/MediaItemsLayout/MediaItemsLayoutVisual.js index a9d06135..ca5e6e28 100644 --- a/src/components/MediaItemsLayout/MediaItemsLayoutVisual.js +++ b/src/components/MediaItemsLayout/MediaItemsLayoutVisual.js @@ -2,12 +2,9 @@ import React from 'react' import PropTypes from 'prop-types' // FPCC -import { IMAGE, VIDEO } from 'common/constants' -import { mediaAdaptor } from 'common/dataAdaptors/mediaAdaptors' import getIcon from 'common/utils/getIcon' function MediaItemsLayoutVisual({ data, - docType, infiniteScroll, currentFile, setCurrentFile, @@ -28,11 +25,7 @@ function MediaItemsLayoutVisual({ data?.pages?.map((page, index) => ( // eslint-disable-next-line react/no-array-index-key - {page.results.map((rawDocument) => { - const doc = mediaAdaptor({ - type: docType, - data: rawDocument, - }) + {page.results.map((doc) => { if (savedMedia?.some((elemId) => elemId === doc?.id)) { // If a media file is already attached to the document // it will not be presented as a choice in the selectMedia dialog box @@ -100,10 +93,9 @@ function MediaItemsLayoutVisual({ ) } // PROPTYPES -const { func, array, object, oneOf, string, bool } = PropTypes +const { func, array, object, string, bool } = PropTypes MediaItemsLayoutVisual.propTypes = { data: object, - docType: oneOf([IMAGE, VIDEO]), infiniteScroll: object, currentFile: object, setCurrentFile: func, diff --git a/src/components/SearchSelector/SearchSelectorPresentation.js b/src/components/SearchSelector/SearchSelectorPresentation.js index 6ed68934..f420f78f 100644 --- a/src/components/SearchSelector/SearchSelectorPresentation.js +++ b/src/components/SearchSelector/SearchSelectorPresentation.js @@ -22,35 +22,31 @@ function SearchSelectorPresentation({
{/* Content area */}
- {!['audio', 'image', 'video'].some((v) => - searchPromptText.includes(v), - ) && ( -
-
-
- - -
-
-
- )} +
+
+
+ + +
+
+