mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 18:44:16 +00:00
304 lines
8.4 KiB
JavaScript
Executable File
304 lines
8.4 KiB
JavaScript
Executable File
import React from "react"
|
|
import * as antd from "antd"
|
|
import classnames from "classnames"
|
|
import { Translation } from "react-i18next"
|
|
|
|
import Searcher from "components/Searcher"
|
|
import { Icons, createIconRender } from "components/Icons"
|
|
|
|
import { WithPlayerContext } from "contexts/WithPlayerContext"
|
|
|
|
import FeedModel from "models/feed"
|
|
import MusicModel from "models/music"
|
|
|
|
import MusicTrack from "components/Music/Track"
|
|
import PlaylistItem from "components/Music/PlaylistItem"
|
|
|
|
import "./index.less"
|
|
|
|
const MusicNavbar = (props) => {
|
|
return <div className="music_navbar">
|
|
<Searcher
|
|
useUrlQuery
|
|
renderResults={false}
|
|
model={MusicModel.search}
|
|
modelParams={{
|
|
useTidal: app.cores.sync.getActiveLinkedServices().tidal,
|
|
}}
|
|
onSearchResult={props.setSearchResults}
|
|
onEmpty={() => props.setSearchResults(false)}
|
|
/>
|
|
</div>
|
|
}
|
|
|
|
const PlaylistsList = (props) => {
|
|
const hopNumber = props.hopsPerPage ?? 6
|
|
|
|
const [offset, setOffset] = React.useState(0)
|
|
const [ended, setEnded] = React.useState(false)
|
|
|
|
const [loading, result, error, makeRequest] = app.cores.api.useRequest(props.fetchMethod, {
|
|
limit: hopNumber,
|
|
trim: offset
|
|
})
|
|
|
|
const onClickPrev = () => {
|
|
if (offset === 0) {
|
|
return
|
|
}
|
|
|
|
setOffset((value) => {
|
|
const newOffset = value - hopNumber
|
|
|
|
// check if newOffset is NaN
|
|
if (newOffset !== newOffset) {
|
|
return false
|
|
}
|
|
|
|
if (typeof makeRequest === "function") {
|
|
makeRequest({
|
|
trim: newOffset,
|
|
limit: hopNumber,
|
|
})
|
|
}
|
|
|
|
return newOffset
|
|
})
|
|
}
|
|
|
|
const onClickNext = () => {
|
|
if (ended) {
|
|
return
|
|
}
|
|
|
|
setOffset((value) => {
|
|
const newOffset = value + hopNumber
|
|
|
|
// check if newOffset is NaN
|
|
if (newOffset !== newOffset) {
|
|
return false
|
|
}
|
|
|
|
if (typeof makeRequest === "function") {
|
|
makeRequest({
|
|
trim: newOffset,
|
|
limit: hopNumber,
|
|
})
|
|
}
|
|
|
|
return newOffset
|
|
})
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
if (result) {
|
|
setEnded(result.length < hopNumber)
|
|
}
|
|
}, [result])
|
|
|
|
if (error) {
|
|
console.error(error)
|
|
|
|
return <div className="playlistExplorer_section">
|
|
<antd.Result
|
|
status="warning"
|
|
title="Failed to load"
|
|
subTitle="We are sorry, but we could not load this requests. Please try again later."
|
|
/>
|
|
</div>
|
|
}
|
|
|
|
return <div className="playlistExplorer_section">
|
|
<div className="playlistExplorer_section_header">
|
|
<h1>
|
|
{
|
|
props.headerIcon
|
|
}
|
|
<Translation>
|
|
{(t) => t(props.headerTitle)}
|
|
</Translation>
|
|
</h1>
|
|
|
|
<div className="playlistExplorer_section_header_actions">
|
|
<antd.Button
|
|
icon={<Icons.MdChevronLeft />}
|
|
onClick={onClickPrev}
|
|
disabled={offset === 0 || loading}
|
|
/>
|
|
|
|
<antd.Button
|
|
icon={<Icons.MdChevronRight />}
|
|
onClick={onClickNext}
|
|
disabled={ended || loading}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="playlistExplorer_section_list">
|
|
{
|
|
loading && <antd.Skeleton active />
|
|
}
|
|
{
|
|
!loading && result.map((playlist, index) => {
|
|
return <PlaylistItem
|
|
key={index}
|
|
playlist={playlist}
|
|
/>
|
|
})
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
const ResultGroupsDecorators = {
|
|
"playlists": {
|
|
icon: "MdPlaylistPlay",
|
|
label: "Playlists",
|
|
renderItem: (props) => {
|
|
return <PlaylistItem
|
|
key={props.key}
|
|
playlist={props.item}
|
|
/>
|
|
}
|
|
},
|
|
"tracks": {
|
|
icon: "MdMusicNote",
|
|
label: "Tracks",
|
|
renderItem: (props) => {
|
|
return <MusicTrack
|
|
key={props.key}
|
|
track={props.item}
|
|
onClickPlayBtn={() => app.cores.player.start(props.item)}
|
|
onClick={() => app.location.push(`/play/${props.item._id}`)}
|
|
/>
|
|
}
|
|
}
|
|
}
|
|
|
|
const SearchResults = ({
|
|
data
|
|
}) => {
|
|
if (typeof data !== "object") {
|
|
return null
|
|
}
|
|
|
|
let groupsKeys = Object.keys(data)
|
|
|
|
// filter out empty groups
|
|
groupsKeys = groupsKeys.filter((key) => {
|
|
return data[key].length > 0
|
|
})
|
|
|
|
if (groupsKeys.length === 0) {
|
|
return <div className="music-explorer_search_results no_results">
|
|
<antd.Result
|
|
status="info"
|
|
title="No results"
|
|
subTitle="We are sorry, but we could not find any results for your search."
|
|
/>
|
|
</div>
|
|
}
|
|
|
|
return <div
|
|
className={classnames(
|
|
"music-explorer_search_results",
|
|
{
|
|
["one_column"]: groupsKeys.length === 1,
|
|
}
|
|
)}
|
|
>
|
|
{
|
|
groupsKeys.map((key, index) => {
|
|
const decorator = ResultGroupsDecorators[key] ?? {
|
|
icon: null,
|
|
label: key,
|
|
renderItem: () => null
|
|
}
|
|
|
|
return <div className="music-explorer_search_results_group" key={index}>
|
|
<div className="music-explorer_search_results_group_header">
|
|
<h1>
|
|
{
|
|
createIconRender(decorator.icon)
|
|
}
|
|
<Translation>
|
|
{(t) => t(decorator.label)}
|
|
</Translation>
|
|
</h1>
|
|
</div>
|
|
|
|
<div className="music-explorer_search_results_group_list">
|
|
{
|
|
data[key].map((item, index) => {
|
|
return decorator.renderItem({
|
|
key: index,
|
|
item
|
|
})
|
|
})
|
|
}
|
|
</div>
|
|
</div>
|
|
})
|
|
}
|
|
</div>
|
|
}
|
|
|
|
export default (props) => {
|
|
const [searchResults, setSearchResults] = React.useState(false)
|
|
|
|
React.useEffect(() => {
|
|
app.layout.toggleCenteredContent(true)
|
|
|
|
app.layout.page_panels.attachComponent("music_navbar", MusicNavbar, {
|
|
props: {
|
|
setSearchResults: setSearchResults,
|
|
}
|
|
})
|
|
|
|
return () => {
|
|
if (app.layout.page_panels) {
|
|
app.layout.page_panels.detachComponent("music_navbar")
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
return <div
|
|
className={classnames(
|
|
"musicExplorer",
|
|
)}
|
|
>
|
|
{
|
|
app.isMobile && <Searcher
|
|
useUrlQuery
|
|
renderResults={false}
|
|
model={MusicModel.search}
|
|
modelParams={{
|
|
useTidal: app.cores.sync.getActiveLinkedServices().tidal,
|
|
}}
|
|
onSearchResult={setSearchResults}
|
|
onEmpty={() => setSearchResults(false)}
|
|
/>
|
|
}
|
|
|
|
{
|
|
searchResults && <SearchResults
|
|
data={searchResults}
|
|
/>
|
|
}
|
|
|
|
{
|
|
!searchResults && <div className="feed_main">
|
|
<PlaylistsList
|
|
headerTitle="From your following artists"
|
|
headerIcon={<Icons.MdPerson />}
|
|
fetchMethod={FeedModel.getMusicFeed}
|
|
/>
|
|
|
|
<PlaylistsList
|
|
headerTitle="Explore from global"
|
|
headerIcon={<Icons.MdExplore />}
|
|
fetchMethod={FeedModel.getGlobalMusicFeed}
|
|
/>
|
|
</div>
|
|
}
|
|
</div>
|
|
} |