diff --git a/front/packages/ui/src/browse/header.tsx b/front/packages/ui/src/browse/header.tsx index ba75cabd..e37a2e89 100644 --- a/front/packages/ui/src/browse/header.tsx +++ b/front/packages/ui/src/browse/header.tsx @@ -18,28 +18,18 @@ * along with Kyoo. If not, see . */ -import { - Chip, - HR, - Icon, - IconButton, - Menu, - P, - PressableFeedback, - tooltip, - ts, -} from "@kyoo/primitives"; +import { HR, Icon, IconButton, Menu, P, PressableFeedback, tooltip, ts } from "@kyoo/primitives"; import ArrowDownward from "@material-symbols/svg-400/rounded/arrow_downward.svg"; import ArrowUpward from "@material-symbols/svg-400/rounded/arrow_upward.svg"; +import FilterList from "@material-symbols/svg-400/rounded/filter_list.svg"; import GridView from "@material-symbols/svg-400/rounded/grid_view.svg"; import Sort from "@material-symbols/svg-400/rounded/sort.svg"; -import Style from "@material-symbols/svg-400/rounded/style.svg"; import ViewList from "@material-symbols/svg-400/rounded/view_list.svg"; import { forwardRef } from "react"; import { useTranslation } from "react-i18next"; import { type PressableProps, View } from "react-native"; import { useYoshiki } from "yoshiki/native"; -import { Layout, SearchSort, SortOrd } from "./types"; +import { Layout, type MediaType, MediaTypeAll, SearchSort, SortOrd } from "./types"; const SortTrigger = forwardRef(function SortTrigger( { sortKey, ...props }, @@ -60,11 +50,34 @@ const SortTrigger = forwardRef(funct ); }); +const MediaTypeTrigger = forwardRef( + function MediaTypeTrigger({ mediaType, ...props }, ref) { + const { css } = useYoshiki(); + const { t } = useTranslation(); + const labelKey = + mediaType !== MediaTypeAll ? `browse.mediatypekey.${mediaType.key}` : "browse.mediatypelabel"; + const icon = mediaType !== MediaTypeAll ? mediaType?.icon ?? FilterList : FilterList; + return ( + + +

{t(labelKey as any)}

+
+ ); + }, +); + export const BrowseSettings = ({ availableSorts, sortKey, sortOrd, setSort, + availableMediaTypes, + mediaType, + setMediaType, layout, setLayout, }: { @@ -72,13 +85,14 @@ export const BrowseSettings = ({ sortKey: string; sortOrd: SortOrd; setSort: (sort: string, ord: SortOrd) => void; + availableMediaTypes: MediaType[]; + mediaType: MediaType; + setMediaType: (mediaType: MediaType) => void; layout: Layout; setLayout: (layout: Layout) => void; }) => { const { css, theme } = useYoshiki(); const { t } = useTranslation(); - const filters: string[] = []; - // TODO: implement filters in the front. return ( - {filters.length !== 0 && ( - - - {filters.map((x) => ( - - ))} - - )} {availableSorts.map((x) => ( @@ -133,6 +139,19 @@ export const BrowseSettings = ({ {...css({ padding: ts(0.5), marginY: "auto" })} /> + + + {availableMediaTypes.map((x) => ( + setMediaType(x)} + /> + ))} + + ); }; diff --git a/front/packages/ui/src/browse/index.tsx b/front/packages/ui/src/browse/index.tsx index be1be7c9..8cc69bf4 100644 --- a/front/packages/ui/src/browse/index.tsx +++ b/front/packages/ui/src/browse/index.tsx @@ -32,9 +32,17 @@ import { DefaultLayout } from "../layout"; import { ItemGrid } from "./grid"; import { BrowseSettings } from "./header"; import { ItemList } from "./list"; -import { Layout, SortBy, SortOrd } from "./types"; +import { + Layout, + type MediaType, + MediaTypeAll, + MediaTypeKey, + MediaTypes, + SortBy, + SortOrd, +} from "./types"; -const { useParam } = createParam<{ sortBy?: string }>(); +const { useParam } = createParam<{ sortBy?: string; mediaType?: string }>(); export const itemMap = ( item: LibraryItem, @@ -52,27 +60,45 @@ export const itemMap = ( item.kind === "show" ? item.watchStatus?.unseenEpisodesCount ?? item.episodesCount! : null, }); -const query = (sortKey?: SortBy, sortOrd?: SortOrd): QueryIdentifier => ({ - parser: LibraryItemP, - path: ["items"], - infinite: true, - params: { - sortBy: sortKey ? `${sortKey}:${sortOrd ?? "asc"}` : "name:asc", - fields: ["watchStatus", "episodesCount"], - }, -}); +export const createFilterString = (mediaType: MediaType): string | undefined => { + return mediaType !== MediaTypeAll ? `kind eq ${mediaType.key}` : undefined; +}; + +const query = ( + mediaType: MediaType, + sortKey?: SortBy, + sortOrd?: SortOrd, +): QueryIdentifier => { + return { + parser: LibraryItemP, + path: ["items"], + infinite: true, + params: { + sortBy: sortKey ? `${sortKey}:${sortOrd ?? "asc"}` : "name:asc", + filter: createFilterString(mediaType), + fields: ["watchStatus", "episodesCount"], + }, + }; +}; + +export const getMediaTypeFromParam = (mediaTypeParam?: string): MediaType => { + const mediaTypeKey = (mediaTypeParam as MediaTypeKey) || MediaTypeKey.All; + return MediaTypes.find((t) => t.key === mediaTypeKey) ?? MediaTypeAll; +}; export const BrowsePage: QueryPage = () => { const [sort, setSort] = useParam("sortBy"); + const [mediaTypeParam, setMediaTypeParam] = useParam("mediaType"); const sortKey = (sort?.split(":")[0] as SortBy) || SortBy.Name; const sortOrd = (sort?.split(":")[1] as SortOrd) || SortOrd.Asc; const [layout, setLayout] = useState(Layout.Grid); + const mediaType = getMediaTypeFromParam(mediaTypeParam); const LayoutComponent = layout === Layout.Grid ? ItemGrid : ItemList; return ( { setSort={(key, ord) => { setSort(`${key}:${ord}`); }} + mediaType={mediaType} + availableMediaTypes={MediaTypes} + setMediaType={(mediaType) => { + setMediaTypeParam(mediaType.key); + }} layout={layout} setLayout={setLayout} /> @@ -94,6 +125,7 @@ export const BrowsePage: QueryPage = () => { BrowsePage.getLayout = DefaultLayout; -BrowsePage.getFetchUrls = ({ sortBy }) => [ - query(sortBy?.split("-")[0] as SortBy, sortBy?.split("-")[1] as SortOrd), -]; +BrowsePage.getFetchUrls = ({ mediaType, sortBy }) => { + const mediaTypeObj = getMediaTypeFromParam(mediaType); + return [query(mediaTypeObj, sortBy?.split("-")[0] as SortBy, sortBy?.split("-")[1] as SortOrd)]; +}; diff --git a/front/packages/ui/src/browse/types.ts b/front/packages/ui/src/browse/types.ts index f44181e0..c914ab26 100644 --- a/front/packages/ui/src/browse/types.ts +++ b/front/packages/ui/src/browse/types.ts @@ -18,6 +18,13 @@ * along with Kyoo. If not, see . */ +import Collection from "@material-symbols/svg-400/rounded/collections_bookmark.svg"; +import Movie from "@material-symbols/svg-400/rounded/movie.svg"; +import TV from "@material-symbols/svg-400/rounded/tv.svg"; +import All from "@material-symbols/svg-400/rounded/view_headline.svg"; +import type { ComponentType } from "react"; +import type { SvgProps } from "react-native-svg"; + export enum SortBy { Name = "name", StartAir = "startAir", @@ -42,3 +49,36 @@ export enum Layout { Grid, List, } + +export enum MediaTypeKey { + All = "all", + Movie = "movie", + Show = "show", + Collection = "collection", +} + +export interface MediaType { + key: MediaTypeKey; + icon: ComponentType; +} + +export const MediaTypeAll: MediaType = { + key: MediaTypeKey.All, + icon: All, +}; + +export const MediaTypes: MediaType[] = [ + MediaTypeAll, + { + key: MediaTypeKey.Movie, + icon: Movie, + }, + { + key: MediaTypeKey.Show, + icon: TV, + }, + { + key: MediaTypeKey.Collection, + icon: Collection, + }, +]; diff --git a/front/packages/ui/src/search/index.tsx b/front/packages/ui/src/search/index.tsx index 89ba727e..fd1283cc 100644 --- a/front/packages/ui/src/search/index.tsx +++ b/front/packages/ui/src/search/index.tsx @@ -23,17 +23,18 @@ import { usePageStyle } from "@kyoo/primitives"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { createParam } from "solito"; -import { itemMap } from "../browse"; +import { createFilterString, getMediaTypeFromParam, itemMap } from "../browse"; import { ItemGrid } from "../browse/grid"; import { BrowseSettings } from "../browse/header"; import { ItemList } from "../browse/list"; -import { Layout, SearchSort, SortOrd } from "../browse/types"; +import { Layout, type MediaType, MediaTypes, SearchSort, SortOrd } from "../browse/types"; import { InfiniteFetch } from "../fetch-infinite"; import { DefaultLayout } from "../layout"; -const { useParam } = createParam<{ sortBy?: string }>(); +const { useParam } = createParam<{ sortBy?: string; mediaType?: string }>(); const query = ( + mediaType: MediaType, query?: string, sortKey?: SearchSort, sortOrd?: SortOrd, @@ -43,6 +44,7 @@ const query = ( infinite: true, params: { q: query, + filter: createFilterString(mediaType), sortBy: sortKey && sortKey !== SearchSort.Relevance ? `${sortKey}:${sortOrd ?? "asc"}` : undefined, }, @@ -52,15 +54,17 @@ export const SearchPage: QueryPage<{ q?: string }> = ({ q }) => { const pageStyle = usePageStyle(); const { t } = useTranslation(); const [sort, setSort] = useParam("sortBy"); + const [mediaTypeParam, setMediaTypeParam] = useParam("mediaType"); const sortKey = (sort?.split(":")[0] as SearchSort) || SearchSort.Relevance; const sortOrd = (sort?.split(":")[1] as SortOrd) || SortOrd.Asc; const [layout, setLayout] = useState(Layout.Grid); + const mediaType = getMediaTypeFromParam(mediaTypeParam); const LayoutComponent = layout === Layout.Grid ? ItemGrid : ItemList; return ( = ({ q }) => { setSort={(key, ord) => { setSort(`${key}:${ord}`); }} + mediaType={mediaType} + availableMediaTypes={MediaTypes} + setMediaType={(mediaType) => { + setMediaTypeParam(mediaType.key); + }} layout={layout} setLayout={setLayout} /> @@ -84,6 +93,9 @@ export const SearchPage: QueryPage<{ q?: string }> = ({ q }) => { }; SearchPage.getLayout = DefaultLayout; -SearchPage.getFetchUrls = ({ q, sortBy }) => [ - query(q, sortBy?.split("-")[0] as SearchSort, sortBy?.split("-")[1] as SortOrd), -]; +SearchPage.getFetchUrls = ({ q, sortBy, mediaType }) => { + const mediaTypeObj = getMediaTypeFromParam(mediaType); + return [ + query(mediaTypeObj, q, sortBy?.split("-")[0] as SearchSort, sortBy?.split("-")[1] as SortOrd), + ]; +}; diff --git a/front/translations/en.json b/front/translations/en.json index 059f58f0..38004535 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -43,6 +43,14 @@ "season": "Season {{number}}" }, "browse": { + "mediatypekey": { + "all": "All", + "movie": "Movies", + "show": "Series", + "collection": "Collection" + }, + "mediatype-tt": "Media Type", + "mediatypelabel": "Media Type", "sortby": "Sort by {{key}}", "sortby-tt": "Sort by", "sortkey": {