import { GetStaticProps } from "next"; import { useEffect, useMemo, useState } from "react"; import { useBoolean } from "usehooks-ts"; import { z } from "zod"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Switch } from "components/Inputs/Switch"; import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { SubPanel } from "components/Containers/SubPanel"; import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { getOpenGraph } from "helpers/openGraph"; import { HorizontalLine } from "components/HorizontalLine"; import { CustomSearchResponse, meiliSearch } from "helpers/search"; import { MeiliIndices, MeiliVideo } from "shared/meilisearch-graphql-typings/meiliTypes"; import { PreviewCard } from "components/PreviewCard"; import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts"; import { getVideoThumbnailURL } from "helpers/videos"; import { useTypedRouter } from "hooks/useTypedRouter"; import { Select } from "components/Inputs/Select"; import { sendAnalytics } from "helpers/analytics"; import { Button } from "components/Inputs/Button"; import { Paginator } from "components/Containers/Paginator"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; /* * ╭─────────────╮ * ────────────────────────────────────────╯ CONSTANTS ╰────────────────────────────────────────── */ const DEFAULT_FILTERS_STATE = { searchName: "", page: 1, sortingMethod: 1, onlyShowGone: false, keepInfoVisible: true, }; const queryParamSchema = z.object({ query: z.coerce.string().optional(), page: z.coerce.number().positive().optional(), sort: z.coerce.number().min(0).max(5).optional(), gone: z.coerce.boolean().optional(), }); /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── */ interface Props extends AppLayoutRequired {} const Videos = ({ ...otherProps }: Props): JSX.Element => { const { format } = useFormat(); const hoverable = useDeviceSupportsHover(); const router = useTypedRouter(queryParamSchema); const sortingMethods = useMemo( () => [ { meiliAttribute: "sortable_published_date:asc", displayedName: format("oldest") }, { meiliAttribute: "sortable_published_date:desc", displayedName: format("newest") }, { meiliAttribute: "views:asc", displayedName: format("least_popular") }, { meiliAttribute: "views:desc", displayedName: format("most_popular") }, { meiliAttribute: "duration:asc", displayedName: format("shortest") }, { meiliAttribute: "duration:desc", displayedName: format("longest") }, ], [format] ); const { value: keepInfoVisible, toggle: toggleKeepInfoVisible, setValue: setKeepInfoVisible, } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); const { value: onlyShowGone, toggle: toggleOnlyShowGone, setValue: setOnlyShowGone, } = useBoolean(router.query.gone ?? DEFAULT_FILTERS_STATE.onlyShowGone); const [query, setQuery] = useState( router.query.query ?? DEFAULT_FILTERS_STATE.searchName ); const [page, setPage] = useState(router.query.page ?? DEFAULT_FILTERS_STATE.page); const [sortingMethod, setSortingMethod] = useState( router.query.sort ?? DEFAULT_FILTERS_STATE.sortingMethod ); const [videos, setVideos] = useState>(); useEffect(() => { const fetchVideos = async () => { const currentSortingMethod = sortingMethods[sortingMethod]; const searchResult = await meiliSearch(MeiliIndices.VIDEOS, query, { hitsPerPage: 25, page, attributesToRetrieve: [ "title", "channel", "uid", "published_date", "views", "duration", "description", ], attributesToHighlight: ["title", "channel", "description"], attributesToCrop: ["description"], sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined, filter: onlyShowGone ? ["gone = true"] : undefined, }); setVideos(searchResult); }; fetchVideos(); }, [query, page, sortingMethod, onlyShowGone, sortingMethods]); useEffect(() => { if (router.isReady) router.updateQuery({ page, query, sort: sortingMethod, gone: onlyShowGone, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, query, sortingMethod, onlyShowGone, router.isReady]); useEffect(() => { if (router.isReady) { if (isDefined(router.query.page)) setPage(router.query.page); if (isDefined(router.query.query)) setQuery(router.query.query); if (isDefined(router.query.sort)) setSortingMethod(router.query.sort); if (isDefined(router.query.gone)) setOnlyShowGone(router.query.gone); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); const subPanel = ( { setPage(1); setQuery(newQuery); if (isDefinedAndNotEmpty(newQuery)) { sendAnalytics("Videos", "Change search term"); } else { sendAnalytics("Videos", "Clear search term"); } }} />