mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
Add change tracking and update to use "items" property
This commit is contained in:
parent
d738995054
commit
74021f38b6
@ -1,280 +1,332 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
|
|
||||||
import { Icons, createIconRender } from "@components/Icons"
|
import { Icons, createIconRender } from "@components/Icons"
|
||||||
|
|
||||||
import MusicModel from "@models/music"
|
import MusicModel from "@models/music"
|
||||||
|
import compareObjectsByProperties from "@utils/compareObjectsByProperties"
|
||||||
import useUrlQueryActiveKey from "@hooks/useUrlQueryActiveKey"
|
import useUrlQueryActiveKey from "@hooks/useUrlQueryActiveKey"
|
||||||
|
|
||||||
import TrackManifest from "@cores/player/classes/TrackManifest"
|
import TrackManifest from "@cores/player/classes/TrackManifest"
|
||||||
|
|
||||||
import { DefaultReleaseEditorState, ReleaseEditorStateContext } from "@contexts/MusicReleaseEditor"
|
import {
|
||||||
|
DefaultReleaseEditorState,
|
||||||
|
ReleaseEditorStateContext,
|
||||||
|
} from "@contexts/MusicReleaseEditor"
|
||||||
|
|
||||||
import Tabs from "./tabs"
|
import Tabs from "./tabs"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
const ReleaseEditor = (props) => {
|
const ReleaseEditor = (props) => {
|
||||||
const { release_id } = props
|
const { release_id } = props
|
||||||
|
|
||||||
const basicInfoRef = React.useRef()
|
const basicInfoRef = React.useRef()
|
||||||
|
|
||||||
const [submitting, setSubmitting] = React.useState(false)
|
const [submitting, setSubmitting] = React.useState(false)
|
||||||
const [loading, setLoading] = React.useState(true)
|
const [loading, setLoading] = React.useState(true)
|
||||||
const [submitError, setSubmitError] = React.useState(null)
|
const [submitError, setSubmitError] = React.useState(null)
|
||||||
|
|
||||||
const [loadError, setLoadError] = React.useState(null)
|
const [loadError, setLoadError] = React.useState(null)
|
||||||
const [globalState, setGlobalState] = React.useState(DefaultReleaseEditorState)
|
const [globalState, setGlobalState] = React.useState(
|
||||||
|
DefaultReleaseEditorState,
|
||||||
|
)
|
||||||
|
const [initialValues, setInitialValues] = React.useState({})
|
||||||
|
|
||||||
const [customPage, setCustomPage] = React.useState(null)
|
const [customPage, setCustomPage] = React.useState(null)
|
||||||
const [customPageActions, setCustomPageActions] = React.useState([])
|
const [customPageActions, setCustomPageActions] = React.useState([])
|
||||||
|
|
||||||
const [selectedTab, setSelectedTab] = useUrlQueryActiveKey({
|
const [selectedTab, setSelectedTab] = useUrlQueryActiveKey({
|
||||||
defaultKey: "info",
|
defaultKey: "info",
|
||||||
queryKey: "tab"
|
queryKey: "tab",
|
||||||
})
|
})
|
||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setLoadError(null)
|
setLoadError(null)
|
||||||
|
|
||||||
if (release_id !== "new") {
|
if (release_id !== "new") {
|
||||||
try {
|
try {
|
||||||
let releaseData = await MusicModel.getReleaseData(release_id)
|
let releaseData = await MusicModel.getReleaseData(release_id)
|
||||||
|
|
||||||
if (Array.isArray(releaseData.list)) {
|
if (Array.isArray(releaseData.items)) {
|
||||||
releaseData.list = releaseData.list.map((item) => {
|
releaseData.items = releaseData.items.map((item) => {
|
||||||
return new TrackManifest(item)
|
return new TrackManifest(item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setGlobalState({
|
setGlobalState({
|
||||||
...globalState,
|
...globalState,
|
||||||
...releaseData,
|
...releaseData,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
|
||||||
setLoadError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false)
|
setInitialValues(releaseData)
|
||||||
}
|
} catch (error) {
|
||||||
|
setLoadError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function renderCustomPage(page, actions) {
|
setLoading(false)
|
||||||
setCustomPage(page ?? null)
|
}
|
||||||
setCustomPageActions(actions ?? [])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
function hasChanges() {
|
||||||
setSubmitting(true)
|
const stagedChanges = {
|
||||||
setSubmitError(null)
|
title: globalState.title,
|
||||||
|
type: globalState.type,
|
||||||
|
public: globalState.public,
|
||||||
|
cover: globalState.cover,
|
||||||
|
items: globalState.items,
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
return !compareObjectsByProperties(
|
||||||
// first sumbit tracks
|
stagedChanges,
|
||||||
const tracks = await MusicModel.putTrack({
|
initialValues,
|
||||||
list: globalState.list,
|
Object.keys(stagedChanges),
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// then submit release
|
async function renderCustomPage(page, actions) {
|
||||||
const result = await MusicModel.putRelease({
|
setCustomPage(page ?? null)
|
||||||
_id: globalState._id,
|
setCustomPageActions(actions ?? [])
|
||||||
title: globalState.title,
|
}
|
||||||
description: globalState.description,
|
|
||||||
public: globalState.public,
|
|
||||||
cover: globalState.cover,
|
|
||||||
explicit: globalState.explicit,
|
|
||||||
type: globalState.type,
|
|
||||||
list: tracks.list.map((item) => item._id),
|
|
||||||
})
|
|
||||||
|
|
||||||
app.location.push(`/studio/music/${result._id}`)
|
async function handleSubmit() {
|
||||||
} catch (error) {
|
setSubmitting(true)
|
||||||
console.error(error)
|
setSubmitError(null)
|
||||||
app.message.error(error.message)
|
|
||||||
|
|
||||||
setSubmitError(error)
|
try {
|
||||||
setSubmitting(false)
|
console.log("Submitting Tracks")
|
||||||
|
|
||||||
return false
|
// first sumbit tracks
|
||||||
}
|
const tracks = await MusicModel.putTrack({
|
||||||
|
items: globalState.items,
|
||||||
|
})
|
||||||
|
|
||||||
setSubmitting(false)
|
console.log("Submitting release")
|
||||||
app.message.success("Release saved")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDelete() {
|
// then submit release
|
||||||
app.layout.modal.confirm({
|
const result = await MusicModel.putRelease({
|
||||||
headerText: "Are you sure you want to delete this release?",
|
_id: globalState._id,
|
||||||
descriptionText: "This action cannot be undone.",
|
title: globalState.title,
|
||||||
onConfirm: async () => {
|
description: globalState.description,
|
||||||
await MusicModel.deleteRelease(globalState._id)
|
public: globalState.public,
|
||||||
app.location.push(window.location.pathname.split("/").slice(0, -1).join("/"))
|
cover: globalState.cover,
|
||||||
},
|
explicit: globalState.explicit,
|
||||||
})
|
type: globalState.type,
|
||||||
}
|
items: tracks.items.map((item) => item._id),
|
||||||
|
})
|
||||||
|
|
||||||
async function canFinish() {
|
app.location.push(`/studio/music/${result._id}`)
|
||||||
return true
|
} catch (error) {
|
||||||
}
|
console.error(error)
|
||||||
|
app.message.error(error.message)
|
||||||
|
|
||||||
React.useEffect(() => {
|
setSubmitError(error)
|
||||||
initialize()
|
setSubmitting(false)
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (loadError) {
|
return false
|
||||||
return <antd.Result
|
}
|
||||||
status="warning"
|
|
||||||
title="Error"
|
|
||||||
subTitle={loadError.message}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
setSubmitting(false)
|
||||||
return <antd.Skeleton active />
|
app.message.success("Release saved")
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tab = Tabs.find(({ key }) => key === selectedTab)
|
async function handleDelete() {
|
||||||
|
app.layout.modal.confirm({
|
||||||
|
headerText: "Are you sure you want to delete this release?",
|
||||||
|
descriptionText: "This action cannot be undone.",
|
||||||
|
onConfirm: async () => {
|
||||||
|
await MusicModel.deleteRelease(globalState._id)
|
||||||
|
app.location.push(
|
||||||
|
window.location.pathname.split("/").slice(0, -1).join("/"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const CustomPageProps = {
|
function canFinish() {
|
||||||
close: () => {
|
return hasChanges()
|
||||||
renderCustomPage(null, null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ReleaseEditorStateContext.Provider
|
React.useEffect(() => {
|
||||||
value={{
|
initialize()
|
||||||
...globalState,
|
}, [])
|
||||||
setGlobalState,
|
|
||||||
renderCustomPage,
|
|
||||||
setCustomPageActions,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="music-studio-release-editor">
|
|
||||||
{
|
|
||||||
customPage && <div className="music-studio-release-editor-custom-page">
|
|
||||||
{
|
|
||||||
customPage.header && <div className="music-studio-release-editor-custom-page-header">
|
|
||||||
<div className="music-studio-release-editor-custom-page-header-title">
|
|
||||||
<antd.Button
|
|
||||||
icon={<Icons.IoIosArrowBack />}
|
|
||||||
onClick={() => renderCustomPage(null, null)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h2>{customPage.header}</h2>
|
if (loadError) {
|
||||||
</div>
|
return (
|
||||||
|
<antd.Result
|
||||||
|
status="warning"
|
||||||
|
title="Error"
|
||||||
|
subTitle={loadError.message}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{
|
if (loading) {
|
||||||
Array.isArray(customPageActions) && customPageActions.map((action, index) => {
|
return <antd.Skeleton active />
|
||||||
return <antd.Button
|
}
|
||||||
key={index}
|
|
||||||
type={action.type}
|
|
||||||
icon={createIconRender(action.icon)}
|
|
||||||
onClick={async () => {
|
|
||||||
if (typeof action.onClick === "function") {
|
|
||||||
await action.onClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.fireEvent) {
|
const Tab = Tabs.find(({ key }) => key === selectedTab)
|
||||||
app.eventBus.emit(action.fireEvent)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={action.disabled}
|
|
||||||
>
|
|
||||||
{action.label}
|
|
||||||
</antd.Button>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
const CustomPageProps = {
|
||||||
customPage.content && (React.isValidElement(customPage.content) ?
|
close: () => {
|
||||||
React.cloneElement(customPage.content, {
|
renderCustomPage(null, null)
|
||||||
...CustomPageProps,
|
},
|
||||||
...customPage.props
|
}
|
||||||
}) :
|
|
||||||
React.createElement(customPage.content, {
|
|
||||||
...CustomPageProps,
|
|
||||||
...customPage.props
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!customPage && <>
|
|
||||||
<div className="music-studio-release-editor-menu">
|
|
||||||
<antd.Menu
|
|
||||||
onClick={(e) => setSelectedTab(e.key)}
|
|
||||||
selectedKeys={[selectedTab]}
|
|
||||||
items={Tabs}
|
|
||||||
mode="vertical"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="music-studio-release-editor-menu-actions">
|
return (
|
||||||
<antd.Button
|
<ReleaseEditorStateContext.Provider
|
||||||
type="primary"
|
value={{
|
||||||
onClick={handleSubmit}
|
...globalState,
|
||||||
icon={release_id !== "new" ? <Icons.FiSave /> : <Icons.MdSend />}
|
setGlobalState,
|
||||||
disabled={submitting || loading || !canFinish()}
|
renderCustomPage,
|
||||||
loading={submitting}
|
setCustomPageActions,
|
||||||
>
|
}}
|
||||||
{release_id !== "new" ? "Save" : "Release"}
|
>
|
||||||
</antd.Button>
|
<div className="music-studio-release-editor">
|
||||||
|
{customPage && (
|
||||||
|
<div className="music-studio-release-editor-custom-page">
|
||||||
|
{customPage.header && (
|
||||||
|
<div className="music-studio-release-editor-custom-page-header">
|
||||||
|
<div className="music-studio-release-editor-custom-page-header-title">
|
||||||
|
<antd.Button
|
||||||
|
icon={<Icons.IoIosArrowBack />}
|
||||||
|
onClick={() =>
|
||||||
|
renderCustomPage(null, null)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{
|
<h2>{customPage.header}</h2>
|
||||||
release_id !== "new" ? <antd.Button
|
</div>
|
||||||
icon={<Icons.IoMdTrash />}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={handleDelete}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</antd.Button> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{Array.isArray(customPageActions) &&
|
||||||
release_id !== "new" ? <antd.Button
|
customPageActions.map((action, index) => {
|
||||||
icon={<Icons.MdLink />}
|
return (
|
||||||
onClick={() => app.location.push(`/music/release/${globalState._id}`)}
|
<antd.Button
|
||||||
>
|
key={index}
|
||||||
Go to release
|
type={action.type}
|
||||||
</antd.Button> : null
|
icon={createIconRender(
|
||||||
}
|
action.icon,
|
||||||
</div>
|
)}
|
||||||
</div>
|
onClick={async () => {
|
||||||
|
if (
|
||||||
|
typeof action.onClick ===
|
||||||
|
"function"
|
||||||
|
) {
|
||||||
|
await action.onClick()
|
||||||
|
}
|
||||||
|
|
||||||
<div className="music-studio-release-editor-content">
|
if (action.fireEvent) {
|
||||||
{
|
app.eventBus.emit(
|
||||||
submitError && <antd.Alert
|
action.fireEvent,
|
||||||
message={submitError.message}
|
)
|
||||||
type="error"
|
}
|
||||||
/>
|
}}
|
||||||
}
|
disabled={action.disabled}
|
||||||
{
|
>
|
||||||
!Tab && <antd.Result
|
{action.label}
|
||||||
status="error"
|
</antd.Button>
|
||||||
title="Error"
|
)
|
||||||
subTitle="Tab not found"
|
})}
|
||||||
/>
|
</div>
|
||||||
}
|
)}
|
||||||
{
|
|
||||||
Tab && React.createElement(Tab.render, {
|
|
||||||
release: globalState,
|
|
||||||
|
|
||||||
state: globalState,
|
{customPage.content &&
|
||||||
setState: setGlobalState,
|
(React.isValidElement(customPage.content)
|
||||||
|
? React.cloneElement(customPage.content, {
|
||||||
|
...CustomPageProps,
|
||||||
|
...customPage.props,
|
||||||
|
})
|
||||||
|
: React.createElement(customPage.content, {
|
||||||
|
...CustomPageProps,
|
||||||
|
...customPage.props,
|
||||||
|
}))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!customPage && (
|
||||||
|
<>
|
||||||
|
<div className="music-studio-release-editor-menu">
|
||||||
|
<antd.Menu
|
||||||
|
onClick={(e) => setSelectedTab(e.key)}
|
||||||
|
selectedKeys={[selectedTab]}
|
||||||
|
items={Tabs}
|
||||||
|
mode="vertical"
|
||||||
|
/>
|
||||||
|
|
||||||
references: {
|
<div className="music-studio-release-editor-menu-actions">
|
||||||
basic: basicInfoRef
|
<antd.Button
|
||||||
}
|
type="primary"
|
||||||
})
|
onClick={handleSubmit}
|
||||||
}
|
icon={
|
||||||
</div>
|
release_id !== "new" ? (
|
||||||
</>
|
<Icons.FiSave />
|
||||||
}
|
) : (
|
||||||
</div>
|
<Icons.MdSend />
|
||||||
</ReleaseEditorStateContext.Provider>
|
)
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
submitting || loading || !canFinish()
|
||||||
|
}
|
||||||
|
loading={submitting}
|
||||||
|
>
|
||||||
|
{release_id !== "new" ? "Save" : "Release"}
|
||||||
|
</antd.Button>
|
||||||
|
|
||||||
|
{release_id !== "new" ? (
|
||||||
|
<antd.Button
|
||||||
|
icon={<Icons.IoMdTrash />}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</antd.Button>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{release_id !== "new" ? (
|
||||||
|
<antd.Button
|
||||||
|
icon={<Icons.MdLink />}
|
||||||
|
onClick={() =>
|
||||||
|
app.location.push(
|
||||||
|
`/music/release/${globalState._id}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Go to release
|
||||||
|
</antd.Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="music-studio-release-editor-content">
|
||||||
|
{submitError && (
|
||||||
|
<antd.Alert
|
||||||
|
message={submitError.message}
|
||||||
|
type="error"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!Tab && (
|
||||||
|
<antd.Result
|
||||||
|
status="error"
|
||||||
|
title="Error"
|
||||||
|
subTitle="Tab not found"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{Tab &&
|
||||||
|
React.createElement(Tab.render, {
|
||||||
|
release: globalState,
|
||||||
|
|
||||||
|
state: globalState,
|
||||||
|
setState: setGlobalState,
|
||||||
|
|
||||||
|
references: {
|
||||||
|
basic: basicInfoRef,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ReleaseEditorStateContext.Provider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReleaseEditor
|
export default ReleaseEditor
|
||||||
|
@ -11,13 +11,27 @@ import { ReleaseEditorStateContext } from "@contexts/MusicReleaseEditor"
|
|||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
|
const stateToString = {
|
||||||
|
uploading: "Uploading",
|
||||||
|
transmuxing: "Processing...",
|
||||||
|
uploading_s3: "Archiving...",
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTitleString = ({ track, progress }) => {
|
||||||
|
if (progress) {
|
||||||
|
return stateToString[progress.state] || progress.state
|
||||||
|
}
|
||||||
|
|
||||||
|
return track.title
|
||||||
|
}
|
||||||
|
|
||||||
const TrackListItem = (props) => {
|
const TrackListItem = (props) => {
|
||||||
const context = React.useContext(ReleaseEditorStateContext)
|
const context = React.useContext(ReleaseEditorStateContext)
|
||||||
|
|
||||||
const [loading, setLoading] = React.useState(false)
|
const [loading, setLoading] = React.useState(false)
|
||||||
const [error, setError] = React.useState(null)
|
const [error, setError] = React.useState(null)
|
||||||
|
|
||||||
const { track } = props
|
const { track, progress } = props
|
||||||
|
|
||||||
async function onClickEditTrack() {
|
async function onClickEditTrack() {
|
||||||
context.renderCustomPage({
|
context.renderCustomPage({
|
||||||
@ -33,8 +47,6 @@ const TrackListItem = (props) => {
|
|||||||
props.onDelete(track.uid)
|
props.onDelete(track.uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("render")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
@ -50,7 +62,7 @@ const TrackListItem = (props) => {
|
|||||||
<div
|
<div
|
||||||
className="music-studio-release-editor-tracks-list-item-progress"
|
className="music-studio-release-editor-tracks-list-item-progress"
|
||||||
style={{
|
style={{
|
||||||
"--upload-progress": `${props.uploading.progress}%`,
|
"--upload-progress": `${props.progress?.percent ?? 0}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -58,7 +70,7 @@ const TrackListItem = (props) => {
|
|||||||
<span>{props.index + 1}</span>
|
<span>{props.index + 1}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{props.uploading.working && <Icons.LoadingOutlined />}
|
{progress !== null && <Icons.LoadingOutlined />}
|
||||||
|
|
||||||
<Image
|
<Image
|
||||||
src={track.cover}
|
src={track.cover}
|
||||||
@ -69,7 +81,7 @@ const TrackListItem = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span>{track.title}</span>
|
<span>{getTitleString({ track, progress })}</span>
|
||||||
|
|
||||||
<div className="music-studio-release-editor-tracks-list-item-actions">
|
<div className="music-studio-release-editor-tracks-list-item-actions">
|
||||||
<antd.Popconfirm
|
<antd.Popconfirm
|
||||||
|
@ -17,12 +17,12 @@ class TracksManager extends React.Component {
|
|||||||
swapyRef = React.createRef()
|
swapyRef = React.createRef()
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
list: Array.isArray(this.props.list) ? this.props.list : [],
|
items: Array.isArray(this.props.items) ? this.props.items : [],
|
||||||
pendingUploads: [],
|
pendingUploads: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate = (prevProps, prevState) => {
|
componentDidUpdate = (prevProps, prevState) => {
|
||||||
if (prevState.list !== this.state.list) {
|
if (prevState.items !== this.state.items) {
|
||||||
if (typeof this.props.onChangeState === "function") {
|
if (typeof this.props.onChangeState === "function") {
|
||||||
this.props.onChangeState(this.state)
|
this.props.onChangeState(this.state)
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ class TracksManager extends React.Component {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state.list.find((item) => item.uid === uid)
|
return this.state.items.find((item) => item.uid === uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
addTrackToList = (track) => {
|
addTrackToList = (track) => {
|
||||||
@ -64,7 +64,7 @@ class TracksManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
list: [...this.state.list, track],
|
items: [...this.state.items, track],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,18 +76,17 @@ class TracksManager extends React.Component {
|
|||||||
this.removeTrackUIDFromPendingUploads(uid)
|
this.removeTrackUIDFromPendingUploads(uid)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
list: this.state.list.filter((item) => item.uid !== uid),
|
items: this.state.items.filter((item) => item.uid !== uid),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
modifyTrackByUid = (uid, track) => {
|
modifyTrackByUid = (uid, track) => {
|
||||||
console.log("modifyTrackByUid", uid, track)
|
|
||||||
if (!uid || !track) {
|
if (!uid || !track) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
list: this.state.list.map((item) => {
|
items: this.state.items.map((item) => {
|
||||||
if (item.uid === uid) {
|
if (item.uid === uid) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
@ -140,7 +139,7 @@ class TracksManager extends React.Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (uploadProgressIndex === -1) {
|
if (uploadProgressIndex === -1) {
|
||||||
return 0
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state.pendingUploads[uploadProgressIndex].progress
|
return this.state.pendingUploads[uploadProgressIndex].progress
|
||||||
@ -159,7 +158,7 @@ class TracksManager extends React.Component {
|
|||||||
|
|
||||||
newData[uploadProgressIndex].progress = progress
|
newData[uploadProgressIndex].progress = progress
|
||||||
|
|
||||||
console.log(`Updating progress for [${uid}] to [${progress}]`)
|
console.log(`Updating progress for [${uid}] to >`, progress)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
pendingUploads: newData,
|
pendingUploads: newData,
|
||||||
@ -189,7 +188,7 @@ class TracksManager extends React.Component {
|
|||||||
// remove pending file
|
// remove pending file
|
||||||
this.removeTrackUIDFromPendingUploads(uid)
|
this.removeTrackUIDFromPendingUploads(uid)
|
||||||
|
|
||||||
let trackManifest = this.state.list.find(
|
let trackManifest = this.state.items.find(
|
||||||
(item) => item.uid === uid,
|
(item) => item.uid === uid,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -231,9 +230,8 @@ class TracksManager extends React.Component {
|
|||||||
const response = await app.cores.remoteStorage
|
const response = await app.cores.remoteStorage
|
||||||
.uploadFile(req.file, {
|
.uploadFile(req.file, {
|
||||||
onProgress: this.handleTrackFileUploadProgress,
|
onProgress: this.handleTrackFileUploadProgress,
|
||||||
service: "b2",
|
|
||||||
headers: {
|
headers: {
|
||||||
transmux: "a-dash",
|
transformations: "a-dash",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -258,17 +256,17 @@ class TracksManager extends React.Component {
|
|||||||
this.setState((prev) => {
|
this.setState((prev) => {
|
||||||
// move all list items by id
|
// move all list items by id
|
||||||
const orderedIds = orderedIdsArray.map((id) =>
|
const orderedIds = orderedIdsArray.map((id) =>
|
||||||
this.state.list.find((item) => item._id === id),
|
this.state.items.find((item) => item._id === id),
|
||||||
)
|
)
|
||||||
console.log("orderedIds", orderedIds)
|
console.log("orderedIds", orderedIds)
|
||||||
return {
|
return {
|
||||||
list: orderedIds,
|
items: orderedIds,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.log(`Tracks List >`, this.state.list)
|
console.log(`Tracks List >`, this.state.items)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="music-studio-release-editor-tracks">
|
<div className="music-studio-release-editor-tracks">
|
||||||
@ -280,7 +278,7 @@ class TracksManager extends React.Component {
|
|||||||
accept="audio/*"
|
accept="audio/*"
|
||||||
multiple
|
multiple
|
||||||
>
|
>
|
||||||
{this.state.list.length === 0 ? (
|
{this.state.items.length === 0 ? (
|
||||||
<UploadHint />
|
<UploadHint />
|
||||||
) : (
|
) : (
|
||||||
<antd.Button
|
<antd.Button
|
||||||
@ -296,11 +294,11 @@ class TracksManager extends React.Component {
|
|||||||
id="editor-tracks-list"
|
id="editor-tracks-list"
|
||||||
className="music-studio-release-editor-tracks-list"
|
className="music-studio-release-editor-tracks-list"
|
||||||
>
|
>
|
||||||
{this.state.list.length === 0 && (
|
{this.state.items.length === 0 && (
|
||||||
<antd.Result status="info" title="No tracks" />
|
<antd.Result status="info" title="No tracks" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.list.map((track, index) => {
|
{this.state.items.map((track, index) => {
|
||||||
const progress = this.getUploadProgress(track.uid)
|
const progress = this.getUploadProgress(track.uid)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -310,12 +308,7 @@ class TracksManager extends React.Component {
|
|||||||
track={track}
|
track={track}
|
||||||
onEdit={this.modifyTrackByUid}
|
onEdit={this.modifyTrackByUid}
|
||||||
onDelete={this.removeTrackByUid}
|
onDelete={this.removeTrackByUid}
|
||||||
uploading={{
|
progress={progress}
|
||||||
progress: progress,
|
|
||||||
working: this.state.pendingUploads.find(
|
|
||||||
(item) => item.uid === track.uid,
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
disabled={progress > 0}
|
disabled={progress > 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -336,7 +329,7 @@ const ReleaseTracks = (props) => {
|
|||||||
|
|
||||||
<TracksManager
|
<TracksManager
|
||||||
_id={state._id}
|
_id={state._id}
|
||||||
list={state.list}
|
items={state.items}
|
||||||
onChangeState={(managerState) => {
|
onChangeState={(managerState) => {
|
||||||
setState({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
|
@ -10,158 +10,163 @@ import { ReleaseEditorStateContext } from "@contexts/MusicReleaseEditor"
|
|||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
const TrackEditor = (props) => {
|
const TrackEditor = (props) => {
|
||||||
const context = React.useContext(ReleaseEditorStateContext)
|
const context = React.useContext(ReleaseEditorStateContext)
|
||||||
const [track, setTrack] = React.useState(props.track ?? {})
|
const [track, setTrack] = React.useState(props.track ?? {})
|
||||||
|
|
||||||
async function handleChange(key, value) {
|
async function handleChange(key, value) {
|
||||||
setTrack((prev) => {
|
setTrack((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
[key]: value
|
[key]: value,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openEnhancedLyricsEditor() {
|
async function openEnhancedLyricsEditor() {
|
||||||
context.renderCustomPage({
|
context.renderCustomPage({
|
||||||
header: "Enhanced Lyrics",
|
header: "Enhanced Lyrics",
|
||||||
content: EnhancedLyricsEditor,
|
content: EnhancedLyricsEditor,
|
||||||
props: {
|
props: {
|
||||||
track: track,
|
track: track,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleOnSave() {
|
async function handleOnSave() {
|
||||||
setTrack((prev) => {
|
setTrack((prev) => {
|
||||||
const listData = [...context.list]
|
const listData = [...context.items]
|
||||||
|
|
||||||
const trackIndex = listData.findIndex((item) => item.uid === prev.uid)
|
const trackIndex = listData.findIndex(
|
||||||
|
(item) => item.uid === prev.uid,
|
||||||
|
)
|
||||||
|
|
||||||
if (trackIndex === -1) {
|
if (trackIndex === -1) {
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
listData[trackIndex] = prev
|
listData[trackIndex] = prev
|
||||||
|
|
||||||
context.setGlobalState({
|
context.setGlobalState({
|
||||||
...context,
|
...context,
|
||||||
list: listData
|
items: listData,
|
||||||
})
|
})
|
||||||
|
|
||||||
return prev
|
props.close()
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
return prev
|
||||||
context.setCustomPageActions([
|
})
|
||||||
{
|
}
|
||||||
label: "Save",
|
|
||||||
icon: "FiSave",
|
|
||||||
type: "primary",
|
|
||||||
onClick: handleOnSave,
|
|
||||||
disabled: props.track === track,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}, [track])
|
|
||||||
|
|
||||||
return <div className="track-editor">
|
function setParentCover() {
|
||||||
<div className="track-editor-field">
|
handleChange("cover", context.cover)
|
||||||
<div className="track-editor-field-header">
|
}
|
||||||
<Icons.MdImage />
|
|
||||||
<span>Cover</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CoverEditor
|
React.useEffect(() => {
|
||||||
value={track.cover}
|
context.setCustomPageActions([
|
||||||
onChange={(url) => handleChange("cover", url)}
|
{
|
||||||
extraActions={[
|
label: "Save",
|
||||||
<antd.Button>
|
icon: "FiSave",
|
||||||
Use Parent
|
type: "primary",
|
||||||
</antd.Button>
|
onClick: handleOnSave,
|
||||||
]}
|
disabled: props.track === track,
|
||||||
/>
|
},
|
||||||
</div>
|
])
|
||||||
|
}, [track])
|
||||||
|
|
||||||
<div className="track-editor-field">
|
return (
|
||||||
<div className="track-editor-field-header">
|
<div className="track-editor">
|
||||||
<Icons.MdOutlineMusicNote />
|
<div className="track-editor-field">
|
||||||
<span>Title</span>
|
<div className="track-editor-field-header">
|
||||||
</div>
|
<Icons.MdImage />
|
||||||
|
<span>Cover</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<antd.Input
|
<CoverEditor
|
||||||
value={track.title}
|
value={track.cover}
|
||||||
placeholder="Track title"
|
onChange={(url) => handleChange("cover", url)}
|
||||||
onChange={(e) => handleChange("title", e.target.value)}
|
extraActions={[
|
||||||
/>
|
<antd.Button onClick={setParentCover}>
|
||||||
</div>
|
Use Parent
|
||||||
|
</antd.Button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="track-editor-field">
|
<div className="track-editor-field">
|
||||||
<div className="track-editor-field-header">
|
<div className="track-editor-field-header">
|
||||||
<Icons.FiUser />
|
<Icons.MdOutlineMusicNote />
|
||||||
<span>Artist</span>
|
<span>Title</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<antd.Input
|
<antd.Input
|
||||||
value={track.artists?.join(", ")}
|
value={track.title}
|
||||||
placeholder="Artist"
|
placeholder="Track title"
|
||||||
onChange={(e) => handleChange("artist", e.target.value)}
|
onChange={(e) => handleChange("title", e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="track-editor-field">
|
<div className="track-editor-field">
|
||||||
<div className="track-editor-field-header">
|
<div className="track-editor-field-header">
|
||||||
<Icons.MdAlbum />
|
<Icons.FiUser />
|
||||||
<span>Album</span>
|
<span>Artist</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<antd.Input
|
<antd.Input
|
||||||
value={track.album}
|
value={track.artist}
|
||||||
placeholder="Album"
|
placeholder="Artist"
|
||||||
onChange={(e) => handleChange("album", e.target.value)}
|
onChange={(e) => handleChange("artist", e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="track-editor-field">
|
<div className="track-editor-field">
|
||||||
<div className="track-editor-field-header">
|
<div className="track-editor-field-header">
|
||||||
<Icons.MdExplicit />
|
<Icons.MdAlbum />
|
||||||
<span>Explicit</span>
|
<span>Album</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<antd.Switch
|
<antd.Input
|
||||||
checked={track.explicit}
|
value={track.album}
|
||||||
onChange={(value) => handleChange("explicit", value)}
|
placeholder="Album"
|
||||||
/>
|
onChange={(e) => handleChange("album", e.target.value)}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="track-editor-field">
|
<div className="track-editor-field">
|
||||||
<div className="track-editor-field-header">
|
<div className="track-editor-field-header">
|
||||||
<Icons.MdLyrics />
|
<Icons.MdExplicit />
|
||||||
<span>Enhanced Lyrics</span>
|
<span>Explicit</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<antd.Switch
|
<antd.Switch
|
||||||
checked={track.lyrics_enabled}
|
checked={track.explicit}
|
||||||
onChange={(value) => handleChange("lyrics_enabled", value)}
|
onChange={(value) => handleChange("explicit", value)}
|
||||||
disabled={!track.params._id}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="track-editor-field-actions">
|
<div className="track-editor-field">
|
||||||
<antd.Button
|
<div className="track-editor-field-header">
|
||||||
disabled={!track.params._id}
|
<Icons.MdLyrics />
|
||||||
onClick={openEnhancedLyricsEditor}
|
<span>Enhanced Lyrics</span>
|
||||||
>
|
</div>
|
||||||
Edit
|
|
||||||
</antd.Button>
|
|
||||||
|
|
||||||
{
|
<div className="track-editor-field-actions">
|
||||||
!track.params._id && <span>
|
<antd.Button
|
||||||
You cannot edit Video and Lyrics without release first
|
disabled={!track.params._id}
|
||||||
</span>
|
onClick={openEnhancedLyricsEditor}
|
||||||
}
|
>
|
||||||
</div>
|
Edit
|
||||||
</div>
|
</antd.Button>
|
||||||
</div>
|
|
||||||
|
{!track.params._id && (
|
||||||
|
<span>
|
||||||
|
You cannot edit Video and Lyrics without release
|
||||||
|
first
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TrackEditor
|
export default TrackEditor
|
||||||
|
Loading…
x
Reference in New Issue
Block a user