diff --git a/src/components/carouselSlide/index.jsx b/src/components/carouselSlide/index.jsx new file mode 100644 index 00000000..98813c21 --- /dev/null +++ b/src/components/carouselSlide/index.jsx @@ -0,0 +1,415 @@ +/** @format */ +import { Blurhash } from "react-blurhash"; + +import { motion } from "framer-motion"; + +import Paper from "@mui/material/Paper"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Stack from "@mui/material/Stack"; +import Chip from "@mui/material/Chip"; +import LinearProgress from "@mui/material/LinearProgress"; +import Button from "@mui/material/Button"; +import IconButton from "@mui/material/IconButton"; + +import { pink, yellow } from "@mui/material/colors"; + +import { ErrorBoundary } from "react-error-boundary"; + +import CarouselSlideError from "../errors/carousel"; +import { MdiStar } from "../icons/mdiStar"; +import { endsAt, getRuntime } from "../../utils/date/time"; +import { useNavigate } from "react-router-dom"; +import { MediaTypeIconCollection } from "../utils/iconsCollection"; +import { MdiPlayOutline } from "../icons/mdiPlayOutline"; +import { MdiChevronRight } from "../icons/mdiChevronRight"; +import { MdiHeart } from "../icons/mdiHeart"; +import { MdiHeartOutline } from "../icons/mdiHeartOutline"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api/user-library-api"; +import { getUserApi } from "@jellyfin/sdk/lib/utils/api/user-api"; +import { useSnackbar } from "notistack"; + +const CarouselSlide = ({ item }) => { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const { enqueueSnackbar } = useSnackbar(); + + const user = useQuery({ + queryKey: ["user"], + queryFn: async () => { + let usr = await getUserApi(window.api).getCurrentUser(); + return usr.data; + }, + networkMode: "always", + }); + + const handleLiking = async () => { + let result; + console.log(item.UserData.IsFavorite); + if (item.UserData.IsFavorite) { + result = await getUserLibraryApi(window.api).unmarkFavoriteItem({ + userId: user.data.Id, + itemId: item.Id, + }); + } else if (!item.UserData.IsFavorite) { + result = await getUserLibraryApi(window.api).markFavoriteItem({ + userId: user.data.Id, + itemId: item.Id, + }); + } + }; + + const favouriteButtonMutation = useMutation({ + mutationFn: handleLiking, + onMutate: () => { + queryClient.cancelQueries(["home", "latestMedia"]); + const currentLatestMedia = queryClient.getQueryData([ + "home", + "latestMedia", + ]); + queryClient.setQueryData(["home", "latestMedia"], (oldMedia) => + oldMedia.map((oitem) => { + if (oitem.Id == item.Id) { + return { + ...oitem, + UserData: { + ...oitem.UserData, + IsFavorite: true, + }, + }; + } + return oitem; + }), + ); + return { currentLatestMedia }; + }, + onError: (error) => { + enqueueSnackbar( + `Error updating item. Please check your connection`, + { variant: "error" }, + ); + console.error(error); + }, + onSuccess: () => { + queryClient.invalidateQueries(["home", "latestMedia"]); + }, + }); + + return ( + }> + +
+ {Object.keys(item.ImageBlurHashes.Backdrop).length != + 0 && ( + + )} +
+
+ {MediaTypeIconCollection[item.Type]} +
+
+ + + {!item.ImageTags.Logo ? ( + item.Name + ) : ( + + )} + + + } + className="hero-carousel-info" + > + + {!!item.ProductionYear + ? item.ProductionYear + : "Unknown"} + + + + {!!item.CommunityRating ? ( + <> + + + {Math.round( + item.CommunityRating * + 10, + ) / 10} + + + ) : ( + + No Community Rating + + )} + + {!!item.RunTimeTicks && ( + + {getRuntime(item.RunTimeTicks)} + + )} + {!!item.RunTimeTicks && ( + + {endsAt(item.RunTimeTicks)} + + )} + + + {item.Overview} + + + {item.UserData.PlaybackPositionTicks > 0 && ( + + + {getRuntime( + item.RunTimeTicks - + item.UserData + .PlaybackPositionTicks, + )} + + + + )} + {/* TODO Link PLay and More info buttons in carousel */} + + + + + {item.UserData.IsFavorite ? ( + + ) : ( + + )} + + + +
+
+ ); +}; + +export default CarouselSlide; diff --git a/src/components/utils/iconsCollection.jsx b/src/components/utils/iconsCollection.jsx index b31a933a..f64f37a7 100644 --- a/src/components/utils/iconsCollection.jsx +++ b/src/components/utils/iconsCollection.jsx @@ -59,6 +59,7 @@ export const MediaCollectionTypeIconCollectionCard = { export const MediaTypeIconCollection = { Movie: , + Series: , Audio: , Photo: , Book: , diff --git a/src/routes/home/index.jsx b/src/routes/home/index.jsx index 3ab7294a..44635d8c 100644 --- a/src/routes/home/index.jsx +++ b/src/routes/home/index.jsx @@ -49,11 +49,12 @@ import { useNavigate } from "react-router-dom"; import { endsAt, getRuntime } from "../../utils/date/time"; import { BaseItemKind, ItemFields } from "@jellyfin/sdk/lib/generated-client"; -import { ErrorBoundary } from "react-error-boundary"; import CarouselSlideError from "../../components/errors/carousel"; import { motion } from "framer-motion"; import { useBackdropStore } from "../../utils/store/backdrop"; + +import CarouselSlide from "../../components/carouselSlide"; const Home = () => { const authUser = useQuery({ queryKey: ["home", "authenticateUser"], @@ -100,13 +101,15 @@ const Home = () => { const media = await getUserLibraryApi(window.api).getLatestMedia( { userId: user.data.Id, - fields: [ItemFields.Overview, ItemFields.ParentId], - includeItemTypes: [ - BaseItemKind.Series, - BaseItemKind.Movie, - BaseItemKind.AudioBook, + fields: [ + ItemFields.Overview, + ItemFields.ParentId, + ItemFields.SeasonUserData, + ItemFields.RecursiveItemCount, + ItemFields.IsHd, ], enableUserData: true, + enableImages: true, limit: 16, }, ); @@ -267,378 +270,10 @@ const Home = () => { {latestMedia.data.length != 0 && latestMedia.data.map((item, index) => { return ( - - } - > - -
- {Object.keys( - item - .ImageBlurHashes - .Backdrop, - ).length != 0 && ( - - )} -
-
- { - MediaTypeIconCollection[ - item - .Type - ] - } -
-
- - - {!item - .ImageTags - .Logo ? ( - item.Name - ) : ( - - )} - - - } - className="hero-carousel-info" - > - - {!!item.ProductionYear - ? item.ProductionYear - : "Unknown"} - - - - {!!item.CommunityRating ? ( - <> - - - {Math.round( - item.CommunityRating * - 10, - ) / - 10} - - - ) : ( - - No - Community - Rating - - )} - - {!!item.RunTimeTicks && ( - - {getRuntime( - item.RunTimeTicks, - )} - - )} - {!!item.RunTimeTicks && ( - - {endsAt( - item.RunTimeTicks, - )} - - )} - - - {item.Overview} - - - {item.UserData - .PlaybackPositionTicks > - 0 && ( - - - {getRuntime( - item.RunTimeTicks - - item - .UserData - .PlaybackPositionTicks, - )}{" "} - - - - )} - {/* TODO Link PLay and More info buttons in carousel */} - - - - - -
-
+ ); })} diff --git a/src/styles/global.scss b/src/styles/global.scss index 18d9d277..c14aa435 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -49,7 +49,7 @@ body { top: 0; left: 0; z-index: -1; - filter: blur(80px); + filter: blur(100px) saturate(120%) contrast(110%); &-container { position: fixed; width: 100vw;