mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
progressive loading for favorite tracks
This commit is contained in:
parent
8e686e1e4e
commit
7c448c854f
@ -92,6 +92,7 @@
|
||||
"react-helmet": "6.1.0",
|
||||
"react-i18next": "11.15.3",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-inlinesvg": "^3.0.1",
|
||||
"react-intersection-observer": "8.33.1",
|
||||
"react-json-view": "1.21.3",
|
||||
@ -174,4 +175,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import useWsEvents from "hooks/useWsEvents"
|
||||
|
||||
import { WithPlayerContext } from "contexts/WithPlayerContext"
|
||||
|
||||
import LoadMore from "components/LoadMore"
|
||||
|
||||
import { ImageViewer } from "components"
|
||||
import { Icons } from "components/Icons"
|
||||
|
||||
@ -67,17 +69,6 @@ export default (props) => {
|
||||
}))
|
||||
}
|
||||
|
||||
const returnTracks = (list) => {
|
||||
return list.map((item, index) => {
|
||||
return <MusicTrack
|
||||
order={index + 1}
|
||||
track={item}
|
||||
onClickPlayBtn={() => handleOnClickTrack(item)}
|
||||
onLike={() => handleTrackLike(item)}
|
||||
/>
|
||||
})
|
||||
}
|
||||
|
||||
const handleOnSearchChange = (value) => {
|
||||
debounceSearch = setTimeout(() => {
|
||||
makeSearch(value)
|
||||
@ -120,6 +111,10 @@ export default (props) => {
|
||||
socketName: "music",
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
setPlaylist(props.playlist)
|
||||
}, [props.playlist])
|
||||
|
||||
if (!playlist) {
|
||||
return <antd.Skeleton active />
|
||||
}
|
||||
@ -164,7 +159,7 @@ export default (props) => {
|
||||
}
|
||||
<div className="play_info_statistics_item">
|
||||
<p>
|
||||
<Icons.MdLibraryMusic /> {playlist.list.length} Tracks
|
||||
<Icons.MdLibraryMusic /> {props.length ?? playlist.list.length} Tracks
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -192,11 +187,36 @@ export default (props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<WithPlayerContext>
|
||||
{
|
||||
returnTracks(searchResults ?? playlist.list)
|
||||
}
|
||||
</WithPlayerContext>
|
||||
{
|
||||
playlist.list.length === 0 && <antd.Empty
|
||||
description={
|
||||
<>
|
||||
<Icons.MdLibraryMusic /> This playlist its empty!
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
{
|
||||
playlist.list.length > 0 && <LoadMore
|
||||
className="list_content"
|
||||
loadingComponent={() => <antd.Skeleton />}
|
||||
onBottom={props.onLoadMore}
|
||||
hasMore={props.hasMore}
|
||||
>
|
||||
<WithPlayerContext>
|
||||
{
|
||||
playlist.list.map((item, index) => {
|
||||
return <MusicTrack
|
||||
order={index + 1}
|
||||
track={item}
|
||||
onClickPlayBtn={() => handleOnClickTrack(item)}
|
||||
onLike={() => handleTrackLike(item)}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</WithPlayerContext>
|
||||
</LoadMore>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
@ -239,5 +239,12 @@ html {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.list_content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,30 +5,124 @@ import PlaylistView from "components/Music/PlaylistView"
|
||||
|
||||
import MusicModel from "models/music"
|
||||
|
||||
export default () => {
|
||||
const [L_Favorites, R_Favorites, E_Favorites] = app.cores.api.useRequest(MusicModel.getFavorites, {
|
||||
useTidal: app.cores.sync.getActiveLinkedServices().tidal
|
||||
})
|
||||
export default class FavoriteTracks extends React.Component {
|
||||
state = {
|
||||
error: null,
|
||||
|
||||
if (E_Favorites) {
|
||||
return <antd.Result
|
||||
status="error"
|
||||
title="Error"
|
||||
subTitle={E_Favorites.message}
|
||||
initialLoading: true,
|
||||
loading: false,
|
||||
|
||||
list: [],
|
||||
total_length: 0,
|
||||
|
||||
empty: false,
|
||||
hasMore: true,
|
||||
offset: 0,
|
||||
}
|
||||
|
||||
static loadLimit = 50
|
||||
|
||||
componentDidMount = async () => {
|
||||
await this.loadItems()
|
||||
}
|
||||
|
||||
onLoadMore = async () => {
|
||||
console.log(`Loading more items...`, this.state.offset)
|
||||
|
||||
const newOffset = this.state.offset + FavoriteTracks.loadLimit
|
||||
|
||||
await this.setState({
|
||||
offset: newOffset,
|
||||
})
|
||||
|
||||
await this.loadItems({
|
||||
offset: newOffset,
|
||||
})
|
||||
}
|
||||
|
||||
loadItems = async ({
|
||||
replace = false,
|
||||
offset = 0,
|
||||
limit = FavoriteTracks.loadLimit,
|
||||
} = {}) => {
|
||||
this.setState({
|
||||
loading: true,
|
||||
})
|
||||
|
||||
const result = await MusicModel.getFavorites({
|
||||
useTidal: app.cores.sync.getActiveLinkedServices().tidal,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
}).catch((err) => {
|
||||
this.setState({
|
||||
error: err.message,
|
||||
})
|
||||
return false
|
||||
})
|
||||
|
||||
console.log("Loaded favorites => ", result)
|
||||
|
||||
if (result) {
|
||||
const { tracks, total_length } = result
|
||||
|
||||
this.setState({
|
||||
total_length
|
||||
})
|
||||
|
||||
if (tracks.length === 0) {
|
||||
if (offset === 0) {
|
||||
this.setState({
|
||||
empty: true,
|
||||
})
|
||||
}
|
||||
|
||||
return this.setState({
|
||||
hasMore: false,
|
||||
})
|
||||
}
|
||||
|
||||
if (replace) {
|
||||
this.setState({
|
||||
list: tracks,
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
list: [...this.state.list, ...tracks],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
initialLoading: false,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return <antd.Result
|
||||
status="error"
|
||||
title="Error"
|
||||
subTitle={this.state.error}
|
||||
/>
|
||||
}
|
||||
|
||||
if (this.state.initialLoading) {
|
||||
return <antd.Skeleton active />
|
||||
}
|
||||
|
||||
return <PlaylistView
|
||||
type="vertical"
|
||||
playlist={{
|
||||
title: "Your favorites",
|
||||
cover: "https://storage.ragestudio.net/comty-static-assets/favorite_song.png",
|
||||
list: this.state.list
|
||||
}}
|
||||
centered={app.isMobile}
|
||||
onLoadMore={this.onLoadMore}
|
||||
hasMore={this.state.hasMore}
|
||||
empty={this.state.empty}
|
||||
length={this.state.total_length}
|
||||
/>
|
||||
}
|
||||
|
||||
if (L_Favorites) {
|
||||
return <antd.Skeleton active />
|
||||
}
|
||||
|
||||
return <PlaylistView
|
||||
type="vertical"
|
||||
playlist={{
|
||||
title: "Your favorites",
|
||||
cover: "https://storage.ragestudio.net/comty-static-assets/favorite_song.png",
|
||||
list: R_Favorites
|
||||
}}
|
||||
centered={app.isMobile}
|
||||
/>
|
||||
}
|
@ -7,32 +7,47 @@ export default class MusicModel {
|
||||
return globalThis.__comty_shared_state.instances["music"]
|
||||
}
|
||||
|
||||
// TODO: Move external services fetching to API
|
||||
static getFavorites = async ({
|
||||
useTidal = false
|
||||
useTidal = false,
|
||||
limit,
|
||||
offset,
|
||||
}) => {
|
||||
let result = []
|
||||
|
||||
let limitPerRequesters = limit
|
||||
|
||||
if (useTidal) {
|
||||
limitPerRequesters = limitPerRequesters / 2
|
||||
}
|
||||
|
||||
const requesters = [
|
||||
async () => {
|
||||
let { data } = await request({
|
||||
instance: MusicModel.api_instance,
|
||||
method: "GET",
|
||||
url: `/tracks/liked`,
|
||||
params: {
|
||||
limit: limitPerRequesters,
|
||||
offset,
|
||||
},
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
]
|
||||
|
||||
if (useTidal) {
|
||||
requesters.push(
|
||||
async () => {
|
||||
const tidalResult = await SyncModel.tidalCore.getMyFavoriteTracks()
|
||||
|
||||
return tidalResult
|
||||
async () => {
|
||||
if (!useTidal) {
|
||||
return []
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const tidalResult = await SyncModel.tidalCore.getMyFavoriteTracks({
|
||||
limit: limitPerRequesters,
|
||||
offset,
|
||||
})
|
||||
|
||||
return tidalResult
|
||||
},
|
||||
]
|
||||
|
||||
result = await pmap(
|
||||
requesters,
|
||||
@ -46,17 +61,24 @@ export default class MusicModel {
|
||||
}
|
||||
)
|
||||
|
||||
result = result.reduce((acc, cur) => {
|
||||
return [...acc, ...cur]
|
||||
let total_length = 0
|
||||
|
||||
result.forEach((result) => {
|
||||
total_length += result.total_length
|
||||
})
|
||||
|
||||
let tracks = result.reduce((acc, cur) => {
|
||||
return [...acc, ...cur.tracks]
|
||||
}, [])
|
||||
|
||||
result = result.sort((a, b) => {
|
||||
tracks = tracks.sort((a, b) => {
|
||||
return b.liked_at - a.liked_at
|
||||
})
|
||||
|
||||
console.log(result)
|
||||
|
||||
return result
|
||||
return {
|
||||
total_length,
|
||||
tracks,
|
||||
}
|
||||
}
|
||||
|
||||
static search = async (keywords, {
|
||||
|
@ -81,11 +81,18 @@ export default class TidalService {
|
||||
return data
|
||||
}
|
||||
|
||||
static async getMyFavoriteTracks() {
|
||||
static async getMyFavoriteTracks({
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
} = {}) {
|
||||
const { data } = await request({
|
||||
instance: TidalService.api_instance,
|
||||
method: "GET",
|
||||
url: `/services/tidal/favorites/tracks`,
|
||||
params: {
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
})
|
||||
|
||||
return data
|
||||
|
@ -1,14 +1,23 @@
|
||||
import { Track, TrackLike } from "@shared-classes/DbModels"
|
||||
import { AuthorizationError } from "@shared-classes/Errors"
|
||||
|
||||
// TODO: Fetch from external linked services (like tidal, spotify, ...)
|
||||
export default async (req, res) => {
|
||||
if (!req.session) {
|
||||
return new AuthorizationError(req, res)
|
||||
}
|
||||
|
||||
const { limit = 100, offset = 0 } = req.query
|
||||
|
||||
let totalLikedTracks = await TrackLike.count({
|
||||
user_id: req.session.user_id,
|
||||
})
|
||||
|
||||
let likedTracks = await TrackLike.find({
|
||||
user_id: req.session.user_id,
|
||||
})
|
||||
.limit(Number(limit))
|
||||
.skip(Number(offset))
|
||||
.sort({ created_at: -1 })
|
||||
|
||||
const likedTracksIds = likedTracks.map((item) => {
|
||||
@ -44,5 +53,8 @@ export default async (req, res) => {
|
||||
return indexA - indexB
|
||||
})
|
||||
|
||||
return res.json(tracks)
|
||||
return res.json({
|
||||
total_length: totalLikedTracks,
|
||||
tracks,
|
||||
})
|
||||
}
|
@ -23,6 +23,8 @@ export default async (req, res) => {
|
||||
user_id: user_data.id,
|
||||
country: user_data.countryCode,
|
||||
access_token: access_token,
|
||||
limit: Number(req.query.limit ?? 50),
|
||||
offset: Number(req.query.offset ?? 0),
|
||||
})
|
||||
|
||||
return res.json(response)
|
||||
|
@ -196,6 +196,8 @@ export default class TidalAPI {
|
||||
user_id,
|
||||
country,
|
||||
access_token,
|
||||
limit = 100,
|
||||
offset = 0,
|
||||
}) {
|
||||
const url = `https://api.tidal.com/v1/users/${user_id}/favorites/tracks?countryCode=${country}`
|
||||
|
||||
@ -208,11 +210,13 @@ export default class TidalAPI {
|
||||
},
|
||||
params: {
|
||||
order: "DATE",
|
||||
orderDirection: "DESC"
|
||||
orderDirection: "DESC",
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
}
|
||||
})
|
||||
|
||||
return response.data.items.map((item) => {
|
||||
response.data.items = response.data.items.map((item) => {
|
||||
// get js time
|
||||
item.item.liked_at = new Date(item.created).getTime()
|
||||
item.item.service = "tidal"
|
||||
@ -238,5 +242,10 @@ export default class TidalAPI {
|
||||
|
||||
return item.item
|
||||
})
|
||||
|
||||
return {
|
||||
total_length: response.data.totalNumberOfItems,
|
||||
tracks: response.data.items
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user