diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx b/packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx index 2e5d604c..1aa38e05 100644 --- a/packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx @@ -1,280 +1,332 @@ import React from "react" import * as antd from "antd" - import { Icons, createIconRender } from "@components/Icons" import MusicModel from "@models/music" - +import compareObjectsByProperties from "@utils/compareObjectsByProperties" import useUrlQueryActiveKey from "@hooks/useUrlQueryActiveKey" import TrackManifest from "@cores/player/classes/TrackManifest" -import { DefaultReleaseEditorState, ReleaseEditorStateContext } from "@contexts/MusicReleaseEditor" +import { + DefaultReleaseEditorState, + ReleaseEditorStateContext, +} from "@contexts/MusicReleaseEditor" import Tabs from "./tabs" import "./index.less" 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 [loading, setLoading] = React.useState(true) - const [submitError, setSubmitError] = React.useState(null) + const [submitting, setSubmitting] = React.useState(false) + const [loading, setLoading] = React.useState(true) + const [submitError, setSubmitError] = React.useState(null) - const [loadError, setLoadError] = React.useState(null) - const [globalState, setGlobalState] = React.useState(DefaultReleaseEditorState) + const [loadError, setLoadError] = React.useState(null) + const [globalState, setGlobalState] = React.useState( + DefaultReleaseEditorState, + ) + const [initialValues, setInitialValues] = React.useState({}) - const [customPage, setCustomPage] = React.useState(null) - const [customPageActions, setCustomPageActions] = React.useState([]) + const [customPage, setCustomPage] = React.useState(null) + const [customPageActions, setCustomPageActions] = React.useState([]) - const [selectedTab, setSelectedTab] = useUrlQueryActiveKey({ - defaultKey: "info", - queryKey: "tab" - }) + const [selectedTab, setSelectedTab] = useUrlQueryActiveKey({ + defaultKey: "info", + queryKey: "tab", + }) - async function initialize() { - setLoading(true) - setLoadError(null) + async function initialize() { + setLoading(true) + setLoadError(null) - if (release_id !== "new") { - try { - let releaseData = await MusicModel.getReleaseData(release_id) + if (release_id !== "new") { + try { + let releaseData = await MusicModel.getReleaseData(release_id) - if (Array.isArray(releaseData.list)) { - releaseData.list = releaseData.list.map((item) => { - return new TrackManifest(item) - }) - } + if (Array.isArray(releaseData.items)) { + releaseData.items = releaseData.items.map((item) => { + return new TrackManifest(item) + }) + } - setGlobalState({ - ...globalState, - ...releaseData, - }) - } catch (error) { - setLoadError(error) - } - } + setGlobalState({ + ...globalState, + ...releaseData, + }) - setLoading(false) - } + setInitialValues(releaseData) + } catch (error) { + setLoadError(error) + } + } - async function renderCustomPage(page, actions) { - setCustomPage(page ?? null) - setCustomPageActions(actions ?? []) - } + setLoading(false) + } - async function handleSubmit() { - setSubmitting(true) - setSubmitError(null) + function hasChanges() { + const stagedChanges = { + title: globalState.title, + type: globalState.type, + public: globalState.public, + cover: globalState.cover, + items: globalState.items, + } - try { - // first sumbit tracks - const tracks = await MusicModel.putTrack({ - list: globalState.list, - }) + return !compareObjectsByProperties( + stagedChanges, + initialValues, + Object.keys(stagedChanges), + ) + } - // then submit release - const result = await MusicModel.putRelease({ - _id: globalState._id, - 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), - }) + async function renderCustomPage(page, actions) { + setCustomPage(page ?? null) + setCustomPageActions(actions ?? []) + } - app.location.push(`/studio/music/${result._id}`) - } catch (error) { - console.error(error) - app.message.error(error.message) + async function handleSubmit() { + setSubmitting(true) + setSubmitError(null) - setSubmitError(error) - setSubmitting(false) + try { + console.log("Submitting Tracks") - return false - } + // first sumbit tracks + const tracks = await MusicModel.putTrack({ + items: globalState.items, + }) - setSubmitting(false) - app.message.success("Release saved") - } + console.log("Submitting release") - 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("/")) - }, - }) - } + // then submit release + const result = await MusicModel.putRelease({ + _id: globalState._id, + title: globalState.title, + description: globalState.description, + public: globalState.public, + cover: globalState.cover, + explicit: globalState.explicit, + type: globalState.type, + items: tracks.items.map((item) => item._id), + }) - async function canFinish() { - return true - } + app.location.push(`/studio/music/${result._id}`) + } catch (error) { + console.error(error) + app.message.error(error.message) - React.useEffect(() => { - initialize() - }, []) + setSubmitError(error) + setSubmitting(false) - if (loadError) { - return - } + return false + } - if (loading) { - return - } + setSubmitting(false) + 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 = { - close: () => { - renderCustomPage(null, null) - } - } + function canFinish() { + return hasChanges() + } - return -
- { - customPage &&
- { - customPage.header &&
-
- } - onClick={() => renderCustomPage(null, null)} - /> + React.useEffect(() => { + initialize() + }, []) -

{customPage.header}

-
+ if (loadError) { + return ( + + ) + } - { - Array.isArray(customPageActions) && customPageActions.map((action, index) => { - return { - if (typeof action.onClick === "function") { - await action.onClick() - } + if (loading) { + return + } - if (action.fireEvent) { - app.eventBus.emit(action.fireEvent) - } - }} - disabled={action.disabled} - > - {action.label} - - }) - } -
- } + const Tab = Tabs.find(({ key }) => key === selectedTab) - { - customPage.content && (React.isValidElement(customPage.content) ? - React.cloneElement(customPage.content, { - ...CustomPageProps, - ...customPage.props - }) : - React.createElement(customPage.content, { - ...CustomPageProps, - ...customPage.props - }) - ) - } -
- } - { - !customPage && <> -
- setSelectedTab(e.key)} - selectedKeys={[selectedTab]} - items={Tabs} - mode="vertical" - /> + const CustomPageProps = { + close: () => { + renderCustomPage(null, null) + }, + } -
- : } - disabled={submitting || loading || !canFinish()} - loading={submitting} - > - {release_id !== "new" ? "Save" : "Release"} - + return ( + +
+ {customPage && ( +
+ {customPage.header && ( +
+
+ } + onClick={() => + renderCustomPage(null, null) + } + /> - { - release_id !== "new" ? } - disabled={loading} - onClick={handleDelete} - > - Delete - : null - } +

{customPage.header}

+
- { - release_id !== "new" ? } - onClick={() => app.location.push(`/music/release/${globalState._id}`)} - > - Go to release - : null - } -
-
+ {Array.isArray(customPageActions) && + customPageActions.map((action, index) => { + return ( + { + if ( + typeof action.onClick === + "function" + ) { + await action.onClick() + } -
- { - submitError && - } - { - !Tab && - } - { - Tab && React.createElement(Tab.render, { - release: globalState, + if (action.fireEvent) { + app.eventBus.emit( + action.fireEvent, + ) + } + }} + disabled={action.disabled} + > + {action.label} + + ) + })} +
+ )} - state: globalState, - setState: setGlobalState, + {customPage.content && + (React.isValidElement(customPage.content) + ? React.cloneElement(customPage.content, { + ...CustomPageProps, + ...customPage.props, + }) + : React.createElement(customPage.content, { + ...CustomPageProps, + ...customPage.props, + }))} +
+ )} + {!customPage && ( + <> +
+ setSelectedTab(e.key)} + selectedKeys={[selectedTab]} + items={Tabs} + mode="vertical" + /> - references: { - basic: basicInfoRef - } - }) - } -
- - } -
- +
+ + ) : ( + + ) + } + disabled={ + submitting || loading || !canFinish() + } + loading={submitting} + > + {release_id !== "new" ? "Save" : "Release"} + + + {release_id !== "new" ? ( + } + disabled={loading} + onClick={handleDelete} + > + Delete + + ) : null} + + {release_id !== "new" ? ( + } + onClick={() => + app.location.push( + `/music/release/${globalState._id}`, + ) + } + > + Go to release + + ) : null} +
+
+ +
+ {submitError && ( + + )} + {!Tab && ( + + )} + {Tab && + React.createElement(Tab.render, { + release: globalState, + + state: globalState, + setState: setGlobalState, + + references: { + basic: basicInfoRef, + }, + })} +
+ + )} +
+
+ ) } -export default ReleaseEditor \ No newline at end of file +export default ReleaseEditor diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/components/TrackListItem/index.jsx b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/components/TrackListItem/index.jsx index 38a6c0ef..23f4fcec 100644 --- a/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/components/TrackListItem/index.jsx +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/components/TrackListItem/index.jsx @@ -11,13 +11,27 @@ import { ReleaseEditorStateContext } from "@contexts/MusicReleaseEditor" 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 context = React.useContext(ReleaseEditorStateContext) const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState(null) - const { track } = props + const { track, progress } = props async function onClickEditTrack() { context.renderCustomPage({ @@ -33,8 +47,6 @@ const TrackListItem = (props) => { props.onDelete(track.uid) } - console.log("render") - return (
{
@@ -58,7 +70,7 @@ const TrackListItem = (props) => { {props.index + 1}
- {props.uploading.working && } + {progress !== null && } { }} /> - {track.title} + {getTitleString({ track, progress })}
{ - if (prevState.list !== this.state.list) { + if (prevState.items !== this.state.items) { if (typeof this.props.onChangeState === "function") { this.props.onChangeState(this.state) } @@ -55,7 +55,7 @@ class TracksManager extends React.Component { return false } - return this.state.list.find((item) => item.uid === uid) + return this.state.items.find((item) => item.uid === uid) } addTrackToList = (track) => { @@ -64,7 +64,7 @@ class TracksManager extends React.Component { } 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.setState({ - list: this.state.list.filter((item) => item.uid !== uid), + items: this.state.items.filter((item) => item.uid !== uid), }) } modifyTrackByUid = (uid, track) => { - console.log("modifyTrackByUid", uid, track) if (!uid || !track) { return false } this.setState({ - list: this.state.list.map((item) => { + items: this.state.items.map((item) => { if (item.uid === uid) { return { ...item, @@ -140,7 +139,7 @@ class TracksManager extends React.Component { ) if (uploadProgressIndex === -1) { - return 0 + return null } return this.state.pendingUploads[uploadProgressIndex].progress @@ -159,7 +158,7 @@ class TracksManager extends React.Component { newData[uploadProgressIndex].progress = progress - console.log(`Updating progress for [${uid}] to [${progress}]`) + console.log(`Updating progress for [${uid}] to >`, progress) this.setState({ pendingUploads: newData, @@ -189,7 +188,7 @@ class TracksManager extends React.Component { // remove pending file this.removeTrackUIDFromPendingUploads(uid) - let trackManifest = this.state.list.find( + let trackManifest = this.state.items.find( (item) => item.uid === uid, ) @@ -231,9 +230,8 @@ class TracksManager extends React.Component { const response = await app.cores.remoteStorage .uploadFile(req.file, { onProgress: this.handleTrackFileUploadProgress, - service: "b2", headers: { - transmux: "a-dash", + transformations: "a-dash", }, }) .catch((error) => { @@ -258,17 +256,17 @@ class TracksManager extends React.Component { this.setState((prev) => { // move all list items by 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) return { - list: orderedIds, + items: orderedIds, } }) } render() { - console.log(`Tracks List >`, this.state.list) + console.log(`Tracks List >`, this.state.items) return (
@@ -280,7 +278,7 @@ class TracksManager extends React.Component { accept="audio/*" multiple > - {this.state.list.length === 0 ? ( + {this.state.items.length === 0 ? ( ) : ( - {this.state.list.length === 0 && ( + {this.state.items.length === 0 && ( )} - {this.state.list.map((track, index) => { + {this.state.items.map((track, index) => { const progress = this.getUploadProgress(track.uid) return ( @@ -310,12 +308,7 @@ class TracksManager extends React.Component { track={track} onEdit={this.modifyTrackByUid} onDelete={this.removeTrackByUid} - uploading={{ - progress: progress, - working: this.state.pendingUploads.find( - (item) => item.uid === track.uid, - ), - }} + progress={progress} disabled={progress > 0} />
@@ -336,7 +329,7 @@ const ReleaseTracks = (props) => { { setState({ ...state, diff --git a/packages/app/src/components/MusicStudio/TrackEditor/index.jsx b/packages/app/src/components/MusicStudio/TrackEditor/index.jsx index d73f48ba..9c5fab25 100644 --- a/packages/app/src/components/MusicStudio/TrackEditor/index.jsx +++ b/packages/app/src/components/MusicStudio/TrackEditor/index.jsx @@ -10,158 +10,163 @@ import { ReleaseEditorStateContext } from "@contexts/MusicReleaseEditor" import "./index.less" const TrackEditor = (props) => { - const context = React.useContext(ReleaseEditorStateContext) - const [track, setTrack] = React.useState(props.track ?? {}) + const context = React.useContext(ReleaseEditorStateContext) + const [track, setTrack] = React.useState(props.track ?? {}) - async function handleChange(key, value) { - setTrack((prev) => { - return { - ...prev, - [key]: value - } - }) - } + async function handleChange(key, value) { + setTrack((prev) => { + return { + ...prev, + [key]: value, + } + }) + } - async function openEnhancedLyricsEditor() { - context.renderCustomPage({ - header: "Enhanced Lyrics", - content: EnhancedLyricsEditor, - props: { - track: track, - } - }) - } + async function openEnhancedLyricsEditor() { + context.renderCustomPage({ + header: "Enhanced Lyrics", + content: EnhancedLyricsEditor, + props: { + track: track, + }, + }) + } - async function handleOnSave() { - setTrack((prev) => { - const listData = [...context.list] + async function handleOnSave() { + setTrack((prev) => { + 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) { - return prev - } + if (trackIndex === -1) { + return prev + } - listData[trackIndex] = prev + listData[trackIndex] = prev - context.setGlobalState({ - ...context, - list: listData - }) + context.setGlobalState({ + ...context, + items: listData, + }) - return prev - }) - } + props.close() - React.useEffect(() => { - context.setCustomPageActions([ - { - label: "Save", - icon: "FiSave", - type: "primary", - onClick: handleOnSave, - disabled: props.track === track, - }, - ]) - }, [track]) + return prev + }) + } - return
-
-
- - Cover -
+ function setParentCover() { + handleChange("cover", context.cover) + } - handleChange("cover", url)} - extraActions={[ - - Use Parent - - ]} - /> -
+ React.useEffect(() => { + context.setCustomPageActions([ + { + label: "Save", + icon: "FiSave", + type: "primary", + onClick: handleOnSave, + disabled: props.track === track, + }, + ]) + }, [track]) -
-
- - Title -
+ return ( +
+
+
+ + Cover +
- handleChange("title", e.target.value)} - /> -
+ handleChange("cover", url)} + extraActions={[ + + Use Parent + , + ]} + /> +
-
-
- - Artist -
+
+
+ + Title +
- handleChange("artist", e.target.value)} - /> -
+ handleChange("title", e.target.value)} + /> +
-
-
- - Album -
+
+
+ + Artist +
- handleChange("album", e.target.value)} - /> -
+ handleChange("artist", e.target.value)} + /> +
-
-
- - Explicit -
+
+
+ + Album +
- handleChange("explicit", value)} - /> -
+ handleChange("album", e.target.value)} + /> +
-
-
- - Enhanced Lyrics +
+
+ + Explicit +
- handleChange("lyrics_enabled", value)} - disabled={!track.params._id} - /> -
+ handleChange("explicit", value)} + /> +
-
- - Edit - +
+
+ + Enhanced Lyrics +
- { - !track.params._id && - You cannot edit Video and Lyrics without release first - - } -
-
-
+
+ + Edit + + + {!track.params._id && ( + + You cannot edit Video and Lyrics without release + first + + )} +
+
+
+ ) } -export default TrackEditor \ No newline at end of file +export default TrackEditor