diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 7a563b0479..01f07ca0f3 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -110,6 +110,7 @@ "recommendations": "Top of the month", "yourScores": "Your scores", "unconnected": "To connect", + "goodShortVideos": "Short videos", "selectAVideo": "Select a video", "select": "Select", "youtubeVideoUnavailable": "YouTube video unavailable.", @@ -122,6 +123,7 @@ "recommendations": "Videos recommended by the community appear here.", "toConnect": "Videos isolated from your other comparisons appear here. Compare them to improve the recommendations.", "rateLater": "Your rate-later videos appear here. You can add some to your list by clicking on the '+' sign on the video cards. You can also add them directly from <2>the extension.", + "goodShortVideos": "A random sample of short videos recommended by the community.", "errorOnLoading": "An error occurred while loading the results.", "emptyList": "This list is empty." }, diff --git a/frontend/public/locales/fr/translation.json b/frontend/public/locales/fr/translation.json index 63858bc207..602a47ae5a 100644 --- a/frontend/public/locales/fr/translation.json +++ b/frontend/public/locales/fr/translation.json @@ -114,6 +114,7 @@ "recommendations": "Top du mois", "yourScores": "Vos scores", "unconnected": "À relier", + "goodShortVideos": "Vidéos courtes", "selectAVideo": "Sélectionner une vidéo", "select": "Sélectionner", "youtubeVideoUnavailable": "Vidéo YouTube indisponible.", @@ -126,6 +127,7 @@ "recommendations": "Les vidéos recommandées par la communauté apparaissent ici.", "toConnect": "Les vidéos isolées du reste de vos comparaisons apparaissent ici. Comparez-les pour améliorer les recommandations.", "rateLater": "Vos vidéos à comparer plus tard apparaissent ici. Vous pouvez en ajouter sur le site web via le bouton '+'. Vous pouvez aussi les ajouter directement depuis YouTube grâce à <2>l'extension.", + "goodShortVideos": "Un échantillon aléatoire de vidéos courtes recommandées par la communauté.", "errorOnLoading": "Une erreur est survenue lors du chargement des résultats.", "emptyList": "Cette liste est vide." }, diff --git a/frontend/src/features/entity_selector/EntitySelectButton.tsx b/frontend/src/features/entity_selector/EntitySelectButton.tsx index 97e0265a27..d0b7ccceaa 100644 --- a/frontend/src/features/entity_selector/EntitySelectButton.tsx +++ b/frontend/src/features/entity_selector/EntitySelectButton.tsx @@ -16,10 +16,11 @@ import { } from 'src/utils/constants'; import { UsersService } from 'src/services/openapi'; import { getRecommendations } from 'src/utils/api/recommendations'; +import { getGoodShortVideos } from 'src/utils/api/goodShortVideos'; import { getAllCandidates } from 'src/utils/polls/presidentielle2022'; import SelectorListBox, { EntitiesTab } from './EntityTabsBox'; import SelectorPopper from './SelectorPopper'; -import { useLoginState } from 'src/hooks'; +import { useLoginState, usePreferredLanguages } from 'src/hooks'; import { ComparisonsCountContext } from 'src/pages/comparisons/Comparison'; import { EntityObject } from 'src/utils/types'; @@ -55,6 +56,8 @@ const VideoInput = ({ const comparisonsCount = useContext(ComparisonsCountContext).comparisonsCount; + const preferredLanguages = usePreferredLanguages({ pollName }); + const handleOptionClick = (uid: string) => { onChange(uid); setSuggestionsOpen(false); @@ -157,8 +160,16 @@ const VideoInput = ({ }, disabled: !isLoggedIn || !otherUid, }, + { + name: 'good-short-videos', + label: t('entitySelector.goodShortVideos'), + fetch: async () => { + return await getGoodShortVideos({ preferredLanguages }); + }, + disabled: !isLoggedIn, + }, ], - [t, isLoggedIn, otherUid, pollName] + [t, isLoggedIn, otherUid, pollName, preferredLanguages] ); return ( diff --git a/frontend/src/features/entity_selector/EntityTabsBox.tsx b/frontend/src/features/entity_selector/EntityTabsBox.tsx index 4ab53fd12c..8ae059cf73 100644 --- a/frontend/src/features/entity_selector/EntityTabsBox.tsx +++ b/frontend/src/features/entity_selector/EntityTabsBox.tsx @@ -89,6 +89,7 @@ const TabInfo = ({ . ), + 'good-short-videos': t('tabsBox.goodShortVideos'), }; if (!(messageKey in descriptionMessages)) { @@ -105,7 +106,7 @@ const TabInfo = ({ const EntityTabsBox = ({ tabs, onSelectEntity, - width = 'min(700px, 100vw)', + width = 'min(760px, 100vw)', elevation = 1, maxHeight = '40vh', withLink = false, diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index fab6999e32..916195f012 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -7,3 +7,4 @@ export { useNotifications } from './useNotifications'; export { useRefreshSettings } from './useRefreshSettings'; export { useScrollToLocation } from './useScrollToLocation'; export { useStats } from './useStats'; +export { usePreferredLanguages } from './usePreferredLanguages'; diff --git a/frontend/src/hooks/usePreferredLanguages.ts b/frontend/src/hooks/usePreferredLanguages.ts new file mode 100644 index 0000000000..c7fefb3bc6 --- /dev/null +++ b/frontend/src/hooks/usePreferredLanguages.ts @@ -0,0 +1,15 @@ +import { useSelector } from 'react-redux'; +import { selectSettings } from 'src/features/settings/userSettingsSlice'; +import { PollUserSettingsKeys } from 'src/utils/types'; +import { recommendationsLanguagesFromNavigator } from 'src/utils/recommendationsLanguages'; + +export const usePreferredLanguages = ({ pollName }: { pollName: string }) => { + const userSettings = useSelector(selectSettings).settings; + let preferredLanguages = + userSettings?.[pollName as PollUserSettingsKeys] + ?.recommendations__default_languages; + + preferredLanguages ??= recommendationsLanguagesFromNavigator().split(','); + + return preferredLanguages; +}; diff --git a/frontend/src/utils/api/goodShortVideos.ts b/frontend/src/utils/api/goodShortVideos.ts new file mode 100644 index 0000000000..a959979de5 --- /dev/null +++ b/frontend/src/utils/api/goodShortVideos.ts @@ -0,0 +1,38 @@ +import { PollsService } from 'src/services/openapi'; +import { Recommendation } from 'src/services/openapi'; + +const shuffleRecommendations = (array: Recommendation[]) => { + // https://stackoverflow.com/a/12646864/188760 + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +}; + +export const getGoodShortVideos = async ({ + preferredLanguages, +}: { + preferredLanguages: string[]; +}): Promise => { + const metadata: Record = {}; + + const minutesMax = 5; + const top = 100; + + metadata['duration:lte:int'] = (60 * minutesMax).toString(); + metadata['language'] = preferredLanguages; + + const videos = await PollsService.pollsRecommendationsList({ + name: 'videos', + metadata: metadata, + limit: top, + excludeComparedEntities: true, + }).then((data) => data.results ?? []); + + const returnCount = 20; + + shuffleRecommendations(videos); + videos.splice(returnCount); + + return videos; +};