mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14:16 +00:00
delete unused pages
This commit is contained in:
parent
b9ce363ef3
commit
c4011f0674
@ -1,35 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
|
|
||||||
import * as antd from "antd"
|
|
||||||
|
|
||||||
import SyncModel from "@models/sync"
|
|
||||||
|
|
||||||
export default (props) => {
|
|
||||||
const [error, setError] = React.useState(null)
|
|
||||||
|
|
||||||
const makeSync = async () => {
|
|
||||||
const result = await SyncModel.spotifyCore.syncAuthCode(window.location.search.split("=")[1]).catch((err) => {
|
|
||||||
setError(err.message)
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
window.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
makeSync()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <antd.Result
|
|
||||||
status="error"
|
|
||||||
title="Error while syncing your Spotify account"
|
|
||||||
subTitle={error}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
return <h3>Please wait meanwhile we are syncing your Spotify account</h3>
|
|
||||||
}
|
|
@ -1,178 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import * as antd from "antd"
|
|
||||||
|
|
||||||
import { Icons } from "@components/Icons"
|
|
||||||
import UploadButton from "@components/UploadButton"
|
|
||||||
|
|
||||||
export default (props) => {
|
|
||||||
const [releaseName, setReleaseName] = React.useState(props.release.title)
|
|
||||||
const [releaseDescription, setReleaseDescription] = React.useState(props.release.description)
|
|
||||||
const [releaseThumbnail, setReleaseThumbnail] = React.useState(props.release.cover ?? props.release.thumbnail)
|
|
||||||
const [releaseVisibility, setReleaseVisibility] = React.useState(props.release.visibility)
|
|
||||||
const [releaseType, setReleaseType] = React.useState(props.release.type)
|
|
||||||
|
|
||||||
const handleReleaseTypeChange = (value) => {
|
|
||||||
setReleaseType(value)
|
|
||||||
|
|
||||||
props.onValueChange("type", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTitleOnChange = (event) => {
|
|
||||||
setReleaseName(event.target.value)
|
|
||||||
|
|
||||||
props.onValueChange("title", event.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDescriptionOnChange = (event) => {
|
|
||||||
setReleaseDescription(event.target.value)
|
|
||||||
|
|
||||||
props.onValueChange("description", event.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCoverChange = (file) => {
|
|
||||||
setReleaseThumbnail(file.url)
|
|
||||||
|
|
||||||
props.onValueChange("cover", file.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRemoveCover = () => {
|
|
||||||
setReleaseThumbnail(null)
|
|
||||||
|
|
||||||
props.onValueChange("cover", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleVisibilityChange = (value) => {
|
|
||||||
setReleaseVisibility(value)
|
|
||||||
|
|
||||||
props.onValueChange("public", value === "public")
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="playlistCreator_layout_row">
|
|
||||||
<div
|
|
||||||
className="playlistCreator_layout_column"
|
|
||||||
>
|
|
||||||
<div className="field">
|
|
||||||
<div className="field_header">
|
|
||||||
<Icons.MdOutlineMusicNote />
|
|
||||||
<span>Title</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Input
|
|
||||||
className="inputText"
|
|
||||||
placeholder="Publish Title"
|
|
||||||
size="large"
|
|
||||||
bordered={false}
|
|
||||||
value={releaseName}
|
|
||||||
onChange={handleTitleOnChange}
|
|
||||||
maxLength={120}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="field">
|
|
||||||
<div className="field_header">
|
|
||||||
<Icons.MdOutlineDescription />
|
|
||||||
<span>Description</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Input.TextArea
|
|
||||||
className="inputText"
|
|
||||||
placeholder="Description (Support Markdown)"
|
|
||||||
bordered={false}
|
|
||||||
value={releaseDescription}
|
|
||||||
onChange={handleDescriptionOnChange}
|
|
||||||
maxLength={2500}
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Divider />
|
|
||||||
|
|
||||||
<div className="field">
|
|
||||||
<div className="field_header">
|
|
||||||
<Icons.IoMdRecording />
|
|
||||||
<span>Type</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Select
|
|
||||||
value={releaseType}
|
|
||||||
onChange={handleReleaseTypeChange}
|
|
||||||
defaultValue="album"
|
|
||||||
>
|
|
||||||
<antd.Select.Option value="album">Album</antd.Select.Option>
|
|
||||||
<antd.Select.Option value="ep">EP</antd.Select.Option>
|
|
||||||
<antd.Select.Option value="single">Single</antd.Select.Option>
|
|
||||||
</antd.Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="field">
|
|
||||||
<div className="field_header">
|
|
||||||
<Icons.FiEye />
|
|
||||||
<span>Visibility</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Select
|
|
||||||
value={releaseVisibility}
|
|
||||||
onChange={handleVisibilityChange}
|
|
||||||
defaultValue={props.release.public ? "public" : "private"}
|
|
||||||
>
|
|
||||||
<antd.Select.Option value="public">Public</antd.Select.Option>
|
|
||||||
<antd.Select.Option value="private">Private</antd.Select.Option>
|
|
||||||
</antd.Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="playlistCreator_layout_column"
|
|
||||||
style={{
|
|
||||||
width: "50%",
|
|
||||||
maxWidth: "300px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="field">
|
|
||||||
<div className="field_header">
|
|
||||||
<Icons.MdImage />
|
|
||||||
<span>Cover</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="coverPreview">
|
|
||||||
<div className="coverPreview_preview">
|
|
||||||
<img src={releaseThumbnail ?? "/assets/no_song.png"} alt="Thumbnail" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="coverPreview_actions">
|
|
||||||
<UploadButton
|
|
||||||
onUploadDone={handleCoverChange}
|
|
||||||
multiple={false}
|
|
||||||
accept="image/*"
|
|
||||||
>
|
|
||||||
Upload cover
|
|
||||||
</UploadButton>
|
|
||||||
|
|
||||||
<antd.Button
|
|
||||||
onClick={handleRemoveCover}
|
|
||||||
disabled={!releaseThumbnail}
|
|
||||||
icon={<Icons.MdClose />}
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
Remove Cover
|
|
||||||
</antd.Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Divider />
|
|
||||||
|
|
||||||
<div className="field">
|
|
||||||
{
|
|
||||||
props.release._id && <antd.Button
|
|
||||||
onClick={props.onDeletePlaylist}
|
|
||||||
icon={<Icons.MdDelete />}
|
|
||||||
danger
|
|
||||||
>
|
|
||||||
Delete Playlist
|
|
||||||
</antd.Button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
@ -1,423 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import * as antd from "antd"
|
|
||||||
import classnames from "classnames"
|
|
||||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"
|
|
||||||
|
|
||||||
import UploadButton from "@components/UploadButton"
|
|
||||||
import { Icons } from "@components/Icons"
|
|
||||||
|
|
||||||
import MusicModel from "@models/music"
|
|
||||||
|
|
||||||
import "./index.less"
|
|
||||||
|
|
||||||
const UploadHint = (props) => {
|
|
||||||
return <div className="uploadHint">
|
|
||||||
<Icons.MdPlaylistAdd />
|
|
||||||
<p>Upload your tracks</p>
|
|
||||||
<p>Drag and drop your tracks here or click this box to start uploading files.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const FileItemEditor = (props) => {
|
|
||||||
const [track, setTrack] = React.useState(props.track ?? {})
|
|
||||||
|
|
||||||
const handleChange = (key, value) => {
|
|
||||||
setTrack((oldData) => {
|
|
||||||
return {
|
|
||||||
...oldData,
|
|
||||||
[key]: value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onRefreshCache = () => {
|
|
||||||
props.onRefreshCache(track)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
if (typeof props.close === "function") {
|
|
||||||
props.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSave = async () => {
|
|
||||||
await props.onSave(track)
|
|
||||||
|
|
||||||
if (typeof props.close === "function") {
|
|
||||||
props.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="fileItemEditor">
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.MdImage />
|
|
||||||
<span>Thumbnail</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field_thumnail">
|
|
||||||
<img src={track.cover ?? track.thumbnail} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_actions">
|
|
||||||
<UploadButton
|
|
||||||
accept="image/*"
|
|
||||||
onUploadDone={(file) => handleChange("cover", file.url)}
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
(track.cover ?? track.thumbnail) && <antd.Button
|
|
||||||
icon={<Icons.MdClose />}
|
|
||||||
type="text"
|
|
||||||
onClick={() => handleChange("cover", null)}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</antd.Button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.MdOutlineMusicNote />
|
|
||||||
<span>Title</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Input
|
|
||||||
value={track.title}
|
|
||||||
placeholder="Track title"
|
|
||||||
onChange={(e) => handleChange("title", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.FiUser />
|
|
||||||
<span>Artist</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Input
|
|
||||||
value={track.artist}
|
|
||||||
placeholder="Artist"
|
|
||||||
onChange={(e) => handleChange("artist", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.MdAlbum />
|
|
||||||
<span>Album</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Input
|
|
||||||
value={track.album}
|
|
||||||
placeholder="Album"
|
|
||||||
onChange={(e) => handleChange("album", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.MdExplicit />
|
|
||||||
<span>Explicit</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Switch
|
|
||||||
checked={track.explicit}
|
|
||||||
onChange={(value) => handleChange("explicit", value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.MdTimeline />
|
|
||||||
<span>Timestamps</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Button
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</antd.Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Divider
|
|
||||||
style={{
|
|
||||||
margin: "5px 0",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.MdLyrics />
|
|
||||||
<span>Enable lyrics</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Switch
|
|
||||||
checked={track.lyricsEnabled}
|
|
||||||
onChange={(value) => handleChange("lyricsEnabled", value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.MdTextFormat />
|
|
||||||
<span>Upload LRC</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Button
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
Upload
|
|
||||||
</antd.Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_field">
|
|
||||||
<div className="fileItemEditor_field_header">
|
|
||||||
<Icons.FiTag />
|
|
||||||
<span>Spotify ID</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<antd.Input
|
|
||||||
value={track.spotifyId}
|
|
||||||
placeholder="ID"
|
|
||||||
onChange={(e) => handleChange("spotifyId", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileItemEditor_actions">
|
|
||||||
{
|
|
||||||
track._id && <antd.Button
|
|
||||||
type="text"
|
|
||||||
icon={<Icons.MdRefresh />}
|
|
||||||
onClick={onRefreshCache}
|
|
||||||
>
|
|
||||||
Refresh Cache
|
|
||||||
</antd.Button>
|
|
||||||
}
|
|
||||||
<antd.Button
|
|
||||||
type="text"
|
|
||||||
icon={<Icons.MdClose />}
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</antd.Button>
|
|
||||||
|
|
||||||
<antd.Button
|
|
||||||
type="primary"
|
|
||||||
icon={<Icons.MdCheck />}
|
|
||||||
onClick={onSave}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</antd.Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const FileListItem = (props) => {
|
|
||||||
const isUploading = props.track.status === "uploading"
|
|
||||||
|
|
||||||
return <Draggable key={props.track.uid} draggableId={props.track.uid} index={props.index}>
|
|
||||||
{(provided, snapshot) => {
|
|
||||||
return <div
|
|
||||||
className={classnames(
|
|
||||||
"fileListItem",
|
|
||||||
{
|
|
||||||
["uploading"]: isUploading,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
ref={provided.innerRef}
|
|
||||||
{...provided.draggableProps}
|
|
||||||
>
|
|
||||||
|
|
||||||
{
|
|
||||||
isUploading &&
|
|
||||||
<Icons.LoadingOutlined
|
|
||||||
spin
|
|
||||||
className="fileListItem_loadingIcon"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={classnames(
|
|
||||||
"fileListItem_progress",
|
|
||||||
{
|
|
||||||
["hidden"]: !isUploading,
|
|
||||||
}
|
|
||||||
)}>
|
|
||||||
<antd.Progress
|
|
||||||
percent={props.track.progress ?? 0}
|
|
||||||
status={props.track.status === "error" ? "exception" : (props.track.progress === 100 ? "success" : "active")}
|
|
||||||
showInfo={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileListItem_cover">
|
|
||||||
<img
|
|
||||||
src={props.track.cover ?? props.track?.thumbnail}
|
|
||||||
alt="Track cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileListItem_details">
|
|
||||||
<div className="fileListItem_namings">
|
|
||||||
<h4>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
marginRight: "0.6rem",
|
|
||||||
fontSize: "0.7rem"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.index + 1} -
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{
|
|
||||||
props.track?.title ?? "Unknown title"
|
|
||||||
}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "0.6rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{
|
|
||||||
props.track?.artist ?? "Unknown artist"
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span>-</span>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{
|
|
||||||
props.track?.album ?? "Unknown album"
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fileListItem_actions">
|
|
||||||
<antd.Button
|
|
||||||
type="primary"
|
|
||||||
icon={<Icons.MdEdit />}
|
|
||||||
onClick={props.onClickEdit}
|
|
||||||
disabled={isUploading}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<antd.Popconfirm
|
|
||||||
title="Delete this track?"
|
|
||||||
description="Are you sure to delete this track?"
|
|
||||||
onConfirm={props.onClickRemove}
|
|
||||||
okText="Yes"
|
|
||||||
cancelText="No"
|
|
||||||
>
|
|
||||||
<antd.Button
|
|
||||||
icon={<Icons.MdDelete />}
|
|
||||||
/>
|
|
||||||
</antd.Popconfirm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
className="fileListItem_dragHandle"
|
|
||||||
>
|
|
||||||
<Icons.MdDragIndicator />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}}
|
|
||||||
</Draggable>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (props) => {
|
|
||||||
const onClickEditTrack = (track) => {
|
|
||||||
app.layout.drawer.open("track_editor", FileItemEditor, {
|
|
||||||
type: "drawer",
|
|
||||||
props: {
|
|
||||||
width: "30vw",
|
|
||||||
minWidth: "600px",
|
|
||||||
},
|
|
||||||
componentProps: {
|
|
||||||
track,
|
|
||||||
onSave: (newTrackData) => {
|
|
||||||
console.log("Saving track", newTrackData)
|
|
||||||
|
|
||||||
props.handleTrackInfoChange(newTrackData.uid, newTrackData)
|
|
||||||
},
|
|
||||||
onRefreshCache: () => {
|
|
||||||
console.log("Refreshing cache for track", track.uid)
|
|
||||||
|
|
||||||
MusicModel.refreshTrackCache(track._id)
|
|
||||||
.catch(() => {
|
|
||||||
app.message.error("Failed to refresh cache for track")
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
app.message.success("Successfully refreshed cache for track")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="tracksUploads">
|
|
||||||
<p>
|
|
||||||
Uploading files that are not permitted by our <a onClick={() => app.location.push("/terms")}>Terms of Service</a> may result in your account being suspended.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="uploadBox">
|
|
||||||
<antd.Upload
|
|
||||||
className="uploader"
|
|
||||||
customRequest={props.handleUploadTrack}
|
|
||||||
onChange={props.onTrackUploaderChange}
|
|
||||||
accept="audio/*"
|
|
||||||
multiple
|
|
||||||
showUploadList={false}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
props.fileList.length === 0 ?
|
|
||||||
<UploadHint /> : <antd.Button
|
|
||||||
className="uploadMoreButton"
|
|
||||||
icon={<Icons.FiPlus />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</antd.Upload>
|
|
||||||
|
|
||||||
<div className="fileList_wrapper">
|
|
||||||
<DragDropContext onDragEnd={props.handleTrackDragEnd}>
|
|
||||||
<Droppable droppableId="droppable">
|
|
||||||
{(provided, snapshot) => (
|
|
||||||
<div
|
|
||||||
{...provided.droppableProps}
|
|
||||||
ref={provided.innerRef}
|
|
||||||
className="fileList"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
props.trackList.map((track, index) => {
|
|
||||||
return <FileListItem
|
|
||||||
index={index}
|
|
||||||
track={track}
|
|
||||||
onClickChangeCover={() => {
|
|
||||||
return props.handleTrackCoverChange(track.uid)
|
|
||||||
}}
|
|
||||||
onTitleChange={(event) => {
|
|
||||||
return props.handleTrackInfoChange(track.uid, "title", event.target.value)
|
|
||||||
}}
|
|
||||||
onClickRemove={() => {
|
|
||||||
return props.handleTrackRemove(track.uid)
|
|
||||||
}}
|
|
||||||
onClickEdit={() => {
|
|
||||||
return onClickEditTrack(track)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{provided.placeholder}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Droppable>
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
@ -1,387 +0,0 @@
|
|||||||
.tracksUploads {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.uploadBox {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
//height: 100%;
|
|
||||||
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
overflow-y: hidden;
|
|
||||||
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
.ant-upload {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.uploadMoreButton {
|
|
||||||
width: 100%;
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
outline: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
height: 70px;
|
|
||||||
background-color: var(--background-color-accent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-upload-wrapper {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileList_wrapper {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
// add a blur effect on top and bottom to decorate overflown content
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
z-index: 50;
|
|
||||||
content: "";
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
background: linear-gradient(0deg,
|
|
||||||
rgba(255, 255, 255, 0) 0%,
|
|
||||||
var(--background-color-primary) 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
background: linear-gradient(180deg,
|
|
||||||
rgba(255, 255, 255, 0) 0%,
|
|
||||||
var(--background-color-primary) 40%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileList {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
max-height: 60vh;
|
|
||||||
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
padding: 15px 0;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.fileListItem {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
z-index: 49;
|
|
||||||
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
background-color: var(--background-color-accent);
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
&.uploading {
|
|
||||||
.fileListItem_cover {
|
|
||||||
img {
|
|
||||||
filter: blur(3px);
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileListItem_progress {
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.ant-progress-line {
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.hidden {
|
|
||||||
animation: hide 0.2s ease-in-out forwards;
|
|
||||||
animation-delay: 2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileListItem_loadingIcon {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn {
|
|
||||||
svg {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileListItem_cover {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
|
|
||||||
object-fit: cover;
|
|
||||||
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileListItem_details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
margin-left: 20px;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.fileListItem_namings {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileListItem_artist {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileListItem_actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.ant-btn {
|
|
||||||
svg {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
margin-right: 10px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileListItem_dragHandle {
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadHint {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-self: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
font-size: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileItemEditor {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
.fileItemEditor_actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
align-self: center;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileItemEditor_field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.fileItemEditor_field_header {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
//margin-bottom: 10px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileItemEditor_field_actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileItemEditor_field_thumnail {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 15vw;
|
|
||||||
height: 15vw;
|
|
||||||
|
|
||||||
max-width: 330px;
|
|
||||||
max-height: 330px;
|
|
||||||
|
|
||||||
object-fit: cover;
|
|
||||||
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes hide {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
99% {
|
|
||||||
opacity: 0;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
width: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,646 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import * as antd from "antd"
|
|
||||||
import jsmediatags from "jsmediatags/dist/jsmediatags.min.js"
|
|
||||||
|
|
||||||
import MusicModel from "@models/music"
|
|
||||||
|
|
||||||
import BasicInformation from "./components/BasicInformation"
|
|
||||||
import TracksUploads from "./components/TracksUploads"
|
|
||||||
|
|
||||||
import "./index.less"
|
|
||||||
|
|
||||||
const allowedTrackFieldChanges = [
|
|
||||||
"title",
|
|
||||||
"artist",
|
|
||||||
"cover",
|
|
||||||
"thumbnail",
|
|
||||||
"album",
|
|
||||||
"year",
|
|
||||||
"genre",
|
|
||||||
"comment",
|
|
||||||
"explicit",
|
|
||||||
"lyricsEnabled",
|
|
||||||
"spotifyId",
|
|
||||||
"public",
|
|
||||||
]
|
|
||||||
|
|
||||||
function createDefaultTrackData({
|
|
||||||
uid,
|
|
||||||
status = "uploading",
|
|
||||||
title,
|
|
||||||
artist,
|
|
||||||
album,
|
|
||||||
source,
|
|
||||||
cover = "https://storage.ragestudio.net/comty-static-assets/default_song.png",
|
|
||||||
lyricsEnabled = false,
|
|
||||||
explicit = false,
|
|
||||||
spotifyId = null,
|
|
||||||
}) {
|
|
||||||
return {
|
|
||||||
uid: uid,
|
|
||||||
title: title,
|
|
||||||
artist: artist,
|
|
||||||
album: album,
|
|
||||||
source: source,
|
|
||||||
status: status,
|
|
||||||
cover: cover,
|
|
||||||
lyricsEnabled: lyricsEnabled,
|
|
||||||
explicit: explicit,
|
|
||||||
spotifyId: spotifyId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PlaylistPublisherSteps extends React.Component {
|
|
||||||
state = {
|
|
||||||
releaseData: {
|
|
||||||
type: "album",
|
|
||||||
public: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
fileList: [],
|
|
||||||
trackList: [],
|
|
||||||
pendingTracksUpload: [],
|
|
||||||
|
|
||||||
loading: true,
|
|
||||||
submitting: false,
|
|
||||||
|
|
||||||
currentStep: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
_hacks = {
|
|
||||||
revertTrackOrders: () => {
|
|
||||||
this.setState({
|
|
||||||
trackList: this.state.trackList.reverse()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
enableLyricsForAllTracks: () => {
|
|
||||||
this.setState({
|
|
||||||
trackList: this.state.trackList.map((track) => {
|
|
||||||
track.lyricsEnabled = true
|
|
||||||
return track
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
disableLyricsForAllTracks: () => {
|
|
||||||
this.setState({
|
|
||||||
trackList: this.state.trackList.map((track) => {
|
|
||||||
track.lyricsEnabled = false
|
|
||||||
return track
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
orderTracksByFileName: () => {
|
|
||||||
let fileList = this.state.fileList
|
|
||||||
|
|
||||||
fileList = fileList.sort((a, b) => {
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
})
|
|
||||||
|
|
||||||
const trackList = fileList.map((file) => {
|
|
||||||
const track = this.state.trackList.find((track) => track.uid === file.uid)
|
|
||||||
|
|
||||||
return track
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(fileList, trackList)
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
fileList,
|
|
||||||
trackList
|
|
||||||
})
|
|
||||||
},
|
|
||||||
orderByArrayIndex: (order) => {
|
|
||||||
const trackList = this.state.trackList
|
|
||||||
|
|
||||||
let orderedTrackList = trackList.map((track, index) => {
|
|
||||||
// find in order
|
|
||||||
const orderIndex = order.findIndex((_track) => {
|
|
||||||
return _track.title === track.title && _track.artist === track.artist && _track.album === track.album
|
|
||||||
})
|
|
||||||
|
|
||||||
if (orderIndex === -1) {
|
|
||||||
track.order = index
|
|
||||||
} else {
|
|
||||||
track.order = orderIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
return track
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(orderedTrackList)
|
|
||||||
|
|
||||||
orderedTrackList = orderedTrackList.sort((a, b) => {
|
|
||||||
return a.order - b.order
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
trackList: orderedTrackList
|
|
||||||
})
|
|
||||||
},
|
|
||||||
removeMetadataFromAllTracks: () => {
|
|
||||||
this.setState({
|
|
||||||
trackList: this.state.trackList.map((track) => {
|
|
||||||
delete track.metadata
|
|
||||||
|
|
||||||
return track
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
updateReleaseData = (key, value) => {
|
|
||||||
this.setState({
|
|
||||||
releaseData: {
|
|
||||||
...this.state.releaseData,
|
|
||||||
[key]: value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTrackList = (trackList) => {
|
|
||||||
this.setState({
|
|
||||||
trackList
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
canSubmit = () => {
|
|
||||||
const { releaseData, trackList, pendingTracksUpload } = this.state
|
|
||||||
|
|
||||||
const hasValidTitle = releaseData.title && releaseData.title.length > 0
|
|
||||||
const hasTracks = trackList.length > 0
|
|
||||||
const hasPendingUploads = pendingTracksUpload.length > 0
|
|
||||||
const tracksHasValidData = trackList.every((track) => {
|
|
||||||
return track.title !== null && track.title?.length !== 0 && track.source !== null && track.source?.length !== 0
|
|
||||||
})
|
|
||||||
|
|
||||||
return hasValidTitle && hasTracks && !hasPendingUploads && tracksHasValidData
|
|
||||||
}
|
|
||||||
|
|
||||||
submit = async () => {
|
|
||||||
this.setState({
|
|
||||||
submitting: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const { releaseData: releaseData, trackList } = this.state
|
|
||||||
|
|
||||||
console.log(`Submitting playlist ${releaseData.title} with ${trackList.length} tracks`, releaseData, trackList)
|
|
||||||
|
|
||||||
const result = await MusicModel.putRelease({
|
|
||||||
...releaseData,
|
|
||||||
list: trackList,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
submitting: false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
app.message.success("Playlist published")
|
|
||||||
|
|
||||||
if (typeof this.props.onModification === "function") {
|
|
||||||
this.props.onModification()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.props.close === "function") {
|
|
||||||
this.props.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TRACK UPLOADS METHODS
|
|
||||||
analyzeTrackMetadata = async (file) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
jsmediatags.read(file, {
|
|
||||||
onSuccess: (data) => {
|
|
||||||
return resolve(data)
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
return reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFileProgress = (file, progress) => {
|
|
||||||
const trackList = this.state.trackList
|
|
||||||
|
|
||||||
const track = trackList.find((track) => track.uid === file.uid)
|
|
||||||
|
|
||||||
if (track) {
|
|
||||||
track.progress = progress
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
trackList
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUploadTrack = async (req) => {
|
|
||||||
const response = await app.cores.remoteStorage.uploadFile(req.file, {
|
|
||||||
onProgress: this.handleFileProgress,
|
|
||||||
service: "premium-cdn"
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
antd.message.error(error)
|
|
||||||
|
|
||||||
req.onError(error)
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
req.onSuccess(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTrackDragEnd = (result) => {
|
|
||||||
if (!result.destination) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackList = this.state.trackList
|
|
||||||
|
|
||||||
const [removed] = trackList.splice(result.source.index, 1)
|
|
||||||
|
|
||||||
trackList.splice(result.destination.index, 0, removed)
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
trackList,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTrackRemove = (uid) => {
|
|
||||||
this.setState({
|
|
||||||
fileList: this.state.fileList.filter((file) => file.uid !== uid),
|
|
||||||
trackList: this.state.trackList.filter((file) => file.uid !== uid),
|
|
||||||
pendingTracksUpload: this.state.pendingTracksUpload.filter((file_uid) => file_uid !== uid)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTrackInfoChange = (uid, key, value) => {
|
|
||||||
if (!uid) {
|
|
||||||
console.error(`Cannot update track withouth uid`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let trackList = this.state.trackList
|
|
||||||
|
|
||||||
const track = trackList.find((track) => track.uid === uid)
|
|
||||||
|
|
||||||
if (typeof key === "object") {
|
|
||||||
allowedTrackFieldChanges.forEach((_key) => {
|
|
||||||
if (typeof key[_key] !== "undefined") {
|
|
||||||
track[_key] = key[_key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (!allowedTrackFieldChanges.includes(key)) {
|
|
||||||
console.error(`Cannot update track with key ${key}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
track[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
trackList: trackList
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`New data for track ${uid}: `, this.state.trackList.find((track) => track.uid === uid))
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTrackCoverChange = async (uid, file) => {
|
|
||||||
if (!uid) {
|
|
||||||
console.error(`Cannot update track withouth uid`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// upload cover file
|
|
||||||
const result = await app.cores.remoteStorage.uploadFile(file, {
|
|
||||||
timeout: 2000
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Uploaded cover for track ${uid}: `, result)
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
this.handleTrackInfoChange(uid, "cover", result.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeletePlaylist = async () => {
|
|
||||||
if (!this.props.release_id) {
|
|
||||||
console.error(`Cannot delete release without id`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
antd.Modal.confirm({
|
|
||||||
title: "Are you sure you want to delete this release?",
|
|
||||||
content: "This action cannot be undone",
|
|
||||||
okText: "Delete",
|
|
||||||
okType: "danger",
|
|
||||||
cancelText: "Cancel",
|
|
||||||
onOk: async () => {
|
|
||||||
const result = await MusicModel.deleteRelease(this.props.release_id, {
|
|
||||||
remove_with_tracks: true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
app.message.success("Playlist deleted")
|
|
||||||
|
|
||||||
if (typeof this.props.onModification === "function") {
|
|
||||||
this.props.onModification()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.props.close === "function") {
|
|
||||||
this.props.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onTrackUploaderChange = async (change) => {
|
|
||||||
switch (change.file.status) {
|
|
||||||
case "uploading": {
|
|
||||||
const { pendingTracksUpload } = this.state
|
|
||||||
|
|
||||||
if (!pendingTracksUpload.includes(change.file.uid)) {
|
|
||||||
pendingTracksUpload.push(change.file.uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackMetadata = await this.analyzeTrackMetadata(change.file.originFileObj)
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(`Failed to analyze track metadata: `, error)
|
|
||||||
|
|
||||||
// return empty metadata
|
|
||||||
return {
|
|
||||||
tags: {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(trackMetadata)
|
|
||||||
|
|
||||||
if (trackMetadata.tags.picture) {
|
|
||||||
const data = trackMetadata.tags.picture.data
|
|
||||||
const format = trackMetadata.tags.picture.format
|
|
||||||
|
|
||||||
if (data && format) {
|
|
||||||
console.log(data, format)
|
|
||||||
|
|
||||||
const filenameExt = format.split("/")[1]
|
|
||||||
const filename = `cover.${filenameExt}`
|
|
||||||
|
|
||||||
const byteArray = new Uint8Array(data)
|
|
||||||
const blob = new Blob([byteArray], { type: data.type })
|
|
||||||
|
|
||||||
// create a file object
|
|
||||||
const file = new File([blob], filename, {
|
|
||||||
type: format,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(file)
|
|
||||||
|
|
||||||
this.handleTrackCoverChange(change.file.uid, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
pendingTracksUpload: pendingTracksUpload,
|
|
||||||
fileList: [...this.state.fileList, change.file],
|
|
||||||
trackList: [...this.state.trackList, createDefaultTrackData({
|
|
||||||
uid: change.file.uid,
|
|
||||||
title: trackMetadata.tags.title ?? change.file.name,
|
|
||||||
artist: trackMetadata.tags.artist ?? null,
|
|
||||||
album: trackMetadata.tags.album ?? null,
|
|
||||||
tags: trackMetadata.tags,
|
|
||||||
})]
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "done": {
|
|
||||||
// remove pending file
|
|
||||||
this.setState({
|
|
||||||
pendingTracksUpload: this.state.pendingTracksUpload.filter((uid) => uid !== change.file.uid)
|
|
||||||
})
|
|
||||||
|
|
||||||
// update file url in the track info
|
|
||||||
const track = this.state.trackList.find((file) => file.uid === change.file.uid)
|
|
||||||
|
|
||||||
if (track) {
|
|
||||||
track.source = change.file.response.url
|
|
||||||
track.status = "done"
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
trackList: this.state.trackList
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "error": {
|
|
||||||
// remove pending file
|
|
||||||
this.handleTrackRemove(change.file.uid)
|
|
||||||
|
|
||||||
// open a dialog to show the error and ask user to retry
|
|
||||||
antd.Modal.error({
|
|
||||||
title: "Upload failed",
|
|
||||||
content: "An error occurred while uploading the file. You want to retry?",
|
|
||||||
cancelText: "No",
|
|
||||||
okText: "Retry",
|
|
||||||
onOk: () => {
|
|
||||||
this.handleUploadTrack(change)
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
this.handleTrackRemove(change.file.uid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case "removed": {
|
|
||||||
this.handleTrackRemove(change.file.uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
steps = [
|
|
||||||
{
|
|
||||||
title: "Information",
|
|
||||||
crender: BasicInformation,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Tracks",
|
|
||||||
crender: TracksUploads,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
onChangeStep = (toStep) => {
|
|
||||||
// check if can change step
|
|
||||||
if (toStep > this.state.currentStep) {
|
|
||||||
if (!this.canNextStep()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
currentStep: toStep
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
nextStep = () => {
|
|
||||||
if (!this.canNextStep()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextStep = this.state.currentStep + 1
|
|
||||||
|
|
||||||
if (nextStep >= this.steps.length) {
|
|
||||||
return this.submit()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
currentStep: nextStep
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
previousStep = () => {
|
|
||||||
const previusStep = this.state.currentStep - 1
|
|
||||||
|
|
||||||
if (previusStep < 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
currentStep: previusStep
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
canNextStep = () => {
|
|
||||||
// check current step
|
|
||||||
switch (this.state.currentStep) {
|
|
||||||
case 0:
|
|
||||||
return typeof this.state.releaseData.title === "string" && this.state.releaseData.title.length > 0
|
|
||||||
case 1:
|
|
||||||
return this.canSubmit()
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window._hacks = this._hacks
|
|
||||||
|
|
||||||
if (this.props.release_id) {
|
|
||||||
this.loadReleaseData(this.props.release_id)
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
delete window._hacks
|
|
||||||
}
|
|
||||||
|
|
||||||
loadReleaseData = async (id) => {
|
|
||||||
console.log(`Loading release data for ${id}...`)
|
|
||||||
|
|
||||||
const releaseData = await MusicModel.getReleaseData(id).catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
antd.message.error(error)
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(releaseData)
|
|
||||||
|
|
||||||
if (releaseData) {
|
|
||||||
const trackList = releaseData.list.map((track) => {
|
|
||||||
return {
|
|
||||||
...track,
|
|
||||||
_id: track._id,
|
|
||||||
uid: track._id,
|
|
||||||
status: "done",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
releaseData: releaseData,
|
|
||||||
trackList: trackList,
|
|
||||||
fileList: trackList.map((track) => {
|
|
||||||
return {
|
|
||||||
uid: track.uid,
|
|
||||||
name: track.title,
|
|
||||||
status: "done",
|
|
||||||
url: track.source
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.loading) {
|
|
||||||
return <antd.Skeleton active />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="playlistCreator">
|
|
||||||
<antd.Steps
|
|
||||||
direction="horizontal"
|
|
||||||
current={this.state.currentStep}
|
|
||||||
onChange={this.onChangeStep}
|
|
||||||
items={this.steps}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="stepContent">
|
|
||||||
{
|
|
||||||
React.createElement(this.steps[this.state.currentStep].crender, {
|
|
||||||
release: this.state.releaseData,
|
|
||||||
|
|
||||||
trackList: this.state.trackList,
|
|
||||||
fileList: this.state.fileList,
|
|
||||||
|
|
||||||
onValueChange: (key, value) => {
|
|
||||||
this.updateReleaseData(key, value)
|
|
||||||
},
|
|
||||||
|
|
||||||
onDeletePlaylist: this.handleDeletePlaylist,
|
|
||||||
|
|
||||||
handleUploadTrack: this.handleUploadTrack,
|
|
||||||
handleTrackDragEnd: this.handleTrackDragEnd,
|
|
||||||
handleTrackRemove: this.handleTrackRemove,
|
|
||||||
handleTrackInfoChange: this.handleTrackInfoChange,
|
|
||||||
onTrackUploaderChange: this.onTrackUploaderChange,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="stepActions">
|
|
||||||
<antd.Button
|
|
||||||
onClick={this.previousStep}
|
|
||||||
disabled={this.state.currentStep === 0}
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</antd.Button>
|
|
||||||
|
|
||||||
<antd.Button
|
|
||||||
type="primary"
|
|
||||||
onClick={this.nextStep}
|
|
||||||
disabled={!this.canNextStep()}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
this.state.currentStep === this.steps.length - 1 ? "Finish" : "Next"
|
|
||||||
}
|
|
||||||
</antd.Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
.playlistCreator {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
//justify-content: center;
|
|
||||||
|
|
||||||
width: 50vw;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
min-width: 800px;
|
|
||||||
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
|
|
||||||
.stepContent {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stepActions {
|
|
||||||
position: sticky;
|
|
||||||
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
//justify-content: flex-end;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
background-color: var(--background-color-accent);
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playlistCreator_layout_row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
.playlistCreator_layout_column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
align-self: start;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
.field_header {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputText {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
outline: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.coverPreview {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.coverPreview_preview {
|
|
||||||
align-self: center;
|
|
||||||
|
|
||||||
width: 15vw;
|
|
||||||
height: 15vw;
|
|
||||||
|
|
||||||
max-width: 300px;
|
|
||||||
max-height: 300px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 15vw;
|
|
||||||
height: 15vw;
|
|
||||||
|
|
||||||
max-width: 300px;
|
|
||||||
max-height: 300px;
|
|
||||||
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.coverPreview_actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-steps-icon {
|
|
||||||
svg {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user