diff --git a/src/renderer/components/audio-player/AudioPlayer.tsx b/src/renderer/components/audio-player/index.tsx similarity index 92% rename from src/renderer/components/audio-player/AudioPlayer.tsx rename to src/renderer/components/audio-player/index.tsx index 04b3407ba..629e1fbff 100644 --- a/src/renderer/components/audio-player/AudioPlayer.tsx +++ b/src/renderer/components/audio-player/index.tsx @@ -7,12 +7,10 @@ import { } from 'react'; import ReactPlayer, { ReactPlayerProps } from 'react-player'; import { - CrossfadeStyle, - PlaybackStyle, - PlayerStatus, - Song, -} from '../../../types'; -import { crossfadeHandler, gaplessHandler } from './utils/listenHandlers'; + crossfadeHandler, + gaplessHandler, +} from '@/renderer/components/audio-player/utils/list-handlers'; +import { CrossfadeStyle, PlaybackStyle, PlayerStatus, Song } from '@/types'; interface AudioPlayerProps extends ReactPlayerProps { crossfadeDuration: number; @@ -125,13 +123,13 @@ export const AudioPlayer = forwardRef( return gaplessHandler({ currentTime: e.playedSeconds, duration: getDuration(player1Ref), - isFlac: player1?.suffix === 'flac', + isFlac: player1?.container === 'flac', isTransitioning, nextPlayerRef: player2Ref, setIsTransitioning, }); }, - [isTransitioning, player1?.suffix] + [isTransitioning, player1?.container] ); const handleGapless2 = useCallback( @@ -139,13 +137,13 @@ export const AudioPlayer = forwardRef( return gaplessHandler({ currentTime: e.playedSeconds, duration: getDuration(player2Ref), - isFlac: player2?.suffix === 'flac', + isFlac: player2?.container === 'flac', isTransitioning, nextPlayerRef: player1Ref, setIsTransitioning, }); }, - [isTransitioning, player2?.suffix] + [isTransitioning, player2?.container] ); return ( diff --git a/src/renderer/components/audio-player/utils/listenHandlers.ts b/src/renderer/components/audio-player/utils/list-handlers.ts similarity index 100% rename from src/renderer/components/audio-player/utils/listenHandlers.ts rename to src/renderer/components/audio-player/utils/list-handlers.ts diff --git a/src/renderer/components/button/Button.tsx b/src/renderer/components/button/Button.tsx deleted file mode 100644 index 3b4799c8f..000000000 --- a/src/renderer/components/button/Button.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { ReactNode } from 'react'; -import { Button as MantineButton } from '@mantine/core'; - -interface ButtonProps { - icon?: ReactNode; -} - -export const Button = ({ icon }: ButtonProps) => { - return Button; -}; diff --git a/src/renderer/components/button/index.tsx b/src/renderer/components/button/index.tsx new file mode 100644 index 000000000..5795eeb43 --- /dev/null +++ b/src/renderer/components/button/index.tsx @@ -0,0 +1,114 @@ +import React, { forwardRef, Ref } from 'react'; +import styled from '@emotion/styled'; +import { + Button as MantineButton, + ButtonProps as MantineButtonProps, + createPolymorphicComponent, + TooltipProps, +} from '@mantine/core'; +import { Tooltip } from '@/renderer/components/tooltip'; + +interface ButtonProps extends MantineButtonProps { + children: React.ReactNode; + onClick?: (e: React.MouseEvent) => void; + tooltip?: Omit; +} + +interface StyledButtonProps extends ButtonProps { + ref: Ref; +} + +const StyledButton = styled(MantineButton)` + color: ${(props) => { + switch (props.variant) { + case 'default': + return 'var(--btn-default-fg)'; + case 'filled': + return 'var(--btn-primary-fg)'; + case 'subtle': + return 'var(--btn-subtle-fg)'; + default: + return ''; + } + }}; + background-color: ${(props) => { + switch (props.variant) { + case 'default': + return 'var(--btn-default-bg)'; + case 'filled': + return 'var(--btn-primary-bg)'; + case 'subtle': + return 'var(--btn-subtle-bg)'; + default: + return ''; + } + }}; + + &:hover { + color: ${(props) => { + switch (props.variant) { + case 'default': + return 'var(--btn-default-fg-hover)'; + case 'filled': + return 'var(--btn-primary-fg-hover)'; + case 'subtle': + return 'var(--btn-subtle-fg-hover)'; + default: + return ''; + } + }}; + background-color: ${(props) => { + switch (props.variant) { + case 'default': + return 'var(--btn-default-bg-hover)'; + case 'filled': + return 'var(--btn-primary-bg-hover)'; + case 'subtle': + return 'var(--btn-subtle-bg-hover)'; + default: + return ''; + } + }}; + } + + &:active { + transform: scale(0.98); + } + + &:focus-visible { + border: 1px var(--primary-color) solid; + } + + &.mantine-Button-root &:focus-visible { + outline: --var-primary; + } +`; + +const pButton = forwardRef( + ({ children, tooltip, ...props }: ButtonProps, ref) => { + if (tooltip) { + return ( + + + {children} + + + ); + } + + return ( + + {children} + + ); + } +); + +export const Button = createPolymorphicComponent<'button', ButtonProps>( + pButton +); + +pButton.defaultProps = { + onClick: undefined, + tooltip: undefined, +}; diff --git a/src/renderer/components/drag-drop/Draggable.tsx b/src/renderer/components/drag-drop/Draggable.tsx deleted file mode 100644 index b5a754f9f..000000000 --- a/src/renderer/components/drag-drop/Draggable.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useDraggable } from '@dnd-kit/core'; - -export const Draggable = ({ element, id, children, key }: any) => { - const Element = element || 'div'; - const { attributes, listeners, setNodeRef } = useDraggable({ - id, - }); - - return ( - - {children} - - ); -}; diff --git a/src/renderer/components/dropdown-menu/index.tsx b/src/renderer/components/dropdown-menu/index.tsx new file mode 100644 index 000000000..abc9eb8a1 --- /dev/null +++ b/src/renderer/components/dropdown-menu/index.tsx @@ -0,0 +1,69 @@ +import styled from '@emotion/styled'; +import { + Menu as MantineMenu, + MenuProps as MantineMenuProps, + MenuItemProps as MantineMenuItemProps, + MenuLabelProps as MantineMenuLabelProps, + MenuDividerProps as MantineMenuDividerProps, + MenuDropdownProps as MantineMenuDropdownProps, + createPolymorphicComponent, +} from '@mantine/core'; + +type MenuProps = MantineMenuProps; +type MenuLabelProps = MantineMenuLabelProps; +type MenuItemProps = MantineMenuItemProps; +type MenuDividerProps = MantineMenuDividerProps; +type MenuDropdownProps = MantineMenuDropdownProps; + +const StyledMenu = styled(MantineMenu)``; + +const StyledMenuLabel = styled(MantineMenu.Label)` + font-family: var(--label-font-family); +`; + +const StyledMenuItem = styled(MantineMenu.Item)` + padding: 0.5rem; + font-size: 0.9em; + font-family: var(--label-font-family); + background-color: var(--dropdown-menu-bg); + + & .mantine-Menu-itemIcon { + margin-right: 0.5rem; + } +`; + +const StyledMenuDropdown = styled(MantineMenu.Dropdown)` + background: var(--dropdown-menu-bg); +`; + +const StyledMenuDivider = styled(MantineMenu.Divider)` + margin: 0.3rem 0; +`; + +export const DropdownMenu = ({ children, ...props }: MenuProps) => { + return {children}; +}; + +const MenuLabel = ({ children, ...props }: MenuLabelProps) => { + return {children}; +}; + +const pMenuItem = ({ children, ...props }: MenuItemProps) => { + return {children}; +}; + +const MenuDropdown = ({ children, ...props }: MenuDropdownProps) => { + return {children}; +}; + +const MenuItem = createPolymorphicComponent<'button', MenuItemProps>(pMenuItem); + +const MenuDivider = ({ ...props }: MenuDividerProps) => { + return ; +}; + +DropdownMenu.Label = MenuLabel; +DropdownMenu.Item = MenuItem; +DropdownMenu.Target = MantineMenu.Target; +DropdownMenu.Dropdown = MenuDropdown; +DropdownMenu.Divider = MenuDivider; diff --git a/src/renderer/components/icon-button/IconButton.tsx b/src/renderer/components/icon-button/IconButton.tsx deleted file mode 100644 index 0f6f577b2..000000000 --- a/src/renderer/components/icon-button/IconButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ComponentPropsWithoutRef, ReactNode } from 'react'; -import { ActionIcon, ActionIconProps, TooltipProps } from '@mantine/core'; -import { Tooltip } from '../tooltip/Tooltip'; - -type MantineIconButtonProps = ActionIconProps & - ComponentPropsWithoutRef<'button'>; - -interface IconButtonProps extends MantineIconButtonProps { - active?: boolean; - icon: ReactNode; - tooltip?: Omit; -} - -export const IconButton = ({ - active, - tooltip, - icon, - ...rest -}: IconButtonProps) => { - if (tooltip) { - return ( - - {icon} - - ); - } - - return {icon}; -}; - -IconButton.defaultProps = { - active: false, - tooltip: undefined, -}; diff --git a/src/renderer/components/index.ts b/src/renderer/components/index.ts index 12c152484..b09648610 100644 --- a/src/renderer/components/index.ts +++ b/src/renderer/components/index.ts @@ -1,4 +1,10 @@ -export * from './tooltip/Tooltip'; -export * from './audio-player/AudioPlayer'; -export * from './icon-button/IconButton'; -export * from './text/Text'; +export * from './tooltip'; +export * from './audio-player'; +export * from './text'; +export * from './button'; +export * from './virtual-grid'; +export * from './modal'; +export * from './input'; +export * from './segmented-control'; +export * from './dropdown-menu'; +export * from './toast'; diff --git a/src/renderer/components/input/index.tsx b/src/renderer/components/input/index.tsx new file mode 100644 index 000000000..2b6f726fa --- /dev/null +++ b/src/renderer/components/input/index.tsx @@ -0,0 +1,79 @@ +import React, { forwardRef } from 'react'; +import styled from '@emotion/styled'; +import { + TextInput as MantineTextInput, + TextInputProps as MantineTextInputProps, + PasswordInput as MantinePasswordInput, + PasswordInputProps as MantinePasswordInputProps, +} from '@mantine/core'; + +interface TextInputProps extends MantineTextInputProps { + children: React.ReactNode; +} + +interface PasswordInputProps extends MantinePasswordInputProps { + children: React.ReactNode; +} + +const StyledTextInput = styled(MantineTextInput)` + &:focus, + &:focus-within { + border-color: var(--primary-color); + } + + & .mantine-TextInput-wrapper { + border-color: var(--primary-color); + } + + & .mantine-TextInput-input { + &:focus, + &:focus-within { + border-color: var(--primary-color); + } + } + + & .mantine-TextInput-required { + color: var(--secondary-color); + } + + & .mantine-TextInput-label { + font-family: var(--label-font-faimly); + } +`; + +const StyledPasswordInput = styled(MantinePasswordInput)` + & .mantine-PasswordInput-input { + &:focus, + &:focus-within { + border-color: var(--primary-color); + } + } + + & .mantine-PasswordInput-required { + color: var(--secondary-color); + } + + & .mantine-PasswordInput-label { + font-family: var(--label-font-faimly); + } +`; + +export const TextInput = forwardRef( + ({ children, ...props }: TextInputProps, ref) => { + return ( + + {children} + + ); + } +); + +export const PasswordInput = forwardRef( + ({ children, ...props }: PasswordInputProps, ref) => { + return ( + + {children} + + ); + } +); diff --git a/src/renderer/components/modal/Modal.tsx b/src/renderer/components/modal/Modal.tsx deleted file mode 100644 index d781fd7f1..000000000 --- a/src/renderer/components/modal/Modal.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { - Modal as MantineModal, - ModalProps as MantineModalProps, -} from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; - -interface ModalProps extends MantineModalProps { - condition: boolean; -} - -export const Modal = ({ condition, children, ...rest }: ModalProps) => { - const [opened, handlers] = useDisclosure(false); - - return ( - <> - {condition && ( - handlers.close()} - > - {children} - - )} - - ); -}; diff --git a/src/renderer/components/modal/index.tsx b/src/renderer/components/modal/index.tsx new file mode 100644 index 000000000..2c5fc6560 --- /dev/null +++ b/src/renderer/components/modal/index.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { + Modal as MantineModal, + ModalProps as MantineModalProps, +} from '@mantine/core'; + +export interface ModalProps extends Omit { + children?: React.ReactNode; + handlers: { + close: () => void; + open: () => void; + toggle: () => void; + }; +} + +export const Modal = ({ children, handlers, ...rest }: ModalProps) => { + return ( + + {children} + + ); +}; + +Modal.defaultProps = { + children: undefined, +}; diff --git a/src/renderer/components/segmented-control/index.tsx b/src/renderer/components/segmented-control/index.tsx new file mode 100644 index 000000000..39b77b43f --- /dev/null +++ b/src/renderer/components/segmented-control/index.tsx @@ -0,0 +1,29 @@ +import { forwardRef } from 'react'; +import styled from '@emotion/styled'; +import { + SegmentedControl as MantineSegmentedControl, + SegmentedControlProps as MantineSegmentedControlProps, +} from '@mantine/core'; + +type SegmentedControlProps = MantineSegmentedControlProps; + +const StyledSegmentedControl = styled( + MantineSegmentedControl +)` + &:focus, + &:focus-within, + &:active { + outline: 1px var(--primary-color) solid; + } + + & .mantine-SegmentedControl-label { + font-family: var(--label-font-family); + } +`; + +export const SegmentedControl = forwardRef< + HTMLDivElement, + SegmentedControlProps +>(({ ...props }: SegmentedControlProps, ref) => { + return ; +}); diff --git a/src/renderer/components/text/Text.tsx b/src/renderer/components/text/index.tsx similarity index 90% rename from src/renderer/components/text/Text.tsx rename to src/renderer/components/text/index.tsx index 979d77890..b6e89daf5 100644 --- a/src/renderer/components/text/Text.tsx +++ b/src/renderer/components/text/index.tsx @@ -3,14 +3,13 @@ import { ComponentPropsWithRef, ReactNode, } from 'react'; +import styled from '@emotion/styled'; import { Text as MantineText, TextProps as MantineTextProps, } from '@mantine/core'; import { Link } from 'react-router-dom'; -import styled from 'styled-components'; -import { Font } from '../../styles'; -import { textEllipsis } from '../../styles/mixins'; +import { Font, textEllipsis } from '@/renderer/styles'; type MantineTextDivProps = MantineTextProps & ComponentPropsWithoutRef<'div'>; type MantineTextLinkProps = MantineTextProps & ComponentPropsWithRef<'link'>; @@ -42,7 +41,7 @@ const BaseText = styled(MantineText)` ? 'var(--playerbar-text-secondary-color)' : 'var(--playerbar-text-primary-color)'}; font-family: ${(props) => props.font || Font.GOTHAM}; - cursor: ${(props) => (props.link ? 'cursor' : 'default')}; + /* cursor: ${(props) => (props.link ? 'cursor' : 'default')}; */ user-select: ${(props) => (props.$noSelect ? 'none' : 'auto')}; ${(props) => props.overflow === 'hidden' && textEllipsis} `; @@ -63,7 +62,7 @@ export const Text = ({ }: TextProps) => { if (link) { return ( - + { + const color = + type === 'success' + ? 'var(--success-color)' + : type === 'warning' + ? 'var(--warning-color)' + : type === 'error' + ? 'var(--danger-color)' + : 'var(--primary-color)'; + + const defaultTitle = + type === 'success' + ? 'Success' + : type === 'warning' + ? 'Warning' + : type === 'error' + ? 'Error' + : 'Info'; + + const defaultDuration = type === 'error' ? 3500 : 2000; + + return showNotification({ + autoClose: defaultDuration, + disallowClose: true, + styles: () => ({ + closeButton: {}, + description: { + color: 'var(--toast-description-fg)', + fontSize: '.9em', + }, + loader: { + margin: '1rem', + }, + root: { + '&::before': { backgroundColor: color }, + background: 'var(--toast-bg)', + }, + title: { + color: 'var(--toast-title-fg)', + fontSize: '1em', + }, + }), + title: defaultTitle, + ...props, + }); +}; + +export const toast = { + clean: cleanNotifications, + cleanQueue: cleanNotificationsQueue, + hide: hideNotification, + show: showToast, + update: updateNotification, +}; diff --git a/src/renderer/components/tooltip/Tooltip.module.scss b/src/renderer/components/tooltip/Tooltip.module.scss deleted file mode 100644 index 951efe2b4..000000000 --- a/src/renderer/components/tooltip/Tooltip.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.body { - padding: 5px; - color: var(--tooltip-text-color); - background: var(--tooltip-bg); -} - -.arrow { - background: var(--tooltip-bg); -} diff --git a/src/renderer/components/tooltip/Tooltip.tsx b/src/renderer/components/tooltip/Tooltip.tsx deleted file mode 100644 index 0647f91be..000000000 --- a/src/renderer/components/tooltip/Tooltip.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Tooltip as MantineTooltip, TooltipProps } from '@mantine/core'; - -export const Tooltip = ({ children, ...rest }: TooltipProps) => { - return ( - - {children} - - ); -}; - -Tooltip.defaultProps = { - openDelay: 0, - position: 'top', - transition: 'fade', - transitionDuration: 250, - withArrow: true, - withinPortal: true, -}; diff --git a/src/renderer/components/tooltip/index.tsx b/src/renderer/components/tooltip/index.tsx new file mode 100644 index 000000000..3ed2b971d --- /dev/null +++ b/src/renderer/components/tooltip/index.tsx @@ -0,0 +1,31 @@ +import styled from '@emotion/styled'; +import { Tooltip as MantineTooltip, TooltipProps } from '@mantine/core'; + +const StyledTooltip = styled(MantineTooltip)` + & .mantine-Tooltip-arrow { + background: var(--tooltip-bg); + } + + & .mantine-Tooltip-tooltip { + margin: 20px; + color: var(--tooltip-text-color); + background: var(--tooltip-bg); + } +`; + +export const Tooltip = ({ children, ...rest }: TooltipProps) => { + return ( + + {children} + + ); +}; + +Tooltip.defaultProps = { + openDelay: 0, + position: 'top', + transition: 'fade', + transitionDuration: 250, + withArrow: true, + withinPortal: true, +}; diff --git a/src/renderer/components/virtual-grid/GridCard.tsx b/src/renderer/components/virtual-grid/GridCard.tsx deleted file mode 100644 index 3a304b7ad..000000000 --- a/src/renderer/components/virtual-grid/GridCard.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React, { useState } from 'react'; -import { DragOverlay, useDndMonitor } from '@dnd-kit/core'; -import { snapCenterToCursor } from '@dnd-kit/modifiers'; -import { Card, Skeleton } from '@mantine/core'; -import { motion } from 'framer-motion'; -import { createPortal } from 'react-dom'; -import styled from 'styled-components'; -import { CardRow } from '../../types'; -import { Draggable } from '../drag-drop/Draggable'; -import { Text } from '../text/Text'; -import { GridCardControls } from './GridCardControls'; - -const CardWrapper = styled(motion.div)<{ - itemGap: number; - itemHeight: number; - itemWidth: number; -}>` - flex: ${({ itemWidth }) => `0 0 ${itemWidth}px`}; - width: ${({ itemWidth }) => `${itemWidth}px`}; - height: ${({ itemHeight }) => `${itemHeight}px`}; - margin: ${({ itemGap }) => `0 ${itemGap / 2}px`}; - border-radius: 3px; - filter: drop-shadow(0 4px 4px #000); - user-select: none; - pointer-events: auto; // https://github.com/bvaughn/react-window/issues/128#issuecomment-460166682 - - &:focus-visible { - outline: 1px solid #fff; - } -`; - -const StyledCard = styled(Card)` - display: flex; - flex-direction: column; - gap: 0.5rem; - width: 100%; - height: 100%; - padding: 0; - background-color: rgb(50, 50, 50, 50%); - border-radius: 3px; - transition: background-color 0.2s ease-in-out; - - &:hover { - background-color: rgb(50, 50, 50, 60%); - } -`; - -const ImageSection = styled.div` - width: 100%; - height: 100%; -`; - -interface ImageProps { - height: number; - src: string; -} - -const Image = styled(motion.div).attrs((props: ImageProps) => ({ - style: { - background: `url(${props.src})`, - }, -}))` - height: ${({ height }) => `${height}px`}; - background-position: center; - background-size: cover; - border: 0; -`; - -const ControlsContainer = styled.div` - display: none; - width: 100%; - height: 100%; - - ${StyledCard}:hover & { - display: block; - } -`; - -const DetailSection = styled.div` - display: flex; - flex-direction: column; -`; - -const Row = styled.div` - height: 25px; - padding: 0 0.2rem; -`; - -export const GridCard = ({ data, index, style }: any) => { - const { - itemHeight, - itemWidth, - columnCount, - itemGap, - itemCount, - cardControls, - handlePlayQueueAdd, - cardRows, - itemData, - } = data; - - const startIndex = index * columnCount; - const stopIndex = Math.min(itemCount - 1, startIndex + columnCount - 1); - const cards = []; - - const [isDragging, setIsDragging] = useState(false); - - useDndMonitor({ - onDragCancel: () => setIsDragging(false), - onDragEnd: () => setIsDragging(false), - onDragStart: () => setIsDragging(true), - }); - - for (let i = startIndex; i <= stopIndex; i += 1) { - cards.push( - - - - - - - - - - - - - - {cardRows.map((row: CardRow) => ( - - - {itemData[i] && itemData[i][row.prop]} - - - ))} - - - - - - {createPortal( - - {isDragging ?
OVERLAY
: null} -
, - document.body - )} -
- ); - } - - return ( -
- {cards} -
- ); -}; diff --git a/src/renderer/components/virtual-grid/GridCardControls.tsx b/src/renderer/components/virtual-grid/grid-card-controls.tsx similarity index 98% rename from src/renderer/components/virtual-grid/GridCardControls.tsx rename to src/renderer/components/virtual-grid/grid-card-controls.tsx index b1d855be2..c7d2b5cb2 100644 --- a/src/renderer/components/virtual-grid/GridCardControls.tsx +++ b/src/renderer/components/virtual-grid/grid-card-controls.tsx @@ -1,8 +1,8 @@ import React from 'react'; +import styled from '@emotion/styled'; import { Button, UnstyledButton, UnstyledButtonProps } from '@mantine/core'; import { motion } from 'framer-motion'; import { RiPlayFill } from 'react-icons/ri'; -import styled from 'styled-components'; import { Play } from '../../../types'; type PlayButtonType = UnstyledButtonProps & diff --git a/src/renderer/components/virtual-grid/grid-card.tsx b/src/renderer/components/virtual-grid/grid-card.tsx new file mode 100644 index 000000000..d595c4f30 --- /dev/null +++ b/src/renderer/components/virtual-grid/grid-card.tsx @@ -0,0 +1,152 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { Skeleton } from '@mantine/core'; +import { motion } from 'framer-motion'; +import { CardRow } from '../../types'; +import { Text } from '../text'; +import { GridCardControls } from './grid-card-controls'; + +const CardWrapper = styled(motion.div)<{ + itemGap: number; + itemHeight: number; + itemWidth: number; +}>` + flex: ${({ itemWidth }) => `0 0 ${itemWidth}px`}; + width: ${({ itemWidth }) => `${itemWidth}px`}; + height: ${({ itemHeight }) => `${itemHeight}px`}; + margin: ${({ itemGap }) => `0 ${itemGap / 2}px`}; + filter: drop-shadow(0 4px 4px #000); + transition: border 0.2s ease-in-out; + user-select: none; + pointer-events: auto; // https://github.com/bvaughn/react-window/issues/128#issuecomment-460166682 + + &:focus-visible { + outline: 1px solid #fff; + } +`; + +const StyledCard = styled.div` + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + height: 100%; + padding: 0; + background: var(--grid-card-bg); + border-radius: 3px; +`; + +const ImageSection = styled.div` + width: 100%; + height: 100%; +`; + +interface ImageProps { + height: number; + src: string; +} + +// const Image = styled(motion.div).attrs((props: ImageProps) => ({ +// style: { +// background: `url(${props.src})`, +// backgroundPosition: 'center', +// backgroundSize: 'cover', +// }, +// }))` +// height: ${({ height }) => `${height}px`}; +// background-position: center; +// background-size: cover; +// border: 0; +// `; + +const Image = styled(motion.div)` + height: ${({ height }) => `${height}px`}; + background: ${({ src }) => `url(${src})`}; + background-position: center; + background-size: cover; + border: 0; +`; + +const ControlsContainer = styled.div` + display: block; + width: 100%; + height: 100%; +`; + +const DetailSection = styled.div` + display: flex; + flex-direction: column; +`; + +const Row = styled.div` + height: 25px; + padding: 0 0.2rem; +`; + +export const GridCard = ({ data, index, style }: any) => { + const { + itemHeight, + itemWidth, + columnCount, + itemGap, + itemCount, + cardControls, + handlePlayQueueAdd, + cardRows, + itemData, + } = data; + + const startIndex = index * columnCount; + const stopIndex = Math.min(itemCount - 1, startIndex + columnCount - 1); + const cards = []; + + for (let i = startIndex; i <= stopIndex; i += 1) { + cards.push( + + + + + + + + + + + + + {cardRows.map((row: CardRow) => ( + + + {itemData[i] && itemData[i][row.prop]} + + + ))} + + + + + + ); + } + + return ( +
+ {cards} +
+ ); +}; diff --git a/src/renderer/components/virtual-grid/index.ts b/src/renderer/components/virtual-grid/index.ts new file mode 100644 index 000000000..05ff6379a --- /dev/null +++ b/src/renderer/components/virtual-grid/index.ts @@ -0,0 +1,2 @@ +export * from './virtual-grid-wrapper'; +export * from './virtual-infinite-grid'; diff --git a/src/renderer/components/virtual-grid/VirtualGridWrapper.tsx b/src/renderer/components/virtual-grid/virtual-grid-wrapper.tsx similarity index 85% rename from src/renderer/components/virtual-grid/VirtualGridWrapper.tsx rename to src/renderer/components/virtual-grid/virtual-grid-wrapper.tsx index 6da2ea7b3..06cc6d5ca 100644 --- a/src/renderer/components/virtual-grid/VirtualGridWrapper.tsx +++ b/src/renderer/components/virtual-grid/virtual-grid-wrapper.tsx @@ -1,8 +1,8 @@ import { Ref, useMemo } from 'react'; import { FixedSizeList, FixedSizeListProps } from 'react-window'; -import { usePlayQueueHandler } from '../../features/player/hooks/usePlayQueueHandler'; -import { CardRow } from '../../types'; -import { GridCard } from './GridCard'; +import { GridCard } from '@/renderer/components/virtual-grid/grid-card'; +import { usePlayQueueHandler } from '@/renderer/features/player/hooks/usePlayQueueHandler'; +import { CardRow } from '@/renderer/types'; export const VirtualGridWrapper = ({ refInstance, diff --git a/src/renderer/components/virtual-grid/VirtualInfiniteGrid.tsx b/src/renderer/components/virtual-grid/virtual-infinite-grid.tsx similarity index 88% rename from src/renderer/components/virtual-grid/VirtualInfiniteGrid.tsx rename to src/renderer/components/virtual-grid/virtual-infinite-grid.tsx index 037822878..550e84e88 100644 --- a/src/renderer/components/virtual-grid/VirtualInfiniteGrid.tsx +++ b/src/renderer/components/virtual-grid/virtual-infinite-grid.tsx @@ -2,8 +2,8 @@ import { useState, useEffect, useRef, useMemo } from 'react'; import debounce from 'lodash/debounce'; import { FixedSizeListProps } from 'react-window'; import InfiniteLoader from 'react-window-infinite-loader'; -import { CardRow } from '../../types'; -import { VirtualGridWrapper } from './VirtualGridWrapper'; +import { VirtualGridWrapper } from '@/renderer/components/virtual-grid/virtual-grid-wrapper'; +import { CardRow } from '@/renderer/types'; interface VirtualGridProps extends Omit { @@ -12,8 +12,9 @@ interface VirtualGridProps itemGap?: number; itemSize: number; minimumBatchSize?: number; - query: (props: any) => Promise; + query: ({ serverId }: { serverId: string }, props: any) => Promise; queryParams?: Record; + serverId: string; } export const VirtualInfiniteGrid = ({ @@ -26,6 +27,7 @@ export const VirtualInfiniteGrid = ({ query, queryParams, height, + serverId, width, }: VirtualGridProps) => { const [itemData, setItemData] = useState([]); @@ -58,11 +60,14 @@ export const VirtualInfiniteGrid = ({ const start = startIndex * columnCount; const end = stopIndex * columnCount + columnCount; - const t = await query({ - skip: start, - take: end - start, - ...queryParams, - }); + const t = await query( + { serverId }, + { + skip: start, + take: end - start, + ...queryParams, + } + ); const newData: any[] = [...itemData]; @@ -109,7 +114,7 @@ export const VirtualInfiniteGrid = ({ itemCount={itemCount || 0} itemData={itemData} itemGap={itemGap!} - itemHeight={itemHeight!} + itemHeight={itemHeight! + itemGap! / 2} itemWidth={itemSize} refInstance={(list) => { infiniteLoaderRef(list);