Add change tracking and update to use "items" property

This commit is contained in:
SrGooglo 2025-04-24 06:13:37 +00:00
parent d738995054
commit 74021f38b6
4 changed files with 450 additions and 388 deletions

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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