import React from "react" import * as antd from "antd" import classnames from "classnames" import ReactMarkdown from "react-markdown" import remarkGfm from "remark-gfm" import fuse from "fuse.js" import { WithPlayerContext } from "@contexts/WithPlayerContext" import { Context as PlaylistContext } from "@contexts/WithPlaylistContext" import useWsEvents from "@hooks/useWsEvents" import checkUserIdIsSelf from "@utils/checkUserIdIsSelf" import LoadMore from "@components/LoadMore" import { Icons } from "@components/Icons" import MusicTrack from "@components/Music/Track" import SearchButton from "@components/SearchButton" import ImageViewer from "@components/ImageViewer" import MusicModel from "@models/music" import "./index.less" const PlaylistTypeDecorators = { single: () => ( Single ), album: () => ( Album ), ep: () => ( EP ), mix: () => ( Mix ), } const PlaylistInfo = (props) => { return (
) } const MoreMenuHandlers = { edit: async (playlist) => {}, delete: async (playlist) => { return antd.Modal.confirm({ title: "Are you sure you want to delete this playlist?", onOk: async () => { const result = await MusicModel.deletePlaylist( playlist._id, ).catch((err) => { console.log(err) app.message.error("Failed to delete playlist") return null }) if (result) { app.navigation.goToMusic() } }, }) }, } const PlaylistView = (props) => { const [playlist, setPlaylist] = React.useState(props.playlist) const [searchResults, setSearchResults] = React.useState(null) const [owningPlaylist, setOwningPlaylist] = React.useState( checkUserIdIsSelf(props.playlist?.user_id), ) const moreMenuItems = React.useMemo(() => { const items = [ { key: "edit", label: "Edit", }, ] if (!playlist.type || playlist.type === "playlist") { if (checkUserIdIsSelf(playlist.user_id)) { items.push({ key: "delete", label: "Delete", }) } } return items }) const contextValues = { playlist_data: playlist, owning_playlist: owningPlaylist, add_track: (track) => {}, remove_track: (track) => {}, } let debounceSearch = null const makeSearch = (value) => { //TODO: Implement me using API return app.message.info("Not implemented yet...") } const handleOnSearchChange = (value) => { debounceSearch = setTimeout(() => { makeSearch(value) }, 500) } const handleOnSearchEmpty = () => { if (debounceSearch) { clearTimeout(debounceSearch) } setSearchResults(null) } const handleOnClickPlaylistPlay = () => { app.cores.player.start(playlist.items) } const handleOnClickViewDetails = () => { app.layout.modal.open("playlist_info", PlaylistInfo, { props: { data: playlist, }, }) } const handleOnClickTrack = (track) => { // search index of track const index = playlist.items.findIndex((item) => { return item._id === track._id }) if (index === -1) { return } // check if clicked track is currently playing if (app.cores.player.state.track_manifest?._id === track._id) { app.cores.player.playback.toggle() } else { app.cores.player.start(playlist.items, { startIndex: index, }) } } const handleUpdateTrackLike = (track_id, liked) => { setPlaylist((prev) => { const index = prev.list.findIndex((item) => { return item._id === track_id }) if (index !== -1) { const newState = { ...prev, } newState.list[index].liked = liked return newState } return prev }) } const handleTrackChangeState = (track_id, update) => { setPlaylist((prev) => { const index = prev.list.findIndex((item) => { return item._id === track_id }) if (index !== -1) { const newState = { ...prev, } newState.list[index] = { ...newState.list[index], ...update, } return newState } return prev }) } const handleMoreMenuClick = async (e) => { const handler = MoreMenuHandlers[e.key] if (typeof handler !== "function") { throw new Error(`Invalid menu handler [${e.key}]`) } return await handler(playlist) } useWsEvents( { "music:track:toggle:like": (data) => { handleUpdateTrackLike(data.track_id, data.action === "liked") }, }, { socketName: "music", }, ) React.useEffect(() => { setPlaylist(props.playlist) setOwningPlaylist(checkUserIdIsSelf(props.playlist?.user_id)) }, [props.playlist]) if (!playlist) { return } const playlistType = playlist.type?.toLowerCase() ?? "playlist" return (
{!props.noHeader && (
{playlist.service === "tidal" && ( )} {typeof playlist.title === "function" ? ( playlist.title ) : (

{playlist.title}

)}
{playlistType && PlaylistTypeDecorators[ playlistType ] && (
{PlaylistTypeDecorators[ playlistType ]()}
)}

{" "} {props.length ?? playlist.total_length ?? playlist.items.length}{" "} Items

{playlist.publisher && (

{ app.navigation.goToAccount( playlist.publisher .username, ) }} > Publised by{" "} { playlist.publisher .username }

)}
Play {playlist.description && ( } onClick={ handleOnClickViewDetails } /> )} {owningPlaylist && ( } /> )}
)}
{!props.noHeader && playlist.items.length > 0 && (

Tracks

)} {playlist.items.length === 0 && ( This playlist its empty! } /> )} {searchResults && searchResults.map((item) => { return ( handleOnClickTrack(item)} changeState={(update) => handleTrackChangeState( item._id, update, ) } /> ) })} {!searchResults && playlist.items.length > 0 && ( } onBottom={props.onLoadMore} hasMore={props.hasMore} > {playlist.items.map((item, index) => { return ( handleOnClickTrack(item) } changeState={(update) => handleTrackChangeState( item._id, update, ) } /> ) })} )}
) } export default PlaylistView