mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-17 00:44:23 +02:00
redesign sidebar playlist item
This commit is contained in:
@@ -8,18 +8,24 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
cursor: default;
|
||||||
|
border-radius: var(--theme-radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-hover {
|
.row-hover {
|
||||||
:global(.label) {
|
.metadata {
|
||||||
margin-right: 135px;
|
margin-right: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
background-color: var(--theme-colors-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
right: var(--theme-spacing-xs);
|
right: var(--theme-spacing-xs);
|
||||||
|
padding: var(--theme-spacing-md);
|
||||||
|
background: var(--theme-colors-surface);
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,3 +34,47 @@
|
|||||||
box-shadow: 0 0 0 2px var(--theme-colors-primary);
|
box-shadow: 0 0 0 2px var(--theme-colors-primary);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row-group {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--theme-spacing-md);
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: var(--theme-spacing-xs) var(--theme-spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--theme-spacing-xs);
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: var(--theme-spacing-md);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-group-item {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--theme-spacing-xs);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 3rem;
|
||||||
|
min-width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { closeAllModals, openContextModal, openModal } from '@mantine/modals';
|
import { closeAllModals, openContextModal, openModal } from '@mantine/modals';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { MouseEvent, useCallback, useMemo, useState } from 'react';
|
import { memo, MouseEvent, useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, Link } from 'react-router';
|
import { generatePath, Link } from 'react-router';
|
||||||
|
|
||||||
@@ -17,14 +17,16 @@ import {
|
|||||||
PlayTooltip,
|
PlayTooltip,
|
||||||
} from '/@/renderer/features/shared/components/play-button-group';
|
} from '/@/renderer/features/shared/components/play-button-group';
|
||||||
import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click';
|
import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click';
|
||||||
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
|
||||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer, useCurrentServerId } from '/@/renderer/store';
|
import { useCurrentServer, useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { formatDurationStringShort } from '/@/renderer/utils';
|
||||||
import { Accordion } from '/@/shared/components/accordion/accordion';
|
import { Accordion } from '/@/shared/components/accordion/accordion';
|
||||||
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { ButtonProps } from '/@/shared/components/button/button';
|
import { ButtonProps } from '/@/shared/components/button/button';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
|
import { Image } from '/@/shared/components/image/image';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import {
|
import {
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
@@ -44,7 +46,7 @@ interface PlaylistRowButtonProps extends Omit<ButtonProps, 'onContextMenu' | 'on
|
|||||||
to: string;
|
to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlaylistRowButton = ({ item, name, onContextMenu, to }: PlaylistRowButtonProps) => {
|
const PlaylistRowButton = memo(({ item, name, onContextMenu, to }: PlaylistRowButtonProps) => {
|
||||||
const url = {
|
const url = {
|
||||||
pathname: generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: to }),
|
pathname: generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: to }),
|
||||||
state: { item },
|
state: { item },
|
||||||
@@ -53,7 +55,7 @@ const PlaylistRowButton = ({ item, name, onContextMenu, to }: PlaylistRowButtonP
|
|||||||
|
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
const { isDraggedOver, isDragging, ref } = useDragDrop<HTMLDivElement>({
|
const { isDraggedOver, isDragging, ref } = useDragDrop<HTMLAnchorElement>({
|
||||||
drag: {
|
drag: {
|
||||||
getId: () => {
|
getId: () => {
|
||||||
const draggedItems = getDraggedItems(item, undefined);
|
const draggedItems = getDraggedItems(item, undefined);
|
||||||
@@ -158,31 +160,47 @@ const PlaylistRowButton = ({ item, name, onContextMenu, to }: PlaylistRowButtonP
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Link
|
||||||
className={clsx(styles.row, {
|
className={clsx(styles.row, {
|
||||||
[styles.rowDraggedOver]: isDraggedOver,
|
[styles.rowDraggedOver]: isDraggedOver,
|
||||||
|
[styles.rowHover]: isHovered,
|
||||||
})}
|
})}
|
||||||
|
onContextMenu={(e: unknown) => onContextMenu(e as MouseEvent<HTMLButtonElement>, item)}
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.5 : 1,
|
||||||
}}
|
}}
|
||||||
|
to={url}
|
||||||
>
|
>
|
||||||
<SidebarItem
|
<div className={styles.rowGroup}>
|
||||||
className={clsx({
|
<Image containerClassName={styles.imageContainer} src={item.imageUrl || ''} />
|
||||||
[styles.rowHover]: isHovered,
|
<div className={styles.metadata}>
|
||||||
})}
|
<Text className={styles.name} size="md">
|
||||||
onContextMenu={(e) => onContextMenu(e, item)}
|
{name}
|
||||||
to={url}
|
</Text>
|
||||||
variant="subtle"
|
<div className={styles.metadataGroup}>
|
||||||
>
|
<div className={styles.metadataGroupItem}>
|
||||||
{name}
|
<Icon color="muted" icon="track" size="sm" />
|
||||||
</SidebarItem>
|
<Text isMuted size="sm">
|
||||||
|
{item.songCount || 0}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className={styles.metadataGroupItem}>
|
||||||
|
<Icon color="muted" icon="duration" size="sm" />
|
||||||
|
<Text isMuted size="sm">
|
||||||
|
{formatDurationStringShort(item.duration ?? 0)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{isHovered && <RowControls id={to} onPlay={handlePlay} />}
|
{isHovered && <RowControls id={to} onPlay={handlePlay} />}
|
||||||
</div>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const RowControls = ({
|
const RowControls = ({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ export const formatDurationString = (duration: number) => {
|
|||||||
return string;
|
return string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatDurationStringShort = (duration: number) => {
|
||||||
|
const rawDuration = formatDuration(duration).split(':');
|
||||||
|
return `${rawDuration[0]}h ${rawDuration[1]}m`;
|
||||||
|
};
|
||||||
|
|
||||||
export const formatRating = (item: Album | AlbumArtist | Song) =>
|
export const formatRating = (item: Album | AlbumArtist | Song) =>
|
||||||
item.userRating !== null ? <Rating readOnly value={item.userRating} /> : null;
|
item.userRating !== null ? <Rating readOnly value={item.userRating} /> : null;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user