mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
merge from local
This commit is contained in:
parent
534463cfa2
commit
0db536bbea
@ -1 +1 @@
|
|||||||
Subproject commit d2e6f1bc5856e3084d4fd068dec5d67ab2ef9d8d
|
Subproject commit e52925b191b6e1d1415f2bb63d921ccad8c18411
|
@ -7,9 +7,11 @@
|
|||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<meta name="description" content="Comty, a prototype of social network." />
|
<meta name="description" content="Comty, a prototype of social network." />
|
||||||
|
<meta name="title" content="Comty" />
|
||||||
|
<meta name="description" content="Comty, a prototype of social network." />
|
||||||
<meta property="og:title" content="Comty" />
|
<meta property="og:title" content="Comty" />
|
||||||
<meta property="og:description" content="Comty, a prototype of social network." />
|
<meta property="og:description" content="Comty, a prototype of social network." />
|
||||||
<meta property="og:image" content="https://dl.ragestudio.net/branding/comty/alt/SVG/t3t3.svg" />
|
<meta property="og:image" content="https://storage.ragestudio.net/rstudio/branding/comty/iso/basic_alt.svg" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -13,6 +13,8 @@ export default class TrackInstance {
|
|||||||
this.player = player
|
this.player = player
|
||||||
this.manifest = manifest
|
this.manifest = manifest
|
||||||
|
|
||||||
|
this.id = this.manifest.id ?? this.manifest._id
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +100,18 @@ export default class TrackInstance {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stop = () => {
|
||||||
|
this.audio.pause()
|
||||||
|
|
||||||
|
const lastProcessor = this.attachedProcessors[this.attachedProcessors.length - 1]
|
||||||
|
|
||||||
|
if (lastProcessor) {
|
||||||
|
this.attachedProcessors[this.attachedProcessors.length - 1]._destroy(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.attachedProcessors = []
|
||||||
|
}
|
||||||
|
|
||||||
resolveManifest = async () => {
|
resolveManifest = async () => {
|
||||||
if (typeof this.manifest === "string") {
|
if (typeof this.manifest === "string") {
|
||||||
this.manifest = {
|
this.manifest = {
|
||||||
|
@ -57,7 +57,6 @@ export default class TrackManifest {
|
|||||||
|
|
||||||
// Extended from db
|
// Extended from db
|
||||||
lyrics_enabled = false
|
lyrics_enabled = false
|
||||||
|
|
||||||
liked = null
|
liked = null
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
@ -78,7 +77,7 @@ export default class TrackManifest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.metadata.tags.picture) {
|
if (this.metadata.tags.picture) {
|
||||||
this.cover = app.cores.remoteStorage.binaryArrayToFile(this.metadata.tags.picture, this.title)
|
this.cover = app.cores.remoteStorage.binaryArrayToFile(this.metadata.tags.picture, "cover")
|
||||||
|
|
||||||
const coverUpload = await app.cores.remoteStorage.uploadFile(this.cover)
|
const coverUpload = await app.cores.remoteStorage.uploadFile(this.cover)
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ const MoreMenuHandlers = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props) => {
|
const PlaylistView = (props) => {
|
||||||
const [playlist, setPlaylist] = React.useState(props.playlist)
|
const [playlist, setPlaylist] = React.useState(props.playlist)
|
||||||
const [searchResults, setSearchResults] = React.useState(null)
|
const [searchResults, setSearchResults] = React.useState(null)
|
||||||
const [owningPlaylist, setOwningPlaylist] = React.useState(checkUserIdIsSelf(props.playlist?.user_id))
|
const [owningPlaylist, setOwningPlaylist] = React.useState(checkUserIdIsSelf(props.playlist?.user_id))
|
||||||
@ -109,8 +109,27 @@ export default (props) => {
|
|||||||
|
|
||||||
let debounceSearch = null
|
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 = () => {
|
const handleOnClickPlaylistPlay = () => {
|
||||||
app.cores.player.start(playlist.list, 0)
|
app.cores.player.start(playlist.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnClickViewDetails = () => {
|
const handleOnClickViewDetails = () => {
|
||||||
@ -131,7 +150,7 @@ export default (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if is currently playing
|
// check if clicked track is currently playing
|
||||||
if (app.cores.player.state.track_manifest?._id === track._id) {
|
if (app.cores.player.state.track_manifest?._id === track._id) {
|
||||||
app.cores.player.playback.toggle()
|
app.cores.player.playback.toggle()
|
||||||
} else {
|
} else {
|
||||||
@ -141,48 +160,7 @@ export default (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTrackLike = async (track) => {
|
const handleUpdateTrackLike = (track_id, liked) => {
|
||||||
return await MusicModel.toggleTrackLike(track._id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeSearch = (value) => {
|
|
||||||
//TODO: Implement me using API
|
|
||||||
return app.message.info("Not implemented yet...")
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
includeScore: true,
|
|
||||||
keys: [
|
|
||||||
"title",
|
|
||||||
"artist",
|
|
||||||
"album",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
const fuseInstance = new fuse(playlist.list, options)
|
|
||||||
const results = fuseInstance.search(value)
|
|
||||||
|
|
||||||
console.log(results)
|
|
||||||
|
|
||||||
setSearchResults(results.map((result) => {
|
|
||||||
return result.item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnSearchChange = (value) => {
|
|
||||||
debounceSearch = setTimeout(() => {
|
|
||||||
makeSearch(value)
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnSearchEmpty = () => {
|
|
||||||
if (debounceSearch) {
|
|
||||||
clearTimeout(debounceSearch)
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchResults(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateTrackLike = (track_id, liked) => {
|
|
||||||
setPlaylist((prev) => {
|
setPlaylist((prev) => {
|
||||||
const index = prev.list.findIndex((item) => {
|
const index = prev.list.findIndex((item) => {
|
||||||
return item._id === track_id
|
return item._id === track_id
|
||||||
@ -202,6 +180,29 @@ export default (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 handleMoreMenuClick = async (e) => {
|
||||||
const handler = MoreMenuHandlers[e.key]
|
const handler = MoreMenuHandlers[e.key]
|
||||||
|
|
||||||
@ -213,8 +214,8 @@ export default (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useWsEvents({
|
useWsEvents({
|
||||||
"music:self:track:toggle:like": (data) => {
|
"music:track:toggle:like": (data) => {
|
||||||
updateTrackLike(data.track_id, data.action === "liked")
|
handleUpdateTrackLike(data.track_id, data.action === "liked")
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
socketName: "music",
|
socketName: "music",
|
||||||
@ -268,7 +269,7 @@ export default (props) => {
|
|||||||
}
|
}
|
||||||
<div className="play_info_statistics_item">
|
<div className="play_info_statistics_item">
|
||||||
<p>
|
<p>
|
||||||
<Icons.MdLibraryMusic /> {props.length ?? playlist.total_length ?? playlist.list.length} Tracks
|
<Icons.MdLibraryMusic /> {props.length ?? playlist.total_length ?? playlist.list.length} Items
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
@ -332,16 +333,29 @@ export default (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="list">
|
<div className="list">
|
||||||
<div className="list_header">
|
{
|
||||||
<h1>
|
playlist.list.length > 0 && <div className="list_header">
|
||||||
<Icons.MdPlaylistPlay /> Tracks
|
<h1>
|
||||||
</h1>
|
<Icons.MdPlaylistPlay /> Tracks
|
||||||
|
</h1>
|
||||||
|
|
||||||
<SearchButton
|
<SearchButton
|
||||||
onChange={handleOnSearchChange}
|
onChange={handleOnSearchChange}
|
||||||
onEmpty={handleOnSearchEmpty}
|
onEmpty={handleOnSearchEmpty}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
playlist.list.length === 0 && <antd.Empty
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
<Icons.MdLibraryMusic /> This playlist its empty!
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
searchResults && searchResults.map((item) => {
|
searchResults && searchResults.map((item) => {
|
||||||
@ -350,21 +364,11 @@ export default (props) => {
|
|||||||
order={item._id}
|
order={item._id}
|
||||||
track={item}
|
track={item}
|
||||||
onClickPlayBtn={() => handleOnClickTrack(item)}
|
onClickPlayBtn={() => handleOnClickTrack(item)}
|
||||||
onLike={() => handleTrackLike(item)}
|
changeState={(update) => handleTrackChangeState(item._id, update)}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
!searchResults && playlist.list.length === 0 && <antd.Empty
|
|
||||||
description={
|
|
||||||
<>
|
|
||||||
<Icons.MdLibraryMusic /> This playlist its empty!
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
!searchResults && playlist.list.length > 0 && <LoadMore
|
!searchResults && playlist.list.length > 0 && <LoadMore
|
||||||
className="list_content"
|
className="list_content"
|
||||||
@ -379,7 +383,7 @@ export default (props) => {
|
|||||||
order={index + 1}
|
order={index + 1}
|
||||||
track={item}
|
track={item}
|
||||||
onClickPlayBtn={() => handleOnClickTrack(item)}
|
onClickPlayBtn={() => handleOnClickTrack(item)}
|
||||||
onLike={() => handleTrackLike(item)}
|
changeState={(update) => handleTrackChangeState(item._id, update)}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -391,3 +395,5 @@ export default (props) => {
|
|||||||
</WithPlayerContext>
|
</WithPlayerContext>
|
||||||
</PlaylistContext.Provider>
|
</PlaylistContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PlaylistView
|
@ -7,6 +7,8 @@ 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 { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
||||||
import { Context as PlaylistContext } from "@contexts/WithPlaylistContext"
|
import { Context as PlaylistContext } from "@contexts/WithPlaylistContext"
|
||||||
|
|
||||||
@ -14,21 +16,29 @@ import "./index.less"
|
|||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"like": async (ctx, track) => {
|
"like": async (ctx, track) => {
|
||||||
app.cores.player.toggleCurrentTrackLike(true, track)
|
await MusicModel.toggleItemFavourite("track", track._id, true)
|
||||||
|
|
||||||
|
ctx.changeState({
|
||||||
|
liked: true,
|
||||||
|
})
|
||||||
ctx.closeMenu()
|
ctx.closeMenu()
|
||||||
},
|
},
|
||||||
"unlike": async (ctx, track) => {
|
"unlike": async (ctx, track) => {
|
||||||
app.cores.player.toggleCurrentTrackLike(false, track)
|
await MusicModel.toggleItemFavourite("track", track._id, false)
|
||||||
|
|
||||||
|
ctx.changeState({
|
||||||
|
liked: false,
|
||||||
|
})
|
||||||
ctx.closeMenu()
|
ctx.closeMenu()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const Track = (props) => {
|
const Track = (props) => {
|
||||||
const {
|
const [{
|
||||||
loading,
|
loading,
|
||||||
track_manifest,
|
track_manifest,
|
||||||
playback_status,
|
playback_status,
|
||||||
} = usePlayerStateContext()
|
}] = usePlayerStateContext()
|
||||||
|
|
||||||
const playlist_ctx = React.useContext(PlaylistContext)
|
const playlist_ctx = React.useContext(PlaylistContext)
|
||||||
|
|
||||||
@ -74,7 +84,8 @@ const Track = (props) => {
|
|||||||
{
|
{
|
||||||
closeMenu: () => {
|
closeMenu: () => {
|
||||||
setMoreMenuOpened(false)
|
setMoreMenuOpened(false)
|
||||||
}
|
},
|
||||||
|
changeState: props.changeState,
|
||||||
},
|
},
|
||||||
props.track
|
props.track
|
||||||
)
|
)
|
||||||
|
@ -35,7 +35,7 @@ const EventsHandlers = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Controls = (props) => {
|
const Controls = (props) => {
|
||||||
const playerState = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const handleAction = (event, ...args) => {
|
const handleAction = (event, ...args) => {
|
||||||
if (typeof EventsHandlers[event] !== "function") {
|
if (typeof EventsHandlers[event] !== "function") {
|
||||||
|
@ -6,11 +6,12 @@ import LikeButton from "@components/LikeButton"
|
|||||||
|
|
||||||
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
||||||
|
|
||||||
const ExtraActions = (props) => {
|
import MusicModel from "@models/music"
|
||||||
const playerState = usePlayerStateContext()
|
|
||||||
|
|
||||||
|
const ExtraActions = (props) => {
|
||||||
|
const [playerState] = usePlayerStateContext()
|
||||||
const handleClickLike = async () => {
|
const handleClickLike = async () => {
|
||||||
await app.cores.player.toggleCurrentTrackLike(!playerState.track_manifest?.liked)
|
await MusicModel.toggleItemFavourite("track", playerState.track_manifest._id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="extra_actions">
|
return <div className="extra_actions">
|
||||||
@ -21,9 +22,10 @@ const ExtraActions = (props) => {
|
|||||||
disabled={!playerState.track_manifest?.lyrics_enabled}
|
disabled={!playerState.track_manifest?.lyrics_enabled}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!app.isMobile && <LikeButton
|
!app.isMobile && <LikeButton
|
||||||
liked={playerState.track_manifest?.liked ?? false}
|
liked={playerState.track_manifest?.fetchLikeStatus}
|
||||||
onClick={handleClickLike}
|
onClick={handleClickLike}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ const ServiceIndicator = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Player = (props) => {
|
const Player = (props) => {
|
||||||
const playerState = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const contentRef = React.useRef()
|
const contentRef = React.useRef()
|
||||||
const titleRef = React.useRef()
|
const titleRef = React.useRef()
|
||||||
|
@ -31,13 +31,13 @@ const SelfActionsItems = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const MoreActionsItems = [
|
const MoreActionsItems = [
|
||||||
{
|
// {
|
||||||
key: "onClickRepost",
|
// key: "onClickRepost",
|
||||||
label: <>
|
// label: <>
|
||||||
<Icons.MdCallSplit />
|
// <Icons.MdCallSplit />
|
||||||
<span>Repost</span>
|
// <span>Repost</span>
|
||||||
</>,
|
// </>,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
key: "onClickShare",
|
key: "onClickShare",
|
||||||
label: <>
|
label: <>
|
||||||
@ -57,6 +57,19 @@ const MoreActionsItems = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const BuiltInActions = {
|
||||||
|
onClickShare: (post) => {
|
||||||
|
navigator.share({
|
||||||
|
title: post.title,
|
||||||
|
text: `Check this post on Comty`,
|
||||||
|
url: post.share_url
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onClickReport: (post) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const PostActions = (props) => {
|
const PostActions = (props) => {
|
||||||
const [isSelf, setIsSelf] = React.useState(false)
|
const [isSelf, setIsSelf] = React.useState(false)
|
||||||
|
|
||||||
@ -77,8 +90,10 @@ const PostActions = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDropdownClickItem = (e) => {
|
const handleDropdownClickItem = (e) => {
|
||||||
if (typeof props.actions[e.key] === "function") {
|
const action = BuiltInActions[e.key] ?? props.actions[e.key]
|
||||||
props.actions[e.key]()
|
|
||||||
|
if (typeof action === "function") {
|
||||||
|
action(props.post)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,12 @@ const messageRegexs = [
|
|||||||
return <a key={key} onClick={() => window.app.location.push(`/@${result[1].substr(1)}`)}>{result[1]}</a>
|
return <a key={key} onClick={() => window.app.location.push(`/@${result[1].substr(1)}`)}>{result[1]}</a>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
regex: /#[a-zA-Z0-9_]+/gi,
|
||||||
|
fn: (key, result) => {
|
||||||
|
return <a key={key} onClick={() => window.app.location.push(`/trending/${result[0].substr(1)}`)}>{result[0]}</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export default class PostCard extends React.PureComponent {
|
export default class PostCard extends React.PureComponent {
|
||||||
@ -223,6 +229,7 @@ export default class PostCard extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PostActions
|
<PostActions
|
||||||
|
post={this.state.data}
|
||||||
user_id={this.state.data.user_id}
|
user_id={this.state.data.user_id}
|
||||||
|
|
||||||
likesCount={this.state.countLikes}
|
likesCount={this.state.countLikes}
|
||||||
|
@ -4,7 +4,7 @@ import classnames from "classnames"
|
|||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
export default (props) => {
|
const SearchButton = (props) => {
|
||||||
const searchBoxRef = React.useRef(null)
|
const searchBoxRef = React.useRef(null)
|
||||||
|
|
||||||
const [value, setValue] = React.useState()
|
const [value, setValue] = React.useState()
|
||||||
@ -51,6 +51,9 @@ export default (props) => {
|
|||||||
openSearchBox(false)
|
openSearchBox(false)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
disabled={props.disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SearchButton
|
45
packages/app/src/components/TrendingsCard/index.jsx
Normal file
45
packages/app/src/components/TrendingsCard/index.jsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
import { Skeleton } from "antd"
|
||||||
|
import { Icons } from "@components/Icons"
|
||||||
|
import PostsModel from "@models/post"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const TrendingsCard = (props) => {
|
||||||
|
const [L_Trendings, R_Trendings, E_Trendings] = app.cores.api.useRequest(PostsModel.getTrendings)
|
||||||
|
|
||||||
|
return <div className="card">
|
||||||
|
<div className="card-header">
|
||||||
|
<h1><Icons.IoMdTrendingUp /> Trendings</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card-content trendings">
|
||||||
|
{
|
||||||
|
L_Trendings && <Skeleton active />
|
||||||
|
}
|
||||||
|
{
|
||||||
|
E_Trendings && <span>Something went wrong</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!L_Trendings && !E_Trendings && R_Trendings && R_Trendings.map((trending, index) => {
|
||||||
|
return <div
|
||||||
|
key={index}
|
||||||
|
className="trending"
|
||||||
|
onClick={() => window.app.location.push(`/trending/${trending.hashtag}`)}
|
||||||
|
>
|
||||||
|
<div className="trending-level">
|
||||||
|
<span>#{index + 1} {trending.hashtag}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="trending-info">
|
||||||
|
<span>{trending.count} posts</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TrendingsCard
|
35
packages/app/src/components/TrendingsCard/index.less
Normal file
35
packages/app/src/components/TrendingsCard/index.less
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
.trendings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
.trending {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trending-level {
|
||||||
|
font-family: "DM Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trending-info {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--background-color-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,19 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
function deepUnproxy(obj) {
|
function deepUnproxy(obj) {
|
||||||
// Verificar si es un array y hacer una copia en consecuencia
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
obj = [...obj];
|
obj = [...obj]
|
||||||
} else {
|
} else {
|
||||||
obj = Object.assign({}, obj);
|
obj = Object.assign({}, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let key in obj) {
|
for (let key in obj) {
|
||||||
if (obj[key] && typeof obj[key] === "object") {
|
if (obj[key] && typeof obj[key] === "object") {
|
||||||
obj[key] = deepUnproxy(obj[key]); // Recursión para profundizar en objetos y arrays
|
obj[key] = deepUnproxy(obj[key])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePlayerStateContext = (updater) => {
|
export const usePlayerStateContext = (updater) => {
|
||||||
@ -40,7 +39,7 @@ export const usePlayerStateContext = (updater) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return state
|
return [state, setState]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Context = React.createContext({})
|
export const Context = React.createContext({})
|
||||||
|
@ -53,6 +53,13 @@ export default class APICore extends Core {
|
|||||||
enableWs: true,
|
enableWs: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.client.eventBus.on("ws:disconnected", () => {
|
||||||
|
app.cores.notifications.new({
|
||||||
|
title: "Failed to connect to server",
|
||||||
|
description: "The connection to the server was lost. Some features may not work properly.",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
this.client.eventBus.on("auth:login_success", () => {
|
this.client.eventBus.on("auth:login_success", () => {
|
||||||
app.eventBus.emit("auth:login_success")
|
app.eventBus.emit("auth:login_success")
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ export default class PlayerProcessors {
|
|||||||
Object.entries(processor.exposeToPublic).forEach(([key, value]) => {
|
Object.entries(processor.exposeToPublic).forEach(([key, value]) => {
|
||||||
const refName = processor.constructor.refName
|
const refName = processor.constructor.refName
|
||||||
|
|
||||||
if (typeof this.public[refName] === "undefined") {
|
if (typeof this.player.public[refName] === "undefined") {
|
||||||
// by default create a empty object
|
// by default create a empty object
|
||||||
this.player.public[refName] = {}
|
this.player.public[refName] = {}
|
||||||
}
|
}
|
||||||
@ -55,8 +55,6 @@ export default class PlayerProcessors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async attachProcessorsToInstance(instance) {
|
async attachProcessorsToInstance(instance) {
|
||||||
this.player.console.log(instance, this.processors)
|
|
||||||
|
|
||||||
for await (const [index, processor] of this.processors.entries()) {
|
for await (const [index, processor] of this.processors.entries()) {
|
||||||
if (processor.constructor.node_bypass === true) {
|
if (processor.constructor.node_bypass === true) {
|
||||||
instance.contextElement.connect(processor.processor)
|
instance.contextElement.connect(processor.processor)
|
||||||
|
@ -10,7 +10,6 @@ export default class PlayerUI {
|
|||||||
//
|
//
|
||||||
// UI Methods
|
// UI Methods
|
||||||
//
|
//
|
||||||
|
|
||||||
attachPlayerComponent() {
|
attachPlayerComponent() {
|
||||||
if (this.currentDomWindow) {
|
if (this.currentDomWindow) {
|
||||||
this.player.console.warn("EmbbededMediaPlayer already attached")
|
this.player.console.warn("EmbbededMediaPlayer already attached")
|
||||||
@ -18,7 +17,9 @@ export default class PlayerUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (app.layout.tools_bar) {
|
if (app.layout.tools_bar) {
|
||||||
this.currentDomWindow = app.layout.tools_bar.attachRender("mediaPlayer", ToolBarPlayer)
|
this.currentDomWindow = app.layout.tools_bar.attachRender("mediaPlayer", ToolBarPlayer, undefined, {
|
||||||
|
position: "bottom",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ export default class Presets {
|
|||||||
constructor({
|
constructor({
|
||||||
storage_key,
|
storage_key,
|
||||||
defaultPresetValue,
|
defaultPresetValue,
|
||||||
|
onApplyValues,
|
||||||
}) {
|
}) {
|
||||||
if (!storage_key) {
|
if (!storage_key) {
|
||||||
throw new Error("storage_key is required")
|
throw new Error("storage_key is required")
|
||||||
@ -11,6 +12,7 @@ export default class Presets {
|
|||||||
|
|
||||||
this.storage_key = storage_key
|
this.storage_key = storage_key
|
||||||
this.defaultPresetValue = defaultPresetValue
|
this.defaultPresetValue = defaultPresetValue
|
||||||
|
this.onApplyValues = onApplyValues
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -38,14 +40,25 @@ export default class Presets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get currentPresetValues() {
|
get currentPresetValues() {
|
||||||
const presets = this.presets
|
if (!this.presets || !this.presets[this.currentPresetKey]) {
|
||||||
const key = this.currentPresetKey
|
|
||||||
|
|
||||||
if (!presets || !presets[key]) {
|
|
||||||
return this.defaultPresetValue
|
return this.defaultPresetValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return presets[key]
|
return this.presets[this.currentPresetKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
set currentPresetValues(values) {
|
||||||
|
const newData = this.presets
|
||||||
|
|
||||||
|
newData[this.currentPresetKey] = values
|
||||||
|
|
||||||
|
this.presets = newData
|
||||||
|
}
|
||||||
|
|
||||||
|
applyValues() {
|
||||||
|
if (typeof this.onApplyValues === "function") {
|
||||||
|
this.onApplyValues(this.presets)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePreset(key) {
|
deletePreset(key) {
|
||||||
@ -54,64 +67,74 @@ export default class Presets {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if current preset is deleted, change to default
|
||||||
if (this.currentPresetKey === key) {
|
if (this.currentPresetKey === key) {
|
||||||
this.changePreset("default")
|
this.changePreset("default")
|
||||||
}
|
}
|
||||||
|
|
||||||
let presets = this.presets
|
let newData = this.presets
|
||||||
|
|
||||||
delete presets[key]
|
delete newData[key]
|
||||||
|
|
||||||
this.presets = presets
|
this.presets = newData
|
||||||
|
|
||||||
return presets
|
this.applyValues()
|
||||||
|
|
||||||
|
return newData
|
||||||
}
|
}
|
||||||
|
|
||||||
createPreset(key, values) {
|
createPreset(key, values) {
|
||||||
let presets = this.presets
|
if (this.presets[key]) {
|
||||||
|
|
||||||
if (presets[key]) {
|
|
||||||
app.message.error("Preset already exists")
|
app.message.error("Preset already exists")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
presets[key] = values ?? this.defaultPresetValue
|
let newData = this.presets
|
||||||
|
|
||||||
this.presets = presets
|
newData[key] = values ?? this.defaultPresetValue
|
||||||
|
|
||||||
return presets[key]
|
this.applyValues()
|
||||||
|
|
||||||
|
this.presets = newData
|
||||||
|
|
||||||
|
return newData
|
||||||
}
|
}
|
||||||
|
|
||||||
changePreset(key) {
|
changePreset(key) {
|
||||||
let presets = this.presets
|
|
||||||
|
|
||||||
// create new one
|
// create new one
|
||||||
if (!presets[key]) {
|
if (!this.presets[key]) {
|
||||||
presets[key] = this.defaultPresetValue
|
this.presets[key] = this.defaultPresetValue
|
||||||
|
|
||||||
this.presets = presets
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentPresetKey = key
|
this.currentPresetKey = key
|
||||||
|
|
||||||
return presets[key]
|
this.applyValues()
|
||||||
|
|
||||||
|
return this.presets[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
setToCurrent(values) {
|
setToCurrent(values) {
|
||||||
let preset = this.currentPresetValues
|
this.currentPresetValues = {
|
||||||
|
...this.currentPresetValues,
|
||||||
preset = {
|
|
||||||
...preset,
|
|
||||||
...values,
|
...values,
|
||||||
}
|
}
|
||||||
|
|
||||||
// update presets
|
this.applyValues()
|
||||||
let presets = this.presets
|
|
||||||
|
|
||||||
presets[this.currentPresetKey] = preset
|
return this.currentPresetValues
|
||||||
|
}
|
||||||
|
|
||||||
this.presets = presets
|
async setCurrentPresetToDefault() {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
app.layout.modal.confirm.confirm({
|
||||||
|
title: "Reset to default values?",
|
||||||
|
content: "Are you sure you want to reset to default values?",
|
||||||
|
onOk: () => {
|
||||||
|
this.setToCurrent(this.defaultPresetValue)
|
||||||
|
|
||||||
return preset
|
resolve(this.currentPresetValues)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
126
packages/app/src/cores/player/classes/QueueManager.js
Normal file
126
packages/app/src/cores/player/classes/QueueManager.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
export default class QueueManager {
|
||||||
|
constructor(params = {}) {
|
||||||
|
this.params = params
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
prevItems = []
|
||||||
|
nextItems = []
|
||||||
|
|
||||||
|
currentItem = null
|
||||||
|
|
||||||
|
next = ({ random = false } = {}) => {
|
||||||
|
if (this.nextItems.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentItem) {
|
||||||
|
this.prevItems.push(this.currentItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (random) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * this.nextItems.length)
|
||||||
|
|
||||||
|
this.currentItem = this.nextItems.splice(randomIndex, 1)[0]
|
||||||
|
} else {
|
||||||
|
this.currentItem = this.nextItems.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.currentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
set = (item) => {
|
||||||
|
if (typeof item === "number") {
|
||||||
|
item = this.nextItems[item]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentItem && this.currentItem.id === item.id) {
|
||||||
|
return this.currentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemInNext = this.nextItems.findIndex((i) => i.id === item.id)
|
||||||
|
const itemInPrev = this.prevItems.findIndex((i) => i.id === item.id)
|
||||||
|
|
||||||
|
if (itemInNext === -1 && itemInPrev === -1) {
|
||||||
|
throw new Error("Item not found in the queue")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemInNext > -1) {
|
||||||
|
if (this.currentItem) {
|
||||||
|
this.prevItems.push(this.currentItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevItems.push(...this.nextItems.splice(0, itemInNext))
|
||||||
|
|
||||||
|
this.currentItem = this.nextItems.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemInPrev > -1) {
|
||||||
|
if (this.currentItem) {
|
||||||
|
this.nextItems.unshift(this.currentItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nextItems.unshift(...this.prevItems.splice(itemInPrev + 1))
|
||||||
|
|
||||||
|
this.currentItem = this.prevItems.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.currentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = () => {
|
||||||
|
if (this.prevItems.length === 0) {
|
||||||
|
return this.currentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentItem) {
|
||||||
|
this.nextItems.unshift(this.currentItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentItem = this.prevItems.pop()
|
||||||
|
|
||||||
|
return this.currentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
add = (items) => {
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
items = [items]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nextItems = [...this.nextItems, ...items]
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
remove = (item) => {
|
||||||
|
const indexNext = this.nextItems.findIndex((i) => i.id === item.id)
|
||||||
|
const indexPrev = this.prevItems.findIndex((i) => i.id === item.id)
|
||||||
|
|
||||||
|
if (indexNext > -1) {
|
||||||
|
this.nextItems.splice(indexNext, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexPrev > -1) {
|
||||||
|
this.prevItems.splice(indexPrev, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.consoleState()
|
||||||
|
}
|
||||||
|
|
||||||
|
flush = () => {
|
||||||
|
this.nextItems = []
|
||||||
|
this.prevItems = []
|
||||||
|
this.currentItem = null
|
||||||
|
|
||||||
|
this.consoleState()
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(item) {
|
||||||
|
if (typeof this.params.loadFunction === "function") {
|
||||||
|
return await this.params.loadFunction(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import ServiceProviders from "./classes/Services"
|
|||||||
import PlayerState from "./classes/PlayerState"
|
import PlayerState from "./classes/PlayerState"
|
||||||
import PlayerUI from "./classes/PlayerUI"
|
import PlayerUI from "./classes/PlayerUI"
|
||||||
import PlayerProcessors from "./classes/PlayerProcessors"
|
import PlayerProcessors from "./classes/PlayerProcessors"
|
||||||
|
import QueueManager from "./classes/QueueManager"
|
||||||
|
|
||||||
import setSampleRate from "./helpers/setSampleRate"
|
import setSampleRate from "./helpers/setSampleRate"
|
||||||
|
|
||||||
@ -28,8 +29,8 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
state = new PlayerState(this)
|
state = new PlayerState(this)
|
||||||
ui = new PlayerUI(this)
|
ui = new PlayerUI(this)
|
||||||
service_providers = new ServiceProviders()
|
serviceProviders = new ServiceProviders()
|
||||||
native_controls = new MediaSession()
|
nativeControls = new MediaSession()
|
||||||
audioContext = new AudioContext({
|
audioContext = new AudioContext({
|
||||||
sampleRate: AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate,
|
sampleRate: AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate,
|
||||||
latencyHint: "playback"
|
latencyHint: "playback"
|
||||||
@ -37,9 +38,11 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
audioProcessors = new PlayerProcessors(this)
|
audioProcessors = new PlayerProcessors(this)
|
||||||
|
|
||||||
track_prev_instances = []
|
queue = new QueueManager({
|
||||||
track_instance = null
|
loadFunction: this.createInstance
|
||||||
track_next_instances = []
|
})
|
||||||
|
|
||||||
|
currentTrackInstance = null
|
||||||
|
|
||||||
public = {
|
public = {
|
||||||
start: this.start,
|
start: this.start,
|
||||||
@ -61,7 +64,7 @@ export default class Player extends Core {
|
|||||||
setSampleRate: setSampleRate,
|
setSampleRate: setSampleRate,
|
||||||
}),
|
}),
|
||||||
track: () => {
|
track: () => {
|
||||||
return this.track_instance
|
return this.queue.currentItem
|
||||||
},
|
},
|
||||||
eventBus: () => {
|
eventBus: () => {
|
||||||
return this.eventBus
|
return this.eventBus
|
||||||
@ -77,7 +80,7 @@ export default class Player extends Core {
|
|||||||
this.state.volume = 1
|
this.state.volume = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.native_controls.initialize()
|
await this.nativeControls.initialize()
|
||||||
await this.audioProcessors.initialize()
|
await this.audioProcessors.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,130 +88,75 @@ export default class Player extends Core {
|
|||||||
// Instance managing methods
|
// Instance managing methods
|
||||||
//
|
//
|
||||||
async abortPreloads() {
|
async abortPreloads() {
|
||||||
for await (const instance of this.track_next_instances) {
|
for await (const instance of this.queue.nextItems) {
|
||||||
if (instance.abortController?.abort) {
|
if (instance.abortController?.abort) {
|
||||||
instance.abortController.abort()
|
instance.abortController.abort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async preloadAudioInstance(instance) {
|
async createInstance(manifest) {
|
||||||
const isIndex = typeof instance === "number"
|
return new TrackInstance(this, manifest)
|
||||||
|
|
||||||
let index = isIndex ? instance : 0
|
|
||||||
|
|
||||||
if (isIndex) {
|
|
||||||
instance = this.track_next_instances[instance]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance) {
|
|
||||||
this.console.error("Instance not found to preload")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIndex) {
|
|
||||||
this.track_next_instances[index] = instance
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
async destroyCurrentInstance() {
|
|
||||||
if (!this.track_instance) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop playback
|
|
||||||
if (this.track_instance.audio) {
|
|
||||||
this.track_instance.audio.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset track_instance
|
|
||||||
this.track_instance = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Playback methods
|
// Playback methods
|
||||||
//
|
//
|
||||||
async play(instance, params = {}) {
|
async play(instance, params = {}) {
|
||||||
if (typeof instance === "number") {
|
|
||||||
if (instance < 0) {
|
|
||||||
instance = this.track_prev_instances[instance]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instance > 0) {
|
|
||||||
instance = this.track_instances[instance]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instance === 0) {
|
|
||||||
instance = this.track_instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
throw new Error("Audio instance is required")
|
throw new Error("Audio instance is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resume audio context if needed
|
||||||
if (this.audioContext.state === "suspended") {
|
if (this.audioContext.state === "suspended") {
|
||||||
this.audioContext.resume()
|
this.audioContext.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.track_instance) {
|
|
||||||
this.track_instance = this.track_instance.attachedProcessors[this.track_instance.attachedProcessors.length - 1]._destroy(this.track_instance)
|
|
||||||
|
|
||||||
this.destroyCurrentInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
// chage current track instance with provided
|
|
||||||
this.track_instance = instance
|
|
||||||
|
|
||||||
// initialize instance if is not
|
// initialize instance if is not
|
||||||
if (this.track_instance._initialized === false) {
|
if (this.queue.currentItem._initialized === false) {
|
||||||
this.track_instance = await instance.initialize()
|
this.queue.currentItem = await instance.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
// update manifest
|
// update manifest
|
||||||
this.state.track_manifest = this.track_instance.manifest
|
this.state.track_manifest = this.queue.currentItem.manifest
|
||||||
|
|
||||||
// attach processors
|
// attach processors
|
||||||
this.track_instance = await this.audioProcessors.attachProcessorsToInstance(this.track_instance)
|
this.queue.currentItem = await this.audioProcessors.attachProcessorsToInstance(this.queue.currentItem)
|
||||||
|
|
||||||
// reconstruct audio src if is not set
|
// reconstruct audio src if is not set
|
||||||
if (this.track_instance.audio.src !== this.track_instance.manifest.source) {
|
if (this.queue.currentItem.audio.src !== this.queue.currentItem.manifest.source) {
|
||||||
this.track_instance.audio.src = this.track_instance.manifest.source
|
this.queue.currentItem.audio.src = this.queue.currentItem.manifest.source
|
||||||
}
|
}
|
||||||
|
|
||||||
// set time to provided time, if not, set to 0
|
// set audio properties
|
||||||
this.track_instance.audio.currentTime = params.time ?? 0
|
this.queue.currentItem.audio.currentTime = params.time ?? 0
|
||||||
|
this.queue.currentItem.audio.muted = this.state.muted
|
||||||
this.track_instance.audio.muted = this.state.muted
|
this.queue.currentItem.audio.loop = this.state.playback_mode === "repeat"
|
||||||
this.track_instance.audio.loop = this.state.playback_mode === "repeat"
|
this.queue.currentItem.gainNode.gain.value = this.state.volume
|
||||||
|
|
||||||
this.track_instance.gainNode.gain.value = this.state.volume
|
|
||||||
|
|
||||||
// play
|
// play
|
||||||
await this.track_instance.audio.play()
|
await this.queue.currentItem.audio.play()
|
||||||
|
|
||||||
this.console.debug(`Playing track >`, this.track_instance)
|
this.console.debug(`Playing track >`, this.queue.currentItem)
|
||||||
|
|
||||||
// update native controls
|
// update native controls
|
||||||
this.native_controls.update(this.track_instance.manifest)
|
this.nativeControls.update(this.queue.currentItem.manifest)
|
||||||
|
|
||||||
return this.track_instance
|
return this.queue.currentItem
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(manifest, { time, startIndex = 0 } = {}) {
|
async start(manifest, { time, startIndex = 0 } = {}) {
|
||||||
this.ui.attachPlayerComponent()
|
this.ui.attachPlayerComponent()
|
||||||
|
|
||||||
// !IMPORTANT: abort preloads before destroying current instance
|
if (this.queue.currentItem) {
|
||||||
|
await this.queue.currentItem.stop()
|
||||||
|
}
|
||||||
|
|
||||||
await this.abortPreloads()
|
await this.abortPreloads()
|
||||||
await this.destroyCurrentInstance()
|
await this.queue.flush()
|
||||||
|
|
||||||
this.state.loading = true
|
this.state.loading = true
|
||||||
|
|
||||||
this.track_prev_instances = []
|
|
||||||
this.track_next_instances = []
|
|
||||||
|
|
||||||
let playlist = Array.isArray(manifest) ? manifest : [manifest]
|
let playlist = Array.isArray(manifest) ? manifest : [manifest]
|
||||||
|
|
||||||
if (playlist.length === 0) {
|
if (playlist.length === 0) {
|
||||||
@ -217,60 +165,47 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (playlist.some((item) => typeof item === "string")) {
|
if (playlist.some((item) => typeof item === "string")) {
|
||||||
playlist = await this.service_providers.resolveMany(playlist)
|
playlist = await this.serviceProviders.resolveMany(playlist)
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist = playlist.slice(startIndex)
|
for await (const [index, _manifest] of playlist.entries()) {
|
||||||
|
let instance = await this.createInstance(_manifest)
|
||||||
|
|
||||||
for (const [index, _manifest] of playlist.entries()) {
|
this.queue.add(instance)
|
||||||
let instance = new TrackInstance(this, _manifest)
|
|
||||||
|
|
||||||
this.track_next_instances.push(instance)
|
|
||||||
|
|
||||||
if (index === 0) {
|
|
||||||
this.play(this.track_next_instances[0], {
|
|
||||||
time: time ?? 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const item = this.queue.set(startIndex)
|
||||||
|
|
||||||
|
this.play(item, {
|
||||||
|
time: time ?? 0
|
||||||
|
})
|
||||||
|
|
||||||
return manifest
|
return manifest
|
||||||
}
|
}
|
||||||
|
|
||||||
next() {
|
next() {
|
||||||
if (this.track_next_instances.length > 0) {
|
if (this.queue.currentItem) {
|
||||||
// move current audio instance to history
|
this.queue.currentItem.stop()
|
||||||
this.track_prev_instances.push(this.track_next_instances.shift())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.track_next_instances.length === 0) {
|
//const isRandom = this.state.playback_mode === "shuffle"
|
||||||
this.console.log(`No more tracks to play, stopping...`)
|
const item = this.queue.next()
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
return this.stopPlayback()
|
return this.stopPlayback()
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextIndex = 0
|
return this.play(item)
|
||||||
|
|
||||||
if (this.state.playback_mode === "shuffle") {
|
|
||||||
nextIndex = Math.floor(Math.random() * this.track_next_instances.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.play(this.track_next_instances[nextIndex])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previous() {
|
previous() {
|
||||||
if (this.track_prev_instances.length > 0) {
|
if (this.queue.currentItem) {
|
||||||
// move current audio instance to history
|
this.queue.currentItem.stop()
|
||||||
this.track_next_instances.unshift(this.track_prev_instances.pop())
|
|
||||||
|
|
||||||
return this.play(this.track_next_instances[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.track_prev_instances.length === 0) {
|
const item = this.queue.previous()
|
||||||
this.console.log(`[PLAYER] No previous tracks, replying...`)
|
|
||||||
// replay the current track
|
return this.play(item)
|
||||||
return this.play(this.track_instance)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -290,23 +225,23 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
if (!this.track_instance) {
|
if (!this.queue.currentItem) {
|
||||||
this.console.error("No audio instance")
|
this.console.error("No audio instance")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// set gain exponentially
|
// set gain exponentially
|
||||||
this.track_instance.gainNode.gain.linearRampToValueAtTime(
|
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
|
||||||
0.0001,
|
0.0001,
|
||||||
this.audioContext.currentTime + (Player.gradualFadeMs / 1000)
|
this.audioContext.currentTime + (Player.gradualFadeMs / 1000)
|
||||||
)
|
)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.track_instance.audio.pause()
|
this.queue.currentItem.audio.pause()
|
||||||
resolve()
|
resolve()
|
||||||
}, Player.gradualFadeMs)
|
}, Player.gradualFadeMs)
|
||||||
|
|
||||||
this.native_controls.updateIsPlaying(false)
|
this.nativeControls.updateIsPlaying(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,25 +251,25 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
if (!this.track_instance) {
|
if (!this.queue.currentItem) {
|
||||||
this.console.error("No audio instance")
|
this.console.error("No audio instance")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure audio elemeto starts from 0 volume
|
// ensure audio elemeto starts from 0 volume
|
||||||
this.track_instance.gainNode.gain.value = 0.0001
|
this.queue.currentItem.gainNode.gain.value = 0.0001
|
||||||
|
|
||||||
this.track_instance.audio.play().then(() => {
|
this.queue.currentItem.audio.play().then(() => {
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
|
|
||||||
// set gain exponentially
|
// set gain exponentially
|
||||||
this.track_instance.gainNode.gain.linearRampToValueAtTime(
|
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
|
||||||
this.state.volume,
|
this.state.volume,
|
||||||
this.audioContext.currentTime + (Player.gradualFadeMs / 1000)
|
this.audioContext.currentTime + (Player.gradualFadeMs / 1000)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.native_controls.updateIsPlaying(true)
|
this.nativeControls.updateIsPlaying(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,8 +280,8 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
this.state.playback_mode = mode
|
this.state.playback_mode = mode
|
||||||
|
|
||||||
if (this.track_instance) {
|
if (this.queue.currentItem) {
|
||||||
this.track_instance.audio.loop = this.state.playback_mode === "repeat"
|
this.queue.currentItem.audio.loop = this.state.playback_mode === "repeat"
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioPlayerStorage.set("mode", mode)
|
AudioPlayerStorage.set("mode", mode)
|
||||||
@ -355,17 +290,22 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async stopPlayback() {
|
async stopPlayback() {
|
||||||
this.destroyCurrentInstance()
|
if (this.queue.currentItem) {
|
||||||
|
this.queue.currentItem.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.flush()
|
||||||
|
|
||||||
this.abortPreloads()
|
this.abortPreloads()
|
||||||
|
|
||||||
this.state.playback_status = "stopped"
|
this.state.playback_status = "stopped"
|
||||||
this.state.track_manifest = null
|
this.state.track_manifest = null
|
||||||
|
|
||||||
this.track_instance = null
|
this.queue.currentItem = null
|
||||||
this.track_next_instances = []
|
this.track_next_instances = []
|
||||||
this.track_prev_instances = []
|
this.track_prev_instances = []
|
||||||
|
|
||||||
this.native_controls.destroy()
|
this.nativeControls.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -383,7 +323,7 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
if (typeof to === "boolean") {
|
if (typeof to === "boolean") {
|
||||||
this.state.muted = to
|
this.state.muted = to
|
||||||
this.track_instance.audio.muted = to
|
this.queue.currentItem.audio.muted = to
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state.muted
|
return this.state.muted
|
||||||
@ -413,9 +353,9 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
AudioPlayerStorage.set("volume", volume)
|
AudioPlayerStorage.set("volume", volume)
|
||||||
|
|
||||||
if (this.track_instance) {
|
if (this.queue.currentItem) {
|
||||||
if (this.track_instance.gainNode) {
|
if (this.queue.currentItem.gainNode) {
|
||||||
this.track_instance.gainNode.gain.value = this.state.volume
|
this.queue.currentItem.gainNode.gain.value = this.state.volume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,31 +363,31 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
seek(time) {
|
seek(time) {
|
||||||
if (!this.track_instance || !this.track_instance.audio) {
|
if (!this.queue.currentItem || !this.queue.currentItem.audio) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// if time not provided, return current time
|
// if time not provided, return current time
|
||||||
if (typeof time === "undefined") {
|
if (typeof time === "undefined") {
|
||||||
return this.track_instance.audio.currentTime
|
return this.queue.currentItem.audio.currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// if time is provided, seek to that time
|
// if time is provided, seek to that time
|
||||||
if (typeof time === "number") {
|
if (typeof time === "number") {
|
||||||
this.console.log(`Seeking to ${time} | Duration: ${this.track_instance.audio.duration}`)
|
this.console.log(`Seeking to ${time} | Duration: ${this.queue.currentItem.audio.duration}`)
|
||||||
|
|
||||||
this.track_instance.audio.currentTime = time
|
this.queue.currentItem.audio.currentTime = time
|
||||||
|
|
||||||
return time
|
return time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
duration() {
|
duration() {
|
||||||
if (!this.track_instance || !this.track_instance.audio) {
|
if (!this.queue.currentItem || !this.queue.currentItem.audio) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.track_instance.audio.duration
|
return this.queue.currentItem.audio.duration
|
||||||
}
|
}
|
||||||
|
|
||||||
loop(to) {
|
loop(to) {
|
||||||
@ -458,8 +398,8 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
this.state.loop = to ?? !this.state.loop
|
this.state.loop = to ?? !this.state.loop
|
||||||
|
|
||||||
if (this.track_instance.audio) {
|
if (this.queue.currentItem.audio) {
|
||||||
this.track_instance.audio.loop = this.state.loop
|
this.queue.currentItem.audio.loop = this.state.loop
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state.loop
|
return this.state.loop
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Modal } from "antd"
|
|
||||||
import ProcessorNode from "../node"
|
import ProcessorNode from "../node"
|
||||||
import Presets from "../../classes/Presets"
|
import Presets from "../../classes/Presets"
|
||||||
|
|
||||||
@ -6,7 +5,7 @@ export default class CompressorProcessorNode extends ProcessorNode {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.presets_controller = new Presets({
|
this.presets = new Presets({
|
||||||
storage_key: "compressor",
|
storage_key: "compressor",
|
||||||
defaultPresetValue: {
|
defaultPresetValue: {
|
||||||
threshold: -50,
|
threshold: -50,
|
||||||
@ -15,98 +14,19 @@ export default class CompressorProcessorNode extends ProcessorNode {
|
|||||||
attack: 0.003,
|
attack: 0.003,
|
||||||
release: 0.25,
|
release: 0.25,
|
||||||
},
|
},
|
||||||
|
onApplyValues: this.applyValues.bind(this),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.state = {
|
|
||||||
compressorValues: this.presets_controller.currentPresetValues,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.exposeToPublic = {
|
this.exposeToPublic = {
|
||||||
presets: new Proxy(this.presets_controller, {
|
presets: this.presets,
|
||||||
get: function (target, key) {
|
detach: this._detach,
|
||||||
if (!key) {
|
attach: this._attach,
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
return target[key]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
deletePreset: this.deletePreset.bind(this),
|
|
||||||
createPreset: this.createPreset.bind(this),
|
|
||||||
changePreset: this.changePreset.bind(this),
|
|
||||||
resetDefaultValues: this.resetDefaultValues.bind(this),
|
|
||||||
modifyValues: this.modifyValues.bind(this),
|
|
||||||
detach: this._detach.bind(this),
|
|
||||||
attach: this._attach.bind(this),
|
|
||||||
values: this.state.compressorValues,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static refName = "compressor"
|
static refName = "compressor"
|
||||||
static dependsOnSettings = ["player.compressor"]
|
static dependsOnSettings = ["player.compressor"]
|
||||||
|
|
||||||
deletePreset(key) {
|
|
||||||
this.changePreset("default")
|
|
||||||
|
|
||||||
this.presets_controller.deletePreset(key)
|
|
||||||
|
|
||||||
return this.presets_controller.presets
|
|
||||||
}
|
|
||||||
|
|
||||||
createPreset(key, values) {
|
|
||||||
this.state = {
|
|
||||||
...this.state,
|
|
||||||
compressorValues: this.presets_controller.createPreset(key, values),
|
|
||||||
}
|
|
||||||
|
|
||||||
this.presets_controller.changePreset(key)
|
|
||||||
|
|
||||||
return this.presets_controller.presets
|
|
||||||
}
|
|
||||||
|
|
||||||
changePreset(key) {
|
|
||||||
const values = this.presets_controller.changePreset(key)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
...this.state,
|
|
||||||
compressorValues: values,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applyValues()
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
modifyValues(values) {
|
|
||||||
values = this.presets_controller.setToCurrent(values)
|
|
||||||
|
|
||||||
this.state.compressorValues = {
|
|
||||||
...this.state.compressorValues,
|
|
||||||
...values,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applyValues()
|
|
||||||
|
|
||||||
return this.state.compressorValues
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetDefaultValues() {
|
|
||||||
return await new Promise((resolve) => {
|
|
||||||
Modal.confirm({
|
|
||||||
title: "Reset to default values?",
|
|
||||||
content: "Are you sure you want to reset to default values?",
|
|
||||||
onOk: () => {
|
|
||||||
this.modifyValues(this.presets_controller.defaultPresetValue)
|
|
||||||
|
|
||||||
resolve(this.state.compressorValues)
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
resolve(this.state.compressorValues)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async init(AudioContext) {
|
async init(AudioContext) {
|
||||||
if (!AudioContext) {
|
if (!AudioContext) {
|
||||||
throw new Error("AudioContext is required")
|
throw new Error("AudioContext is required")
|
||||||
@ -118,8 +38,8 @@ export default class CompressorProcessorNode extends ProcessorNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyValues() {
|
applyValues() {
|
||||||
Object.keys(this.state.compressorValues).forEach((key) => {
|
Object.keys(this.presets.currentPresetValues).forEach((key) => {
|
||||||
this.processor[key].value = this.state.compressorValues[key]
|
this.processor[key].value = this.presets.currentPresetValues[key]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import { Modal } from "antd"
|
|
||||||
import ProcessorNode from "../node"
|
import ProcessorNode from "../node"
|
||||||
import Presets from "../../classes/Presets"
|
import Presets from "../../classes/Presets"
|
||||||
|
|
||||||
@ -6,7 +5,7 @@ export default class EqProcessorNode extends ProcessorNode {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.presets_controller = new Presets({
|
this.presets = new Presets({
|
||||||
storage_key: "eq",
|
storage_key: "eq",
|
||||||
defaultPresetValue: {
|
defaultPresetValue: {
|
||||||
32: 0,
|
32: 0,
|
||||||
@ -20,94 +19,21 @@ export default class EqProcessorNode extends ProcessorNode {
|
|||||||
8000: 0,
|
8000: 0,
|
||||||
16000: 0,
|
16000: 0,
|
||||||
},
|
},
|
||||||
|
onApplyValues: this.applyValues.bind(this),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.state = {
|
|
||||||
eqValues: this.presets_controller.currentPresetValues,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.exposeToPublic = {
|
this.exposeToPublic = {
|
||||||
presets: new Proxy(this.presets_controller, {
|
presets: this.presets,
|
||||||
get: function (target, key) {
|
|
||||||
if (!key) {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
return target[key]
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
deletePreset: this.deletePreset.bind(this),
|
|
||||||
createPreset: this.createPreset.bind(this),
|
|
||||||
changePreset: this.changePreset.bind(this),
|
|
||||||
modifyValues: this.modifyValues.bind(this),
|
|
||||||
resetDefaultValues: this.resetDefaultValues.bind(this),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static refName = "eq"
|
static refName = "eq"
|
||||||
static lock = true
|
static lock = true
|
||||||
|
|
||||||
deletePreset(key) {
|
|
||||||
this.changePreset("default")
|
|
||||||
|
|
||||||
this.presets_controller.deletePreset(key)
|
|
||||||
|
|
||||||
return this.presets_controller.presets
|
|
||||||
}
|
|
||||||
|
|
||||||
createPreset(key, values) {
|
|
||||||
this.state = {
|
|
||||||
...this.state,
|
|
||||||
eqValues: this.presets_controller.createPreset(key, values),
|
|
||||||
}
|
|
||||||
|
|
||||||
this.presets_controller.changePreset(key)
|
|
||||||
|
|
||||||
return this.presets_controller.presets
|
|
||||||
}
|
|
||||||
|
|
||||||
changePreset(key) {
|
|
||||||
const values = this.presets_controller.changePreset(key)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
...this.state,
|
|
||||||
eqValues: values,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applyValues()
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
modifyValues(values) {
|
|
||||||
values = this.presets_controller.setToCurrent(values)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
...this.state,
|
|
||||||
eqValues: values,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applyValues()
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
resetDefaultValues() {
|
|
||||||
Modal.confirm({
|
|
||||||
title: "Reset to default values?",
|
|
||||||
content: "Are you sure you want to reset to default values?",
|
|
||||||
onOk: () => {
|
|
||||||
this.modifyValues(this.presets_controller.defaultPresetValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return this.state.eqValues
|
|
||||||
}
|
|
||||||
|
|
||||||
applyValues() {
|
applyValues() {
|
||||||
// apply to current instance
|
// apply to current instance
|
||||||
this.processor.eqNodes.forEach((processor) => {
|
this.processor.eqNodes.forEach((processor) => {
|
||||||
const gainValue = this.state.eqValues[processor.frequency.value]
|
const gainValue = this.presets.currentPresetValues[processor.frequency.value]
|
||||||
|
|
||||||
if (processor.gain.value !== gainValue) {
|
if (processor.gain.value !== gainValue) {
|
||||||
console.debug(`[EQ] Applying values to ${processor.frequency.value} Hz frequency with gain ${gainValue}`)
|
console.debug(`[EQ] Applying values to ${processor.frequency.value} Hz frequency with gain ${gainValue}`)
|
||||||
@ -127,7 +53,7 @@ export default class EqProcessorNode extends ProcessorNode {
|
|||||||
|
|
||||||
this.processor.eqNodes = []
|
this.processor.eqNodes = []
|
||||||
|
|
||||||
const values = Object.entries(this.state.eqValues).map((entry) => {
|
const values = Object.entries(this.presets.currentPresetValues).map((entry) => {
|
||||||
return {
|
return {
|
||||||
freq: parseFloat(entry[0]),
|
freq: parseFloat(entry[0]),
|
||||||
gain: parseFloat(entry[1]),
|
gain: parseFloat(entry[1]),
|
||||||
|
@ -8,7 +8,23 @@ export default class RemoteStorage extends Core {
|
|||||||
static depends = ["api", "tasksQueue"]
|
static depends = ["api", "tasksQueue"]
|
||||||
|
|
||||||
public = {
|
public = {
|
||||||
uploadFile: this.uploadFile.bind(this),
|
uploadFile: this.uploadFile,
|
||||||
|
getFileHash: this.getFileHash,
|
||||||
|
binaryArrayToFile: this.binaryArrayToFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryArrayToFile(bin, filename) {
|
||||||
|
const { format, data } = bin
|
||||||
|
|
||||||
|
const filenameExt = format.split("/")[1]
|
||||||
|
filename = `${filename}.${filenameExt}`
|
||||||
|
|
||||||
|
const byteArray = new Uint8Array(data)
|
||||||
|
const blob = new Blob([byteArray], { type: data.type })
|
||||||
|
|
||||||
|
return new File([blob], filename, {
|
||||||
|
type: format,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFileHash(file) {
|
async getFileHash(file) {
|
||||||
|
@ -3,7 +3,9 @@ import React from "react"
|
|||||||
const usePageWidgets = (widgets = []) => {
|
const usePageWidgets = (widgets = []) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
for (const widget of widgets) {
|
for (const widget of widgets) {
|
||||||
app.layout.tools_bar.attachRender(widget.id, widget.component, widget.props)
|
app.layout.tools_bar.attachRender(widget.id, widget.component, widget.props, {
|
||||||
|
position: "top",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
import { Motion, spring } from "react-motion"
|
import { Motion, spring } from "react-motion"
|
||||||
import { Translation } from "react-i18next"
|
|
||||||
import { Icons } from "@components/Icons"
|
|
||||||
|
|
||||||
import WidgetsWrapper from "@components/WidgetsWrapper"
|
import WidgetsWrapper from "@components/WidgetsWrapper"
|
||||||
|
|
||||||
@ -11,7 +9,10 @@ import "./index.less"
|
|||||||
export default class ToolsBar extends React.Component {
|
export default class ToolsBar extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
visible: false,
|
visible: false,
|
||||||
renders: [],
|
renders: {
|
||||||
|
top: [],
|
||||||
|
bottom: [],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -34,20 +35,25 @@ export default class ToolsBar extends React.Component {
|
|||||||
visible: to ?? !this.state.visible,
|
visible: to ?? !this.state.visible,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
attachRender: (id, component, props) => {
|
attachRender: (id, component, props, { position = "bottom" } = {}) => {
|
||||||
this.setState({
|
this.setState((prev) => {
|
||||||
renders: [...this.state.renders, {
|
prev.renders[position].push({
|
||||||
id: id,
|
id: id,
|
||||||
component: component,
|
component: component,
|
||||||
props: props,
|
props: props,
|
||||||
}],
|
})
|
||||||
|
|
||||||
|
return prev
|
||||||
})
|
})
|
||||||
|
|
||||||
return component
|
return component
|
||||||
},
|
},
|
||||||
detachRender: (id) => {
|
detachRender: (id) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
renders: this.state.renders.filter((render) => render.id !== id),
|
renders: {
|
||||||
|
top: this.state.renders.top.filter((render) => render.id !== id),
|
||||||
|
bottom: this.state.renders.bottom.filter((render) => render.id !== id),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -78,15 +84,29 @@ export default class ToolsBar extends React.Component {
|
|||||||
id="tools_bar"
|
id="tools_bar"
|
||||||
className="tools-bar"
|
className="tools-bar"
|
||||||
>
|
>
|
||||||
<div className="attached_renders">
|
<div className="attached_renders top">
|
||||||
{
|
{
|
||||||
this.state.renders.map((render) => {
|
this.state.renders.top.map((render, index) => {
|
||||||
return React.createElement(render.component, render.props)
|
return React.createElement(render.component, {
|
||||||
|
...render.props,
|
||||||
|
key: index,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WidgetsWrapper />
|
<WidgetsWrapper />
|
||||||
|
|
||||||
|
<div className="attached_renders bottom">
|
||||||
|
{
|
||||||
|
this.state.renders.bottom.map((render, index) => {
|
||||||
|
return React.createElement(render.component, {
|
||||||
|
...render.props,
|
||||||
|
key: index,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}}
|
}}
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tools-bar {
|
.tools-bar {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
@ -46,14 +48,32 @@
|
|||||||
flex: 0;
|
flex: 0;
|
||||||
|
|
||||||
.attached_renders {
|
.attached_renders {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: fit-content;
|
||||||
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
|
&.bottom {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 100%;
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ const ServiceIndicator = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AudioPlayer = (props) => {
|
const AudioPlayer = (props) => {
|
||||||
const playerState = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (app.currentDragger) {
|
if (app.currentDragger) {
|
||||||
|
115
packages/app/src/pages/_debug/queuemanager/index.jsx
Normal file
115
packages/app/src/pages/_debug/queuemanager/index.jsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import React, { useState } from "react"
|
||||||
|
import { Button, Card, List, Typography, Space, Divider, notification } from "antd"
|
||||||
|
import QueueManager from "@cores/player/classes/QueueManager"
|
||||||
|
|
||||||
|
const { Title, Text } = Typography
|
||||||
|
|
||||||
|
const QueueDebugger = () => {
|
||||||
|
const queueManager = React.useRef(new QueueManager())
|
||||||
|
|
||||||
|
const [current, setCurrent] = useState(queueManager.current.currentItem)
|
||||||
|
const [prevItems, setPrevItems] = useState([...queueManager.current.prevItems])
|
||||||
|
const [nextItems, setNextItems] = useState([...queueManager.current.nextItems])
|
||||||
|
|
||||||
|
const updateQueueState = () => {
|
||||||
|
setCurrent(queueManager.current.currentItem)
|
||||||
|
setPrevItems([...queueManager.current.prevItems])
|
||||||
|
setNextItems([...queueManager.current.nextItems])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNext = (random = false) => {
|
||||||
|
queueManager.current.next(random)
|
||||||
|
updateQueueState()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePrevious = () => {
|
||||||
|
queueManager.current.previous()
|
||||||
|
updateQueueState()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSet = (item) => {
|
||||||
|
try {
|
||||||
|
queueManager.current.set(item)
|
||||||
|
updateQueueState()
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: "Error",
|
||||||
|
description: error.message,
|
||||||
|
placement: "bottomRight",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
const newItem = {
|
||||||
|
id: (nextItems.length + prevItems.length + 2).toString(),
|
||||||
|
name: `Item ${nextItems.length + prevItems.length + 2}`
|
||||||
|
}
|
||||||
|
queueManager.current.add(newItem)
|
||||||
|
updateQueueState()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemove = (item) => {
|
||||||
|
queueManager.current.remove(item)
|
||||||
|
updateQueueState()
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
queueManager.current.add({ id: "1", name: "Item 1" })
|
||||||
|
queueManager.current.add({ id: "2", name: "Item 2" })
|
||||||
|
queueManager.current.add({ id: "3", name: "Item 3" })
|
||||||
|
queueManager.current.add({ id: "4", name: "Item 4" })
|
||||||
|
|
||||||
|
updateQueueState()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" size="large" style={{ width: "100%", padding: "20px" }}>
|
||||||
|
<Title level={2}>Queue Debugger</Title>
|
||||||
|
<Card title="Current Item">
|
||||||
|
<Text>{current ? current.name : "None"}</Text>
|
||||||
|
</Card>
|
||||||
|
<Divider />
|
||||||
|
<Card title="Previous Items">
|
||||||
|
<List
|
||||||
|
bordered
|
||||||
|
dataSource={prevItems}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item
|
||||||
|
actions={[
|
||||||
|
<Button type="link" onClick={() => handleSet(item)}>Set</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title="Next Items">
|
||||||
|
<List
|
||||||
|
bordered
|
||||||
|
dataSource={nextItems}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item
|
||||||
|
actions={[
|
||||||
|
<Button type="link" onClick={() => handleSet(item)}>Set</Button>,
|
||||||
|
<Button type="link" danger onClick={() => handleRemove(item)}>Remove</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Divider />
|
||||||
|
<Space>
|
||||||
|
<Button onClick={handlePrevious}>Previous</Button>
|
||||||
|
<Button onClick={() => handleNext(false)}>Next</Button>
|
||||||
|
<Button onClick={() => handleNext(true)}>Next (Random)</Button>
|
||||||
|
<Button type="primary" onClick={handleAdd}>Add Item</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QueueDebugger
|
@ -47,7 +47,7 @@ const RenderAlbum = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PlayerController = React.forwardRef((props, ref) => {
|
const PlayerController = React.forwardRef((props, ref) => {
|
||||||
const playerState = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const titleRef = React.useRef()
|
const titleRef = React.useRef()
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { Motion, spring } from "react-motion"
|
|||||||
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
||||||
|
|
||||||
const LyricsText = React.forwardRef((props, textRef) => {
|
const LyricsText = React.forwardRef((props, textRef) => {
|
||||||
const playerState = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const { lyrics } = props
|
const { lyrics } = props
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ const LyricsText = React.forwardRef((props, textRef) => {
|
|||||||
setVisible(false)
|
setVisible(false)
|
||||||
} else {
|
} else {
|
||||||
setVisible(true)
|
setVisible(true)
|
||||||
console.log(`Scrolling to line ${currentLineIndex}`)
|
|
||||||
// find line element by id
|
// find line element by id
|
||||||
const lineElement = textRef.current.querySelector(`#lyrics-line-${currentLineIndex}`)
|
const lineElement = textRef.current.querySelector(`#lyrics-line-${currentLineIndex}`)
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
|||||||
const maxLatencyInMs = 55
|
const maxLatencyInMs = 55
|
||||||
|
|
||||||
const LyricsVideo = React.forwardRef((props, videoRef) => {
|
const LyricsVideo = React.forwardRef((props, videoRef) => {
|
||||||
const playerState = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const { lyrics } = props
|
const { lyrics } = props
|
||||||
|
|
||||||
@ -57,12 +57,10 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
|
|||||||
setCurrentVideoLatency(currentVideoTimeDiff)
|
setCurrentVideoLatency(currentVideoTimeDiff)
|
||||||
|
|
||||||
if (syncingVideo === true) {
|
if (syncingVideo === true) {
|
||||||
console.log(`Syncing video...`)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentVideoTimeDiff > maxOffset) {
|
if (currentVideoTimeDiff > maxOffset) {
|
||||||
console.warn(`Video offset exceeds`, maxOffset)
|
|
||||||
seekVideoToSyncAudio()
|
seekVideoToSyncAudio()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ function getDominantColorStr(track_manifest) {
|
|||||||
return `${values[0]}, ${values[1]}, ${values[2]}`
|
return `${values[0]}, ${values[1]}, ${values[2]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const EnchancedLyrics = (props) => {
|
const EnchancedLyricsPage = () => {
|
||||||
const playerState = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const [initialized, setInitialized] = React.useState(false)
|
const [initialized, setInitialized] = React.useState(false)
|
||||||
const [lyrics, setLyrics] = React.useState(null)
|
const [lyrics, setLyrics] = React.useState(null)
|
||||||
@ -71,11 +71,6 @@ const EnchancedLyrics = (props) => {
|
|||||||
}
|
}
|
||||||
}, [playerState.track_manifest])
|
}, [playerState.track_manifest])
|
||||||
|
|
||||||
//* Handle when lyrics data change
|
|
||||||
React.useEffect(() => {
|
|
||||||
console.log(lyrics)
|
|
||||||
}, [lyrics])
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}, [])
|
}, [])
|
||||||
@ -129,4 +124,4 @@ const EnchancedLyrics = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EnchancedLyrics
|
export default EnchancedLyricsPage
|
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
import Image from "@components/Image"
|
||||||
|
|
||||||
|
import MusicModel from "@models/music"
|
||||||
|
|
||||||
|
const FeaturedPlaylist = (props) => {
|
||||||
|
const [featuredPlaylist, setFeaturedPlaylist] = React.useState(false)
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (!featuredPlaylist) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.navigation.goToPlaylist(featuredPlaylist.playlist_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
MusicModel.getFeaturedPlaylists().then((data) => {
|
||||||
|
if (data[0]) {
|
||||||
|
console.log(`Loaded featured playlist >`, data[0])
|
||||||
|
setFeaturedPlaylist(data[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!featuredPlaylist) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="featured_playlist" onClick={onClick}>
|
||||||
|
<Image
|
||||||
|
src={featuredPlaylist.cover_url}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="featured_playlist_content">
|
||||||
|
<h1>{featuredPlaylist.title}</h1>
|
||||||
|
<p>{featuredPlaylist.description}</p>
|
||||||
|
|
||||||
|
{
|
||||||
|
featuredPlaylist.genre && <div className="featured_playlist_genre">
|
||||||
|
<span>{featuredPlaylist.genre}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FeaturedPlaylist
|
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
import Searcher from "@components/Searcher"
|
||||||
|
import MusicModel from "@models/music"
|
||||||
|
|
||||||
|
const MusicNavbar = (props) => {
|
||||||
|
return <div className="music_navbar">
|
||||||
|
<Searcher
|
||||||
|
useUrlQuery
|
||||||
|
renderResults={false}
|
||||||
|
model={MusicModel.search}
|
||||||
|
onSearchResult={props.setSearchResults}
|
||||||
|
onEmpty={() => props.setSearchResults(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MusicNavbar
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { Icons } from "@components/Icons"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const RecentlyPlayedList = (props) => {
|
||||||
|
return <div className="recently_played">
|
||||||
|
<div className="recently_played-header">
|
||||||
|
<h1>Recently played</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecentlyPlayedList
|
@ -0,0 +1,17 @@
|
|||||||
|
.recently_played {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.recently_played-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import classnames from "classnames"
|
||||||
|
import { Translation } from "react-i18next"
|
||||||
|
|
||||||
|
import { createIconRender } from "@components/Icons"
|
||||||
|
import MusicTrack from "@components/Music/Track"
|
||||||
|
import PlaylistItem from "@components/Music/PlaylistItem"
|
||||||
|
|
||||||
|
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 SearchResults
|
@ -1,175 +1,27 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import * as antd from "antd"
|
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
import { Translation } from "react-i18next"
|
|
||||||
|
|
||||||
import Image from "@components/Image"
|
|
||||||
import Searcher from "@components/Searcher"
|
import Searcher from "@components/Searcher"
|
||||||
import { Icons, createIconRender } from "@components/Icons"
|
import { Icons } from "@components/Icons"
|
||||||
import MusicTrack from "@components/Music/Track"
|
|
||||||
import PlaylistItem from "@components/Music/PlaylistItem"
|
|
||||||
|
|
||||||
import ReleasesList from "@components/ReleasesList"
|
|
||||||
|
|
||||||
import FeedModel from "@models/feed"
|
import FeedModel from "@models/feed"
|
||||||
import MusicModel from "@models/music"
|
import MusicModel from "@models/music"
|
||||||
|
|
||||||
|
import Navbar from "./components/Navbar"
|
||||||
|
import RecentlyPlayedList from "./components/RecentlyPlayedList"
|
||||||
|
import SearchResults from "./components/SearchResults"
|
||||||
|
import ReleasesList from "./components/ReleasesList"
|
||||||
|
import FeaturedPlaylist from "./components/FeaturedPlaylist"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
const FeaturedPlaylist = (props) => {
|
const MusicExploreTab = (props) => {
|
||||||
const [featuredPlaylist, setFeaturedPlaylist] = React.useState(false)
|
|
||||||
|
|
||||||
const onClick = () => {
|
|
||||||
if (!featuredPlaylist) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
app.navigation.goToPlaylist(featuredPlaylist.playlist_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
MusicModel.getFeaturedPlaylists().then((data) => {
|
|
||||||
if (data[0]) {
|
|
||||||
console.log(`Loaded featured playlist >`, data[0])
|
|
||||||
setFeaturedPlaylist(data[0])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!featuredPlaylist) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="featured_playlist" onClick={onClick}>
|
|
||||||
<Image
|
|
||||||
src={featuredPlaylist.cover_url}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="featured_playlist_content">
|
|
||||||
<h1>{featuredPlaylist.title}</h1>
|
|
||||||
<p>{featuredPlaylist.description}</p>
|
|
||||||
|
|
||||||
{
|
|
||||||
featuredPlaylist.genre && <div className="featured_playlist_genre">
|
|
||||||
<span>{featuredPlaylist.genre}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const MusicNavbar = (props) => {
|
|
||||||
return <div className="music_navbar">
|
|
||||||
<Searcher
|
|
||||||
useUrlQuery
|
|
||||||
renderResults={false}
|
|
||||||
model={MusicModel.search}
|
|
||||||
onSearchResult={props.setSearchResults}
|
|
||||||
onEmpty={() => props.setSearchResults(false)}
|
|
||||||
/>
|
|
||||||
</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)
|
const [searchResults, setSearchResults] = React.useState(false)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
app.layout.toggleCenteredContent(true)
|
app.layout.toggleCenteredContent(true)
|
||||||
|
|
||||||
app.layout.page_panels.attachComponent("music_navbar", MusicNavbar, {
|
app.layout.page_panels.attachComponent("music_navbar", Navbar, {
|
||||||
props: {
|
props: {
|
||||||
setSearchResults: setSearchResults,
|
setSearchResults: setSearchResults,
|
||||||
}
|
}
|
||||||
@ -192,9 +44,6 @@ export default (props) => {
|
|||||||
useUrlQuery
|
useUrlQuery
|
||||||
renderResults={false}
|
renderResults={false}
|
||||||
model={MusicModel.search}
|
model={MusicModel.search}
|
||||||
modelParams={{
|
|
||||||
useTidal: app.cores.sync.getActiveLinkedServices().tidal,
|
|
||||||
}}
|
|
||||||
onSearchResult={setSearchResults}
|
onSearchResult={setSearchResults}
|
||||||
onEmpty={() => setSearchResults(false)}
|
onEmpty={() => setSearchResults(false)}
|
||||||
/>
|
/>
|
||||||
@ -210,6 +59,8 @@ export default (props) => {
|
|||||||
!searchResults && <div className="feed_main">
|
!searchResults && <div className="feed_main">
|
||||||
<FeaturedPlaylist />
|
<FeaturedPlaylist />
|
||||||
|
|
||||||
|
<RecentlyPlayedList />
|
||||||
|
|
||||||
<ReleasesList
|
<ReleasesList
|
||||||
headerTitle="From your following artists"
|
headerTitle="From your following artists"
|
||||||
headerIcon={<Icons.MdPerson />}
|
headerIcon={<Icons.MdPerson />}
|
||||||
@ -225,3 +76,5 @@ export default (props) => {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default MusicExploreTab
|
@ -49,47 +49,62 @@ export default class FavoriteTracks extends React.Component {
|
|||||||
loading: true,
|
loading: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await MusicModel.getFavoriteTracks({
|
const result = await MusicModel.getFavouriteFolder({
|
||||||
useTidal: app.cores.sync.getActiveLinkedServices().tidal,
|
|
||||||
offset: offset,
|
offset: offset,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
}).catch((err) => {
|
}).catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: err.message,
|
error: error.message,
|
||||||
})
|
})
|
||||||
return false
|
|
||||||
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log("Loaded favorites => ", result)
|
console.log("Loaded favorites => ", result)
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
const { tracks, total_length } = result
|
const {
|
||||||
|
tracks,
|
||||||
|
releases,
|
||||||
|
playlists,
|
||||||
|
total_length,
|
||||||
|
} = result
|
||||||
|
|
||||||
this.setState({
|
const data = [
|
||||||
total_length
|
...tracks.list,
|
||||||
})
|
...releases.list,
|
||||||
|
...playlists.list,
|
||||||
|
]
|
||||||
|
|
||||||
if (tracks.length === 0) {
|
if (total_length === 0) {
|
||||||
if (offset === 0) {
|
this.setState({
|
||||||
this.setState({
|
empty: true,
|
||||||
empty: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.setState({
|
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
|
initialLoading: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return this.setState({
|
||||||
|
empty: false,
|
||||||
|
hasMore: false,
|
||||||
|
initialLoading: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replace) {
|
if (replace) {
|
||||||
this.setState({
|
this.setState({
|
||||||
list: tracks,
|
list: data,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
list: [...this.state.list, ...tracks],
|
list: [...this.state.list, ...data],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
total_length
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -112,7 +127,7 @@ export default class FavoriteTracks extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <PlaylistView
|
return <PlaylistView
|
||||||
favorite
|
favorite
|
||||||
type="vertical"
|
type="vertical"
|
||||||
playlist={{
|
playlist={{
|
||||||
title: "Your favorites",
|
title: "Your favorites",
|
||||||
@ -122,7 +137,6 @@ export default class FavoriteTracks extends React.Component {
|
|||||||
centered={app.isMobile}
|
centered={app.isMobile}
|
||||||
onLoadMore={this.onLoadMore}
|
onLoadMore={this.onLoadMore}
|
||||||
hasMore={this.state.hasMore}
|
hasMore={this.state.hasMore}
|
||||||
empty={this.state.empty}
|
|
||||||
length={this.state.total_length}
|
length={this.state.total_length}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -2,23 +2,12 @@ import React from "react"
|
|||||||
import { Translation } from "react-i18next"
|
import { Translation } from "react-i18next"
|
||||||
|
|
||||||
import { PagePanelWithNavMenu } from "@components/PagePanels"
|
import { PagePanelWithNavMenu } from "@components/PagePanels"
|
||||||
|
import TrendingsCard from "@components/TrendingsCard"
|
||||||
|
|
||||||
import usePageWidgets from "@hooks/usePageWidgets"
|
import usePageWidgets from "@hooks/usePageWidgets"
|
||||||
|
|
||||||
import Tabs from "./tabs"
|
import Tabs from "./tabs"
|
||||||
|
|
||||||
const TrendingsCard = () => {
|
|
||||||
return <div className="card">
|
|
||||||
<div className="card-header">
|
|
||||||
<span>Trendings</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="card-content">
|
|
||||||
<span>XD</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const TimelinePage = () => {
|
const TimelinePage = () => {
|
||||||
usePageWidgets([
|
usePageWidgets([
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@ import * as antd from "antd"
|
|||||||
|
|
||||||
import { Icons } from "@components/Icons"
|
import { Icons } from "@components/Icons"
|
||||||
|
|
||||||
import UserModel from "@models/user"
|
import AuthModel from "@models/auth"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ const ChangePasswordComponent = (props) => {
|
|||||||
setError(null)
|
setError(null)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
const result = await UserModel.changePassword({ currentPassword, newPassword }).catch((err) => {
|
const result = await AuthModel.changePassword({ currentPassword, newPassword }).catch((err) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
setError(err.response.data.message)
|
setError(err.response.data.message)
|
||||||
return null
|
return null
|
||||||
|
@ -6,14 +6,23 @@ import { Icons } from "@components/Icons"
|
|||||||
import Sliders from "../sliderValues"
|
import Sliders from "../sliderValues"
|
||||||
|
|
||||||
export default (props) => {
|
export default (props) => {
|
||||||
const [selectedPreset, setSelectedPreset] = React.useState(props.controller.presets.currentPresetKey)
|
const [selectedPreset, setSelectedPreset] = React.useState(props.controller.currentPresetKey)
|
||||||
const [presets, setPresets] = React.useState(props.controller.presets.presets ?? {})
|
const [presets, setPresets] = React.useState(props.controller.presets ?? {})
|
||||||
|
|
||||||
const createPreset = (key) => {
|
const createPreset = (key) => {
|
||||||
setPresets(props.controller.createPreset(key))
|
const presets = props.controller.createPreset(key)
|
||||||
|
|
||||||
|
setPresets(presets)
|
||||||
setSelectedPreset(key)
|
setSelectedPreset(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletePreset = (key) => {
|
||||||
|
const presets = props.controller.deletePreset(key)
|
||||||
|
|
||||||
|
setPresets(presets)
|
||||||
|
setSelectedPreset(props.controller.currentPresetKey)
|
||||||
|
}
|
||||||
|
|
||||||
const handleCreateNewPreset = () => {
|
const handleCreateNewPreset = () => {
|
||||||
app.layout.modal.open("create_preset", (props) => {
|
app.layout.modal.open("create_preset", (props) => {
|
||||||
const [presetKey, setPresetKey] = React.useState("")
|
const [presetKey, setPresetKey] = React.useState("")
|
||||||
@ -51,15 +60,11 @@ export default (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeletePreset = () => {
|
const handleDeleteCurrentPreset = () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "Delete preset",
|
title: "Delete preset",
|
||||||
content: "Are you sure you want to delete this preset?",
|
content: "Are you sure you want to delete this preset?",
|
||||||
onOk: () => {
|
onOk: () => deletePreset(selectedPreset)
|
||||||
props.controller.deletePreset(selectedPreset)
|
|
||||||
setPresets(props.controller.presets.presets ?? {})
|
|
||||||
setSelectedPreset(props.controller.presets.currentPresetKey)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,14 +82,13 @@ export default (props) => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const presets = props.controller.presets.presets ?? {}
|
const presetValues = props.controller.presets[selectedPreset]
|
||||||
const preset = presets[selectedPreset]
|
|
||||||
|
|
||||||
if (props.controller.presets.currentPresetKey !== selectedPreset) {
|
if (props.controller.currentPresetKey !== selectedPreset) {
|
||||||
props.controller.changePreset(selectedPreset)
|
props.controller.changePreset(selectedPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
props.ctx.updateCurrentValue(preset)
|
props.ctx.updateCurrentValue(presetValues)
|
||||||
}, [selectedPreset])
|
}, [selectedPreset])
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
@ -152,7 +156,7 @@ export default (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDeletePreset}
|
onClick={handleDeleteCurrentPreset}
|
||||||
icon={<Icons.MdDelete />}
|
icon={<Icons.MdDelete />}
|
||||||
disabled={selectedPreset === "default"}
|
disabled={selectedPreset === "default"}
|
||||||
/>
|
/>
|
||||||
|
@ -64,7 +64,7 @@ export default {
|
|||||||
return app.cores.player.audioContext.sampleRate
|
return app.cores.player.audioContext.sampleRate
|
||||||
},
|
},
|
||||||
onUpdate: async (value) => {
|
onUpdate: async (value) => {
|
||||||
const sampleRate = await app.cores.player.setSampleRate(value)
|
const sampleRate = await app.cores.player.controls.setSampleRate(value)
|
||||||
|
|
||||||
return sampleRate
|
return sampleRate
|
||||||
},
|
},
|
||||||
@ -93,6 +93,18 @@ export default {
|
|||||||
app.cores.player.compressor.detach()
|
app.cores.player.compressor.detach()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
extraActions: [
|
||||||
|
{
|
||||||
|
id: "reset",
|
||||||
|
title: "Default",
|
||||||
|
icon: "MdRefresh",
|
||||||
|
onClick: async (ctx) => {
|
||||||
|
const values = await app.cores.player.compressor.presets.setCurrentPresetToDefault()
|
||||||
|
|
||||||
|
ctx.updateCurrentValue(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
valueFormat: (value) => `${value}dB`,
|
valueFormat: (value) => `${value}dB`,
|
||||||
sliders: [
|
sliders: [
|
||||||
@ -131,20 +143,8 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
extraActions: [
|
|
||||||
{
|
|
||||||
id: "reset",
|
|
||||||
title: "Default",
|
|
||||||
icon: "MdRefresh",
|
|
||||||
onClick: async (ctx) => {
|
|
||||||
const values = await app.cores.player.compressor.resetDefaultValues()
|
|
||||||
|
|
||||||
ctx.updateCurrentValue(values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
onUpdate: (value) => {
|
onUpdate: (value) => {
|
||||||
app.cores.player.compressor.modifyValues(value)
|
app.cores.player.compressor.presets.setToCurrent(value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
},
|
},
|
||||||
@ -164,15 +164,15 @@ export default {
|
|||||||
title: "Reset",
|
title: "Reset",
|
||||||
icon: "MdRefresh",
|
icon: "MdRefresh",
|
||||||
onClick: (ctx) => {
|
onClick: (ctx) => {
|
||||||
const values = app.cores.player.eq.resetDefaultValues()
|
const values = app.cores.player.eq.presets.setCurrentPresetToDefault()
|
||||||
|
|
||||||
ctx.updateCurrentValue(values)
|
ctx.updateCurrentValue(values)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dependsOn: {
|
// dependsOn: {
|
||||||
"player.equalizer": true
|
// "player.equalizer": true
|
||||||
},
|
// },
|
||||||
props: {
|
props: {
|
||||||
valueFormat: (value) => `${value}dB`,
|
valueFormat: (value) => `${value}dB`,
|
||||||
marks: [
|
marks: [
|
||||||
@ -251,7 +251,7 @@ export default {
|
|||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
app.cores.player.eq.modifyValues(values)
|
app.cores.player.eq.presets.setToCurrent(values)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@ import SlidersWithPresets from "../../../components/slidersWithPresets"
|
|||||||
export default (props) => {
|
export default (props) => {
|
||||||
return <SlidersWithPresets
|
return <SlidersWithPresets
|
||||||
{...props}
|
{...props}
|
||||||
controller={app.cores.player.compressor}
|
controller={app.cores.player.compressor.presets}
|
||||||
extraHeaderItems={[
|
extraHeaderItems={[
|
||||||
<Switch
|
<Switch
|
||||||
onChange={props.onEnabledChange}
|
onChange={props.onEnabledChange}
|
||||||
|
@ -3,6 +3,6 @@ import SlidersWithPresets from "../../../components/slidersWithPresets"
|
|||||||
export default (props) => {
|
export default (props) => {
|
||||||
return <SlidersWithPresets
|
return <SlidersWithPresets
|
||||||
{...props}
|
{...props}
|
||||||
controller={app.cores.player.eq}
|
controller={app.cores.player.eq.presets}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
@ -26,6 +26,18 @@ export default defineConfig({
|
|||||||
headers: {
|
headers: {
|
||||||
"Strict-Transport-Security": `max-age=${oneYearInSeconds}`
|
"Strict-Transport-Security": `max-age=${oneYearInSeconds}`
|
||||||
},
|
},
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "https://0.0.0.0:9000",
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||||
|
hostRewrite: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
xfwd: true,
|
||||||
|
//ws: true,
|
||||||
|
toProxy: true,
|
||||||
|
secure: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
@ -42,7 +54,7 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
target: "esnext",
|
target: "esnext",
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output:{
|
output: {
|
||||||
manualChunks(id) {
|
manualChunks(id) {
|
||||||
if (id.includes('node_modules')) {
|
if (id.includes('node_modules')) {
|
||||||
return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
||||||
|
@ -160,13 +160,11 @@ export default class Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sanitizedUrl === "/") {
|
if (sanitizedUrl === "/") {
|
||||||
return res.end(`
|
return res.end(JSON.stringify({
|
||||||
{
|
name: pkg.name,
|
||||||
"name": "${pkg.name}",
|
version: pkg.version,
|
||||||
"version": "${pkg.version}",
|
lb_version: defaults.version
|
||||||
"lb_version": "${defaults.version}"
|
}))
|
||||||
}
|
|
||||||
`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const namespace = `/${sanitizedUrl.split("/")[1]}`
|
const namespace = `/${sanitizedUrl.split("/")[1]}`
|
||||||
|
@ -2,4 +2,6 @@ export default class Track {
|
|||||||
static create = require("./methods/create").default
|
static create = require("./methods/create").default
|
||||||
static delete = require("./methods/delete").default
|
static delete = require("./methods/delete").default
|
||||||
static get = require("./methods/get").default
|
static get = require("./methods/get").default
|
||||||
|
static toggleFavourite = require("./methods/toggleFavourite").default
|
||||||
|
static isFavourite = require("./methods/isFavourite").default
|
||||||
}
|
}
|
@ -1,32 +1,66 @@
|
|||||||
import { Track } from "@db_models"
|
import { Track, TrackLike } from "@db_models"
|
||||||
|
|
||||||
export default async (track_id, { limit = 50, offset = 0 } = {}) => {
|
export default async (track_id, { user_id = null, onlyList = false } = {}) => {
|
||||||
if (!track_id) {
|
if (!track_id) {
|
||||||
throw new OperationError(400, "Missing track_id")
|
throw new OperationError(400, "Missing track_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMultiple = track_id.includes(",")
|
const isMultiple = Array.isArray(track_id) || track_id.includes(",")
|
||||||
|
|
||||||
if (isMultiple) {
|
if (isMultiple) {
|
||||||
const track_ids = track_id.split(",")
|
const track_ids = Array.isArray(track_id) ? track_id : track_id.split(",")
|
||||||
|
|
||||||
const tracks = await Track.find({ _id: { $in: track_ids } })
|
const tracks = await Track.find({
|
||||||
.limit(limit)
|
_id: { $in: track_ids }
|
||||||
.skip(offset)
|
}).lean()
|
||||||
|
|
||||||
|
if (user_id) {
|
||||||
|
const trackLikes = await TrackLike.find({
|
||||||
|
user_id: user_id,
|
||||||
|
track_id: { $in: track_ids }
|
||||||
|
})
|
||||||
|
|
||||||
|
// FIXME: this could be a performance issue when there are a lot of likes
|
||||||
|
// Array.find may not be a good idea
|
||||||
|
for (const trackLike of trackLikes) {
|
||||||
|
const track = tracks.find(track => track._id.toString() === trackLike.track_id.toString())
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
track.liked_at = trackLike.created_at
|
||||||
|
track.liked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyList) {
|
||||||
|
return tracks
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
total_count: await Track.countDocuments({ _id: { $in: track_ids } }),
|
total_count: await Track.countDocuments({ _id: { $in: track_ids } }),
|
||||||
list: tracks.map(track => track.toObject()),
|
list: tracks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const track = await Track.findOne({
|
const track = await Track.findOne({
|
||||||
_id: track_id
|
_id: track_id
|
||||||
})
|
}).lean()
|
||||||
|
|
||||||
if (!track) {
|
if (!track) {
|
||||||
throw new OperationError(404, "Track not found")
|
throw new OperationError(404, "Track not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user_id) {
|
||||||
|
const trackLike = await TrackLike.findOne({
|
||||||
|
user_id: user_id,
|
||||||
|
track_id: track_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (trackLike) {
|
||||||
|
track.liked_at = trackLike.created_at
|
||||||
|
track.liked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return track
|
return track
|
||||||
}
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Track, TrackLike } from "@db_models"
|
||||||
|
|
||||||
|
export default async (user_id, track_id, to) => {
|
||||||
|
if (!user_id) {
|
||||||
|
throw new OperationError(400, "Missing user_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_id) {
|
||||||
|
throw new OperationError(400, "Missing track_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
const track = await Track.findById(track_id)
|
||||||
|
|
||||||
|
if (!track) {
|
||||||
|
throw new OperationError(404, "Track not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
let trackLike = await TrackLike.findOne({
|
||||||
|
user_id: user_id,
|
||||||
|
track_id: track_id,
|
||||||
|
}).catch(() => null)
|
||||||
|
|
||||||
|
return {
|
||||||
|
liked: !!trackLike
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
import { Track, TrackLike } from "@db_models"
|
||||||
|
|
||||||
|
export default async (user_id, track_id, to) => {
|
||||||
|
if (!user_id) {
|
||||||
|
throw new OperationError(400, "Missing user_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_id) {
|
||||||
|
throw new OperationError(400, "Missing track_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
const track = await Track.findById(track_id)
|
||||||
|
|
||||||
|
if (!track) {
|
||||||
|
throw new OperationError(404, "Track not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
let trackLike = await TrackLike.findOne({
|
||||||
|
user_id: user_id,
|
||||||
|
track_id: track_id,
|
||||||
|
}).catch(() => null)
|
||||||
|
|
||||||
|
if (typeof to === "undefined") {
|
||||||
|
to = !!!trackLike
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
if (!trackLike) {
|
||||||
|
trackLike = new TrackLike({
|
||||||
|
user_id: user_id,
|
||||||
|
track_id: track_id,
|
||||||
|
created_at: Date.now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
await trackLike.save()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (trackLike) {
|
||||||
|
await TrackLike.deleteOne({
|
||||||
|
user_id: user_id,
|
||||||
|
track_id: track_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
trackLike = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(global.websocket.find)
|
||||||
|
|
||||||
|
const targetSocket = await global.websocket.find.socketByUserId(user_id)
|
||||||
|
|
||||||
|
if (targetSocket) {
|
||||||
|
await targetSocket.emit("music:track:toggle:like", {
|
||||||
|
track_id: track_id,
|
||||||
|
action: trackLike ? "liked" : "unliked"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
liked: trackLike ? true : false,
|
||||||
|
track_like_id: trackLike ? trackLike._id : null,
|
||||||
|
track_id: track._id.toString(),
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import LimitsClass from "@shared-classes/Limits"
|
|||||||
|
|
||||||
export default class API extends Server {
|
export default class API extends Server {
|
||||||
static refName = "music"
|
static refName = "music"
|
||||||
|
static enableWebsockets = true
|
||||||
static routesPath = `${__dirname}/routes`
|
static routesPath = `${__dirname}/routes`
|
||||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3003
|
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3003
|
||||||
|
|
||||||
|
@ -61,6 +61,10 @@ export default async (req) => {
|
|||||||
if (typeof trackLyrics.lrc === "object") {
|
if (typeof trackLyrics.lrc === "object") {
|
||||||
trackLyrics.translated_lang = translate_lang
|
trackLyrics.translated_lang = translate_lang
|
||||||
|
|
||||||
|
if (!trackLyrics.lrc[translate_lang]) {
|
||||||
|
translate_lang = "original"
|
||||||
|
}
|
||||||
|
|
||||||
if (trackLyrics.lrc[translate_lang]) {
|
if (trackLyrics.lrc[translate_lang]) {
|
||||||
trackLyrics.synced_lyrics = await remoteLcrToSyncedLyrics(trackLyrics.lrc[translate_lang])
|
trackLyrics.synced_lyrics = await remoteLcrToSyncedLyrics(trackLyrics.lrc[translate_lang])
|
||||||
}
|
}
|
||||||
|
73
packages/server/services/music/routes/music/my/folder/get.js
Normal file
73
packages/server/services/music/routes/music/my/folder/get.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
TrackLike,
|
||||||
|
} from "@db_models"
|
||||||
|
|
||||||
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
|
//
|
||||||
|
// A endpoint to fetch track & playlists & releases likes
|
||||||
|
//
|
||||||
|
export default {
|
||||||
|
middlewares: ["withAuthentication"],
|
||||||
|
fn: async (req) => {
|
||||||
|
const user_id = req.auth.session.user_id
|
||||||
|
const { limit, offset } = req.query
|
||||||
|
|
||||||
|
const [
|
||||||
|
totalTrackLikes,
|
||||||
|
totalReleasesLikes,
|
||||||
|
totalPlaylistsLikes,
|
||||||
|
] = await Promise.all([
|
||||||
|
TrackLike.countDocuments({ user_id }),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
])
|
||||||
|
|
||||||
|
let [
|
||||||
|
trackLikes,
|
||||||
|
releasesLikes,
|
||||||
|
playlistsLikes
|
||||||
|
] = await Promise.all([
|
||||||
|
TrackLike.find({
|
||||||
|
user_id
|
||||||
|
})
|
||||||
|
.limit(limit)
|
||||||
|
.skip(offset),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
])
|
||||||
|
|
||||||
|
let [
|
||||||
|
Tracks,
|
||||||
|
Releases,
|
||||||
|
Playlists,
|
||||||
|
] = await Promise.all([
|
||||||
|
TrackClass.get(trackLikes.map(trackLike => trackLike.track_id), {
|
||||||
|
user_id,
|
||||||
|
onlyList: true,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
])
|
||||||
|
|
||||||
|
Tracks = Tracks.sort((a, b) => b.liked_at - a.liked_at)
|
||||||
|
// Releases = Releases.sort((a, b) => b.liked_at - a.liked_at)
|
||||||
|
// Playlists = Playlists.sort((a, b) => b.liked_at - a.liked_at)
|
||||||
|
|
||||||
|
return {
|
||||||
|
tracks: {
|
||||||
|
list: Tracks,
|
||||||
|
total_items: totalTrackLikes,
|
||||||
|
},
|
||||||
|
releases: {
|
||||||
|
list: Releases,
|
||||||
|
total_items: totalReleasesLikes,
|
||||||
|
},
|
||||||
|
playlists: {
|
||||||
|
list: Playlists,
|
||||||
|
total_items: totalPlaylistsLikes,
|
||||||
|
},
|
||||||
|
total_length: totalTrackLikes + totalReleasesLikes + totalPlaylistsLikes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,34 @@
|
|||||||
import { MusicRelease, Track } from "@db_models"
|
import { MusicRelease, Track } from "@db_models"
|
||||||
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
export default async (req) => {
|
export default {
|
||||||
const { release_id } = req.params
|
middlewares: ["withOptionalAuthentication"],
|
||||||
const { limit = 50, offset = 0 } = req.query
|
fn: async (req) => {
|
||||||
|
const { release_id } = req.params
|
||||||
|
const { limit = 50, offset = 0 } = req.query
|
||||||
|
|
||||||
let release = await MusicRelease.findOne({
|
let release = await MusicRelease.findOne({
|
||||||
_id: release_id
|
_id: release_id
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!release) {
|
if (!release) {
|
||||||
throw new OperationError(404, "Release not found")
|
throw new OperationError(404, "Release not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
release = release.toObject()
|
||||||
|
|
||||||
|
const totalTracks = await Track.countDocuments({
|
||||||
|
_id: release.list
|
||||||
|
})
|
||||||
|
|
||||||
|
const tracks = await TrackClass.get(release.list, {
|
||||||
|
user_id: req.auth?.session?.user_id,
|
||||||
|
onlyList: true
|
||||||
|
})
|
||||||
|
|
||||||
|
release.listLength = totalTracks
|
||||||
|
release.list = tracks
|
||||||
|
|
||||||
|
return release
|
||||||
}
|
}
|
||||||
|
|
||||||
release = release.toObject()
|
|
||||||
|
|
||||||
const totalTracks = await Track.countDocuments({
|
|
||||||
_id: release.list
|
|
||||||
})
|
|
||||||
const tracks = await Track.find({
|
|
||||||
_id: { $in: release.list }
|
|
||||||
})
|
|
||||||
.limit(limit)
|
|
||||||
.skip(offset)
|
|
||||||
|
|
||||||
release.listLength = totalTracks
|
|
||||||
release.list = tracks
|
|
||||||
|
|
||||||
return release
|
|
||||||
}
|
}
|
@ -1,10 +1,14 @@
|
|||||||
import TrackClass from "@classes/track"
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
middlewares: ["withOptionalAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { track_id } = req.params
|
const { track_id } = req.params
|
||||||
|
const user_id = req.auth?.session?.user_id
|
||||||
|
|
||||||
const track = await TrackClass.get(track_id)
|
const track = await TrackClass.get(track_id, {
|
||||||
|
user_id
|
||||||
|
})
|
||||||
|
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
middlewares: ["withAuthentication"],
|
||||||
|
fn: async (req) => {
|
||||||
|
const { track_id } = req.params
|
||||||
|
const { to } = req.body
|
||||||
|
|
||||||
|
const track = await TrackClass.toggleFavourite(
|
||||||
|
req.auth.session.user_id,
|
||||||
|
track_id,
|
||||||
|
to,
|
||||||
|
)
|
||||||
|
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
middlewares: ["withAuthentication"],
|
||||||
|
fn: async (req) => {
|
||||||
|
const { track_id } = req.params
|
||||||
|
|
||||||
|
const likeStatus = await TrackClass.isFavourite(
|
||||||
|
req.auth.session.user_id,
|
||||||
|
track_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return likeStatus
|
||||||
|
}
|
||||||
|
}
|
@ -126,6 +126,8 @@ export default async (payload = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post.share_url = `${process.env.APP_URL}/post/${post._id}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...post,
|
...post,
|
||||||
user,
|
user,
|
||||||
|
57
packages/server/services/posts/routes/posts/trendings/get.js
Normal file
57
packages/server/services/posts/routes/posts/trendings/get.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Post } from "@db_models"
|
||||||
|
import { DateTime } from "luxon"
|
||||||
|
|
||||||
|
const maxDaysOld = 30
|
||||||
|
|
||||||
|
export default async (req) => {
|
||||||
|
// fetch all posts that contain in message an #, with a maximun of 5 diferent hashtags
|
||||||
|
let posts = await Post.find({
|
||||||
|
message: {
|
||||||
|
$regex: /#/gi
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
$gte: DateTime.local().minus({ days: maxDaysOld }).toISO()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.lean()
|
||||||
|
|
||||||
|
// get the hastag content
|
||||||
|
posts = posts.map((post) => {
|
||||||
|
post.hashtags = post.message.match(/#[a-zA-Z0-9_]+/gi)
|
||||||
|
|
||||||
|
post.hashtags = post.hashtags.map((hashtag) => {
|
||||||
|
return hashtag.substring(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
return post
|
||||||
|
})
|
||||||
|
|
||||||
|
// build trendings
|
||||||
|
let trendings = posts.reduce((acc, post) => {
|
||||||
|
post.hashtags.forEach((hashtag) => {
|
||||||
|
if (acc.find((trending) => trending.hashtag === hashtag)) {
|
||||||
|
acc = acc.map((trending) => {
|
||||||
|
if (trending.hashtag === hashtag) {
|
||||||
|
trending.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return trending
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
acc.push({
|
||||||
|
hashtag,
|
||||||
|
count: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// sort by count
|
||||||
|
trendings = trendings.sort((a, b) => {
|
||||||
|
return b.count - a.count
|
||||||
|
})
|
||||||
|
|
||||||
|
return trendings
|
||||||
|
}
|
@ -67,18 +67,18 @@ async function linkInternalSubmodules(packages) {
|
|||||||
const appPath = path.resolve(rootPath, pkgjson._web_app_path)
|
const appPath = path.resolve(rootPath, pkgjson._web_app_path)
|
||||||
|
|
||||||
const comtyjsPath = path.resolve(rootPath, "comty.js")
|
const comtyjsPath = path.resolve(rootPath, "comty.js")
|
||||||
const evitePath = path.resolve(rootPath, "evite")
|
const vesselPath = path.resolve(rootPath, "vessel")
|
||||||
const linebridePath = path.resolve(rootPath, "linebridge")
|
const linebridePath = path.resolve(rootPath, "linebridge")
|
||||||
|
|
||||||
//* EVITE LINKING
|
//* APP RUNTIME LINKING
|
||||||
console.log(`Linking Evite to app...`)
|
console.log(`Linking Vessel to app...`)
|
||||||
|
|
||||||
await child_process.execSync("yarn link", {
|
await child_process.execSync("yarn link", {
|
||||||
cwd: evitePath,
|
cwd: vesselPath,
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
})
|
})
|
||||||
|
|
||||||
await child_process.execSync(`yarn link "evite"`, {
|
await child_process.execSync(`yarn link "vessel"`, {
|
||||||
cwd: appPath,
|
cwd: appPath,
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user