From 58d912065bdafa0376c173a8330635f128bb63f2 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 17 May 2023 17:11:33 -0700 Subject: [PATCH] Add swiper card / update virt cards --- src/renderer/components/card/card-rows.tsx | 2 +- src/renderer/components/card/poster-card.tsx | 206 ++++++++++++++++++ .../virtual-grid/grid-card/default-card.tsx | 2 +- .../grid-card/grid-card-controls.tsx | 111 ++++++---- .../virtual-grid/grid-card/poster-card.tsx | 2 +- 5 files changed, 278 insertions(+), 45 deletions(-) create mode 100644 src/renderer/components/card/poster-card.tsx diff --git a/src/renderer/components/card/card-rows.tsx b/src/renderer/components/card/card-rows.tsx index 5c38e036..d96bec6e 100644 --- a/src/renderer/components/card/card-rows.tsx +++ b/src/renderer/components/card/card-rows.tsx @@ -21,7 +21,7 @@ const Row = styled.div<{ $secondary?: boolean }>` interface CardRowsProps { data: any; - rows: CardRow[]; + rows: CardRow[] | CardRow[] | CardRow[]; } export const CardRows = ({ data, rows }: CardRowsProps) => { diff --git a/src/renderer/components/card/poster-card.tsx b/src/renderer/components/card/poster-card.tsx new file mode 100644 index 00000000..ed9a0ebb --- /dev/null +++ b/src/renderer/components/card/poster-card.tsx @@ -0,0 +1,206 @@ +import { Center, Stack } from '@mantine/core'; +import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri'; +import { generatePath, Link } from 'react-router-dom'; +import { SimpleImg } from 'react-simple-img'; +import styled, { css } from 'styled-components'; +import { Album, AlbumArtist, Artist, LibraryItem } from '/@/renderer/api/types'; +import { CardRows } from '/@/renderer/components/card'; +import { Skeleton } from '/@/renderer/components/skeleton'; +import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls'; +import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types'; + +interface BaseGridCardProps { + controls: { + cardRows: CardRow[] | CardRow[] | CardRow[]; + handleFavorite: (options: { + id: string[]; + isFavorite: boolean; + itemType: LibraryItem; + serverId: string; + }) => void; + handlePlayQueueAdd: ((options: PlayQueueAddOptions) => void) | undefined; + itemType: LibraryItem; + playButtonBehavior: Play; + route: CardRoute; + }; + data: any; + isLoading?: boolean; +} + +const PosterCardContainer = styled.div<{ $isHidden?: boolean }>` + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + overflow: hidden; + opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)}; + pointer-events: auto; + + .card-controls { + opacity: 0; + } +`; + +const ImageContainerStyles = css` + position: relative; + display: flex; + align-items: center; + aspect-ratio: 1/1; + overflow: hidden; + background: var(--card-default-bg); + border-radius: var(--card-poster-radius); + + &::before { + position: absolute; + top: 0; + left: 0; + z-index: 1; + width: 100%; + height: 100%; + background: linear-gradient(0deg, rgba(0, 0, 0, 100%) 35%, rgba(0, 0, 0, 0%) 100%); + opacity: 0; + transition: all 0.2s ease-in-out; + content: ''; + user-select: none; + } + + &:hover { + &::before { + opacity: 0.5; + } + } + + &:hover .card-controls { + opacity: 1; + } +`; + +const ImageContainer = styled(Link)<{ $isFavorite?: boolean }>` + ${ImageContainerStyles} +`; + +const ImageContainerSkeleton = styled.div` + ${ImageContainerStyles} +`; + +const Image = styled(SimpleImg)` + width: 100%; + max-width: 100%; + height: 100% !important; + max-height: 100%; + border: 0; + + img { + height: 100%; + object-fit: cover; + } +`; + +const DetailContainer = styled.div` + margin-top: 0.5rem; +`; + +export const PosterCard = ({ + data, + controls, + isLoading, + uniqueId, +}: BaseGridCardProps & { uniqueId: string }) => { + if (!isLoading) { + const path = generatePath( + controls.route.route, + controls.route.slugs?.reduce((acc, slug) => { + return { + ...acc, + [slug.slugProperty]: data[slug.idProperty], + }; + }, {}), + ); + + let Placeholder = RiAlbumFill; + + switch (controls.itemType) { + case LibraryItem.ALBUM: + Placeholder = RiAlbumFill; + break; + case LibraryItem.ARTIST: + Placeholder = RiUserVoiceFill; + break; + case LibraryItem.ALBUM_ARTIST: + Placeholder = RiUserVoiceFill; + break; + case LibraryItem.PLAYLIST: + Placeholder = RiPlayListFill; + break; + default: + Placeholder = RiAlbumFill; + break; + } + + return ( + + + {data?.imageUrl ? ( + + ) : ( +
+ +
+ )} + +
+ + + +
+ ); + } + + return ( + + + + + + + {controls.cardRows.map((row, index) => ( + + ))} + + + + ); +}; diff --git a/src/renderer/components/virtual-grid/grid-card/default-card.tsx b/src/renderer/components/virtual-grid/grid-card/default-card.tsx index ff2399c3..a67ca701 100644 --- a/src/renderer/components/virtual-grid/grid-card/default-card.tsx +++ b/src/renderer/components/virtual-grid/grid-card/default-card.tsx @@ -110,7 +110,7 @@ const ImageContainer = styled.div<{ $isFavorite?: boolean }>` const Image = styled(SimpleImg)` width: 100%; max-width: 100%; - height: 100%; + height: 100% !important; max-height: 100%; border: 0; diff --git a/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx b/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx index 07f49c55..41a99161 100644 --- a/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx +++ b/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx @@ -1,5 +1,4 @@ -import type { MouseEvent } from 'react'; -import React from 'react'; +import React, { MouseEvent, useState } from 'react'; import type { UnstyledButtonProps } from '@mantine/core'; import { RiPlayFill, RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri'; import styled from 'styled-components'; @@ -62,7 +61,7 @@ const SecondaryButton = styled(_Button)` } `; -const GridCardControlsContainer = styled.div` +const GridCardControlsContainer = styled.div<{ $isFavorite?: boolean }>` position: absolute; z-index: 100; display: flex; @@ -73,6 +72,19 @@ const GridCardControlsContainer = styled.div` height: 100%; `; +const FavoriteBanner = styled.div` + position: absolute; + top: -50px; + left: -50px; + width: 80px; + height: 80px; + background-color: var(--primary-color); + box-shadow: 0 0 10px 8px rgba(0, 0, 0, 80%); + transform: rotate(-45deg); + content: ''; + pointer-events: none; +`; + const ControlsRow = styled.div` width: 100%; height: calc(100% / 3); @@ -100,11 +112,17 @@ export const GridCardControls = ({ handlePlayQueueAdd, handleFavorite, }: { - handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void; + handleFavorite: (options: { + id: string[]; + isFavorite: boolean; + itemType: LibraryItem; + serverId: string; + }) => void; handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void; itemData: any; itemType: LibraryItem; }) => { + const [isFavorite, setIsFavorite] = useState(itemData?.userFavorite); const playButtonBehavior = usePlayButtonBehavior(); const handlePlay = async (e: MouseEvent, playType?: Play) => { @@ -120,7 +138,7 @@ export const GridCardControls = ({ }); }; - const handleFavorites = async (e: MouseEvent) => { + const handleFavorites = async (e: MouseEvent, serverId: string) => { e.preventDefault(); e.stopPropagation(); @@ -128,7 +146,10 @@ export const GridCardControls = ({ id: [itemData.id], isFavorite: itemData.userFavorite, itemType, + serverId, }); + + setIsFavorite(!isFavorite); }; const handleContextMenu = useHandleGeneralContextMenu( @@ -137,42 +158,48 @@ export const GridCardControls = ({ ); return ( - - - - - - - - {itemData?.userFavorite ? ( - - ) : ( - - )} - - - { - e.preventDefault(); - e.stopPropagation(); - handleContextMenu(e, [itemData]); - }} - > - - - - + <> + {isFavorite ? : null} + + + + + + handleFavorites(e, itemData?.serverId)} + > + + {isFavorite ? ( + + ) : ( + + )} + + + { + e.preventDefault(); + e.stopPropagation(); + handleContextMenu(e, [itemData]); + }} + > + + + + + ); }; diff --git a/src/renderer/components/virtual-grid/grid-card/poster-card.tsx b/src/renderer/components/virtual-grid/grid-card/poster-card.tsx index 0bad5d02..fcc8cb37 100644 --- a/src/renderer/components/virtual-grid/grid-card/poster-card.tsx +++ b/src/renderer/components/virtual-grid/grid-card/poster-card.tsx @@ -98,7 +98,7 @@ const ImageContainer = styled.div<{ $isFavorite?: boolean }>` const Image = styled(SimpleImg)` width: 100%; max-width: 100%; - height: 100%; + height: 100% !important; max-height: 100%; border: 0;