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);