Refactor Track menu logic into separate handlers and items files

This commit is contained in:
srgooglo 2025-07-04 14:10:31 +02:00
parent d1b58d19fc
commit 843405dd15
4 changed files with 164 additions and 159 deletions

View File

@ -1,9 +1,10 @@
import React from "react"
import * as antd from "antd" import * as antd from "antd"
import classnames from "classnames" import classnames from "classnames"
import { WithPlayerContext } from "@contexts/WithPlayerContext" import {
import { Context as PlaylistContext } from "@contexts/WithPlaylistContext" WithPlayerContext,
usePlayerStateContext,
} from "@contexts/WithPlayerContext"
import LoadMore from "@components/LoadMore" import LoadMore from "@components/LoadMore"
import { Icons } from "@components/Icons" import { Icons } from "@components/Icons"
@ -24,6 +25,8 @@ const TrackList = ({
hasMore, hasMore,
noHeader = false, noHeader = false,
}) => { }) => {
const [{ track_manifest, playback_status }] = usePlayerStateContext()
const showListHeader = !noHeader && (tracks.length > 0 || searchResults) const showListHeader = !noHeader && (tracks.length > 0 || searchResults)
if (!searchResults && tracks.length === 0) { if (!searchResults && tracks.length === 0) {
@ -62,7 +65,7 @@ const TrackList = ({
key={item._id} key={item._id}
order={item._id} // Consider using index if order matters order={item._id} // Consider using index if order matters
track={item} track={item}
onPlay={() => onTrackClick(item)} onPlay={onTrackClick}
changeState={(update) => changeState={(update) =>
onTrackStateChange(item._id, update) onTrackStateChange(item._id, update)
} }
@ -76,19 +79,19 @@ const TrackList = ({
onBottom={onLoadMore} onBottom={onLoadMore}
hasMore={hasMore} hasMore={hasMore}
> >
<WithPlayerContext>
{tracks.map((item, index) => ( {tracks.map((item, index) => (
<MusicTrack <MusicTrack
key={item._id} // Use unique ID for key key={item._id}
order={index + 1} order={index + 1}
track={item} track={item}
onPlay={() => onTrackClick(item)} onPlay={onTrackClick}
changeState={(update) => isCurrent={item._id === track_manifest?._id}
onTrackStateChange(item._id, update) isPlaying={
item._id === track_manifest?._id &&
playback_status === "playing"
} }
/> />
))} ))}
</WithPlayerContext>
</LoadMore> </LoadMore>
)} )}
</div> </div>

View File

@ -2,14 +2,13 @@ import React from "react"
import * as antd from "antd" import * as antd from "antd"
import classnames from "classnames" import classnames from "classnames"
import RGBStringToValues from "@utils/rgbToValues"
import ImageViewer from "@components/ImageViewer" import ImageViewer from "@components/ImageViewer"
import { Icons } from "@components/Icons" import { Icons } from "@components/Icons"
import MusicModel from "@models/music" import MenuItemsBase from "./menuItems"
import MenuHandlers from "./menuHandlers"
import { usePlayerStateContext } from "@contexts/WithPlayerContext" import RGBStringToValues from "@utils/rgbToValues"
import { Context as PlaylistContext } from "@contexts/WithPlaylistContext" import { Context as PlaylistContext } from "@contexts/WithPlaylistContext"
import "./index.less" import "./index.less"
@ -22,128 +21,18 @@ function secondsToIsoTime(seconds) {
.padStart(2, "0")}` .padStart(2, "0")}`
} }
const handlers = { const Track = React.memo((props) => {
like: async (ctx, track) => {
await MusicModel.toggleItemFavourite("track", track._id, true)
ctx.changeState({
liked: true,
})
ctx.closeMenu()
},
unlike: async (ctx, track) => {
await MusicModel.toggleItemFavourite("track", track._id, false)
ctx.changeState({
liked: false,
})
ctx.closeMenu()
},
add_to_playlist: async (ctx, track) => {},
add_to_queue: async (ctx, track) => {
await app.cores.player.queue.add(track)
},
play_next: async (ctx, track) => {
await app.cores.player.queue.add(track, { next: true })
},
}
const Track = (props) => {
const [{ loading, track_manifest, playback_status }] =
usePlayerStateContext()
const playlist_ctx = React.useContext(PlaylistContext) const playlist_ctx = React.useContext(PlaylistContext)
const [moreMenuOpened, setMoreMenuOpened] = React.useState(false) const [moreMenuOpened, setMoreMenuOpened] = React.useState(false)
const [liked, setLiked] = React.useState(props.track.liked)
const isCurrent = track_manifest?._id === props.track._id const trackDuration = React.useMemo(() => {
const isPlaying = isCurrent && playback_status === "playing" return props.track?.metadata?.duration ?? props.track?.duration
}, [props.track])
const handleClickPlayBtn = () => { const menuItems = React.useMemo(() => {
if (typeof props.onPlay === "function") { const items = [...MenuItemsBase]
return props.onPlay(props.track)
}
if (typeof props.onClickPlayBtn === "function") {
props.onClickPlayBtn(props.track)
}
if (!isCurrent) {
app.cores.player.start(props.track)
} else {
app.cores.player.playback.toggle()
}
}
const handleOnClickItem = React.useCallback(() => {
if (props.onClick) {
props.onClick(props.track)
}
if (app.isMobile) {
handleClickPlayBtn()
}
}, [])
const handleMoreMenuOpen = React.useCallback(() => {
if (app.isMobile) {
return
}
return setMoreMenuOpened((prev) => {
return !prev
})
}, [])
const handleMoreMenuItemClick = React.useCallback((e) => {
const { key } = e
if (typeof handlers[key] === "function") {
return handlers[key](
{
closeMenu: () => {
setMoreMenuOpened(false)
},
changeState: props.changeState,
},
props.track,
)
}
}, [])
const moreMenuItems = React.useMemo(() => {
const items = [
{
key: "like",
icon: <Icons.MdFavorite />,
label: "Like",
},
{
key: "share",
icon: <Icons.MdShare />,
label: "Share",
disabled: true,
},
{
key: "add_to_playlist",
icon: <Icons.MdPlaylistAdd />,
label: "Add to playlist",
disabled: true,
},
{
type: "divider",
},
{
key: "add_to_queue",
icon: <Icons.MdQueueMusic />,
label: "Add to queue",
},
{
key: "play_next",
icon: <Icons.MdSkipNext />,
label: "Play next",
},
]
if (props.track.liked) { if (props.track.liked) {
items[0] = { items[0] = {
@ -170,22 +59,68 @@ const Track = (props) => {
return items return items
}, [props.track]) }, [props.track])
const trackDuration = const handleClickPlayBtn = React.useCallback(() => {
props.track?.metadata?.duration ?? props.track?.duration if (typeof props.onPlay === "function") {
return props.onPlay(props.track)
}
if (typeof props.onClickPlayBtn === "function") {
props.onClickPlayBtn(props.track)
}
if (!props.isCurrent) {
app.cores.player.start(props.track)
} else {
app.cores.player.playback.toggle()
}
}, [props.isCurrent])
const handleOnClickItem = React.useCallback(() => {
if (props.onClick) {
props.onClick(props.track)
}
if (app.isMobile) {
handleClickPlayBtn()
}
}, [props.track])
const handleMoreMenuOpen = React.useCallback(() => {
if (app.isMobile) {
return
}
return setMoreMenuOpened((prev) => {
return !prev
})
}, [])
const handleMoreMenuItemClick = React.useCallback(
(e) => {
const { key } = e
if (typeof MenuHandlers[key] === "function") {
return MenuHandlers[key](
{
close: () => {
setMoreMenuOpened(false)
},
setLiked: setLiked,
},
props.track,
)
}
},
[props.track],
)
return ( return (
<div <div
id={props.track._id} id={props.track._id}
className={classnames("music-track", { className={classnames("music-track", {
["current"]: isCurrent, ["current"]: props.isCurrent,
["playing"]: isPlaying, ["playing"]: props.isPlaying,
["loading"]: isCurrent && loading,
})} })}
style={{
"--cover_average-color": RGBStringToValues(
track_manifest?.cover_analysis?.rgb,
),
}}
> >
<div className="music-track_background" /> <div className="music-track_background" />
@ -204,7 +139,7 @@ const Track = (props) => {
type="primary" type="primary"
shape="circle" shape="circle"
icon={ icon={
isPlaying ? ( props.isPlaying ? (
<Icons.MdPause /> <Icons.MdPause />
) : ( ) : (
<Icons.MdPlayArrow /> <Icons.MdPlayArrow />
@ -212,14 +147,6 @@ const Track = (props) => {
} }
onClick={handleClickPlayBtn} onClick={handleClickPlayBtn}
/> />
{/* {props.track?.metadata?.duration && (
<div className="music-track_play_duration">
{secondsToIsoTime(
props.track.metadata.duration,
)}
</div>
)} */}
</div> </div>
)} )}
@ -267,7 +194,7 @@ const Track = (props) => {
<antd.Dropdown <antd.Dropdown
menu={{ menu={{
items: moreMenuItems, items: menuItems,
onClick: handleMoreMenuItemClick, onClick: handleMoreMenuItemClick,
}} }}
onOpenChange={handleMoreMenuOpen} onOpenChange={handleMoreMenuOpen}
@ -281,6 +208,8 @@ const Track = (props) => {
</div> </div>
</div> </div>
) )
} })
Track.displayName = "Track"
export default Track export default Track

View File

@ -0,0 +1,31 @@
import MusicModel from "@models/music"
export default {
like: async (ctx, track) => {
await MusicModel.toggleItemFavourite("track", track._id, true)
ctx.changeState({
liked: true,
})
ctx.close()
},
unlike: async (ctx, track) => {
await MusicModel.toggleItemFavourite("track", track._id, false)
ctx.changeState({
liked: false,
})
ctx.close()
},
add_to_playlist: async (ctx, track) => {},
add_to_queue: async (ctx, track) => {
await app.cores.player.queue.add(track)
},
play_next: async (ctx, track) => {
await app.cores.player.queue.add(track, { next: true })
},
copy_id: (ctx, track) => {
console.log("copy_id", track)
navigator.clipboard.writeText(track._id)
},
}

View File

@ -0,0 +1,42 @@
import { Icons } from "@components/Icons"
export default [
{
key: "like",
icon: <Icons.MdFavorite />,
label: "Like",
},
{
key: "share",
icon: <Icons.MdShare />,
label: "Share",
disabled: true,
},
{
key: "add_to_playlist",
icon: <Icons.MdPlaylistAdd />,
label: "Add to playlist",
disabled: true,
},
{
type: "divider",
},
{
key: "add_to_queue",
icon: <Icons.MdQueueMusic />,
label: "Add to queue",
},
{
key: "play_next",
icon: <Icons.MdSkipNext />,
label: "Play next",
},
{
type: "divider",
},
{
key: "copy_id",
icon: <Icons.MdLink />,
label: "Copy ID",
},
]