mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-11 03:24:16 +00:00
added music feed & creator
This commit is contained in:
parent
5f27846c4e
commit
6fd8670ee7
@ -15,10 +15,6 @@ export default class MusicDashboard extends React.Component {
|
|||||||
|
|
||||||
primaryPanelRef = React.createRef()
|
primaryPanelRef = React.createRef()
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
app.eventBus.emit("style.compactMode", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderActiveTab() {
|
renderActiveTab() {
|
||||||
const tab = Tabs[this.state.activeTab]
|
const tab = Tabs[this.state.activeTab]
|
||||||
|
|
||||||
@ -68,18 +64,17 @@ export default class MusicDashboard extends React.Component {
|
|||||||
selectedKeys={[this.state.activeTab]}
|
selectedKeys={[this.state.activeTab]}
|
||||||
activeKey={this.state.activeTab}
|
activeKey={this.state.activeTab}
|
||||||
onClick={({ key }) => this.handleTabChange(key)}
|
onClick={({ key }) => this.handleTabChange(key)}
|
||||||
>
|
items={Object.keys(Tabs).map((key) => {
|
||||||
{Object.keys(Tabs).map((key) => {
|
|
||||||
const tab = Tabs[key]
|
const tab = Tabs[key]
|
||||||
|
|
||||||
return <antd.Menu.Item
|
return {
|
||||||
key={key}
|
key,
|
||||||
icon={createIconRender(tab.icon)}
|
icon: createIconRender(tab.icon),
|
||||||
>
|
label: tab.label,
|
||||||
{tab.title}
|
disabled: tab.disabled
|
||||||
</antd.Menu.Item>
|
}
|
||||||
})}
|
})}
|
||||||
</antd.Menu>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
130
packages/app/src/pages/music/components/dashboard/index.jsx
Normal file
130
packages/app/src/pages/music/components/dashboard/index.jsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
import { ImageViewer } from "components"
|
||||||
|
|
||||||
|
import * as antd from "antd"
|
||||||
|
import PlaylistsModel from "models/playlists"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const getReleases = async () => {
|
||||||
|
const response = await PlaylistsModel.getMyReleases().catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
app.message.error("Failed to load releases")
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReleaseItem = (props) => {
|
||||||
|
const { key, release } = props
|
||||||
|
|
||||||
|
console.log(props)
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className="music_panel_releases_item"
|
||||||
|
key={key}
|
||||||
|
id={key}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="music_panel_releases_info"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="music_panel_releases_info_cover"
|
||||||
|
>
|
||||||
|
<ImageViewer
|
||||||
|
src={release.thumbnail ?? "/assets/no_song.png"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="music_panel_releases_info_title"
|
||||||
|
>
|
||||||
|
<h1>
|
||||||
|
{release.title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
{release.description}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="music_panel_releases_actions"
|
||||||
|
>
|
||||||
|
<antd.Button
|
||||||
|
onClick={props.onClickEditTrack}
|
||||||
|
icon={<Icons.Edit />}
|
||||||
|
>
|
||||||
|
Modify
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const [releases, setReleases] = React.useState([])
|
||||||
|
const [loading, setLoading] = React.useState(false)
|
||||||
|
|
||||||
|
const onClickEditTrack = (track_id) => {
|
||||||
|
console.log("Edit track", track_id)
|
||||||
|
|
||||||
|
app.setLocation(`/music/creator?playlist_id=${track_id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
const releases = await getReleases()
|
||||||
|
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
console.log(releases)
|
||||||
|
|
||||||
|
if (releases) {
|
||||||
|
setReleases(releases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
loadData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <antd.Skeleton active />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className="music_panel_creator"
|
||||||
|
>
|
||||||
|
<div className="music_panel_releases_header">
|
||||||
|
<h1>
|
||||||
|
<Icons.Music />
|
||||||
|
Your releases
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="music_panel_releases_header_actions">
|
||||||
|
<antd.Button
|
||||||
|
onClick={() => app.setLocation("/music/creator")}
|
||||||
|
icon={<Icons.Plus />}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
New release
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="music_panel_releases_list">
|
||||||
|
{
|
||||||
|
releases.map((release) => {
|
||||||
|
return <ReleaseItem
|
||||||
|
key={release._id}
|
||||||
|
release={release}
|
||||||
|
onClickEditTrack={() => onClickEditTrack(release._id)}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
109
packages/app/src/pages/music/components/dashboard/index.less
Normal file
109
packages/app/src/pages/music/components/dashboard/index.less
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
.music_panel_creator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
|
||||||
|
.music_panel_releases_header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.music_panel_releases_header_actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.music_panel_releases_list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.music_panel_releases_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
background-color: var(--background-color-accent);
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music_panel_releases_info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.music_panel_releases_info_cover {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.music_panel_releases_info_title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.music_panel_releases_actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import React from "react"
|
|||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
import { ImageViewer } from "components"
|
import { ImageViewer } from "components"
|
||||||
import { Icons } from "components/Icons"
|
import { Icons } from "components/Icons"
|
||||||
|
import { Translation } from "react-i18next"
|
||||||
|
|
||||||
import FeedModel from "models/feed"
|
import FeedModel from "models/feed"
|
||||||
|
|
||||||
@ -18,6 +19,14 @@ const PlaylistItem = (props) => {
|
|||||||
return app.setLocation(`/play/${playlist._id}`)
|
return app.setLocation(`/play/${playlist._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onClickPlay = (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
console.log(playlist.list)
|
||||||
|
|
||||||
|
app.cores.player.startPlaylist(playlist.list)
|
||||||
|
}
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
id={playlist._id}
|
id={playlist._id}
|
||||||
key={props.key}
|
key={props.key}
|
||||||
@ -25,7 +34,7 @@ const PlaylistItem = (props) => {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div className="playlistItem_cover">
|
<div className="playlistItem_cover">
|
||||||
<ImageViewer src={playlist.cover ?? "/assets/no_song.png"} />
|
<ImageViewer src={playlist.thumbnail ?? "/assets/no_song.png"} />
|
||||||
</div>
|
</div>
|
||||||
<div className="playlistItem_info">
|
<div className="playlistItem_info">
|
||||||
<div className="playlistItem_info_title">
|
<div className="playlistItem_info_title">
|
||||||
@ -35,6 +44,14 @@ const PlaylistItem = (props) => {
|
|||||||
{playlist.user.username}
|
{playlist.user.username}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="playlistItem_actions">
|
||||||
|
<antd.Button
|
||||||
|
icon={<Icons.Play />}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
onClick={onClickPlay}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,23 +91,12 @@ export default () => {
|
|||||||
return <div className="playlistExplorer">
|
return <div className="playlistExplorer">
|
||||||
<div className="playlistExplorer_section">
|
<div className="playlistExplorer_section">
|
||||||
<div className="playlistExplorer_section_header">
|
<div className="playlistExplorer_section_header">
|
||||||
<h1><Icons.Compass /> Releases from your artist</h1>
|
<h1>
|
||||||
</div>
|
<Icons.MdOutlineMarkunreadMailbox />
|
||||||
<div className="playlistExplorer_section_list">
|
<Translation>
|
||||||
{
|
{(t) => t("Releases from your artists")}
|
||||||
list.map((playlist, index) => {
|
</Translation>
|
||||||
return <PlaylistItem
|
</h1>
|
||||||
key={index}
|
|
||||||
playlist={playlist}
|
|
||||||
/>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="playlistExplorer_section">
|
|
||||||
<div className="playlistExplorer_section_header">
|
|
||||||
<h1><Icons.MdOutlineTrendingUp /> Discover new trends</h1>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="playlistExplorer_section_list">
|
<div className="playlistExplorer_section_list">
|
||||||
{
|
{
|
||||||
|
@ -22,18 +22,11 @@
|
|||||||
|
|
||||||
.playlistExplorer_section_list {
|
.playlistExplorer_section_list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
//flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
overflow-x: scroll;
|
|
||||||
|
|
||||||
padding: 10px 0;
|
|
||||||
|
|
||||||
>.playlistItem {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,36 +34,58 @@
|
|||||||
.playlistItem {
|
.playlistItem {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
width: 200px;
|
width: 45%;
|
||||||
min-width: 200px;
|
height: 10vh;
|
||||||
|
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
|
margin-right: 20px;
|
||||||
&:hover {
|
|
||||||
transform: translate(0, -5px);
|
|
||||||
outline: 2px solid var(--background-color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
background-color: var(--background-color-accent);
|
background-color: var(--background-color-accent);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
.playlistItem_cover {
|
margin-bottom: 20px;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
transition: all 0.2s ease-in-out;
|
&:hover {
|
||||||
|
.playlistItem_cover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
|
.playlistItem_info {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
width: 10vh;
|
||||||
|
height: 10vh;
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistItem_cover {
|
||||||
|
height: 10vh;
|
||||||
|
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
@ -104,4 +119,21 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playlistItem_actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
svg {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
699
packages/app/src/pages/music/creator/index.jsx
Normal file
699
packages/app/src/pages/music/creator/index.jsx
Normal file
@ -0,0 +1,699 @@
|
|||||||
|
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 PlaylistModel from "models/playlists"
|
||||||
|
import UploadModel from "models/upload"
|
||||||
|
|
||||||
|
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 FileListItem = React.memo((props) => {
|
||||||
|
console.log(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="fileListItem_cover">
|
||||||
|
<img
|
||||||
|
src={props.track?.thumbnail}
|
||||||
|
alt="Track cover"
|
||||||
|
onClick={() => {
|
||||||
|
if (typeof props.onClickChangeCover === "function") {
|
||||||
|
if (!isUploading) {
|
||||||
|
props.onClickChangeCover(props.track.uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="fileListItem_details">
|
||||||
|
<div className="fileListItem_title">
|
||||||
|
<div className="fileListItem_title_label">
|
||||||
|
<Icons.MdTitle />
|
||||||
|
<h4>Track name</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<antd.Input
|
||||||
|
size="large"
|
||||||
|
bordered={false}
|
||||||
|
value={props.track?.title ?? "Track name"}
|
||||||
|
onChange={props.onTitleChange}
|
||||||
|
placeholder="Example: My track"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="fileListItem_actions">
|
||||||
|
<antd.Popconfirm
|
||||||
|
title="Delete this track?"
|
||||||
|
description="Are you sure to delete this track?"
|
||||||
|
onConfirm={props.onClickRemove}
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
>
|
||||||
|
<antd.Button
|
||||||
|
type="primary"
|
||||||
|
icon={<Icons.MdDelete />}
|
||||||
|
danger
|
||||||
|
/>
|
||||||
|
</antd.Popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
className="fileListItem_dragHandle"
|
||||||
|
>
|
||||||
|
<Icons.MdDragIndicator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</Draggable>
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Make cover preview style more beautiful (E.G. Use the entire div as background)
|
||||||
|
// TODO: Make files list item can be dragged to change their order
|
||||||
|
|
||||||
|
export default class PlaylistCreator extends React.Component {
|
||||||
|
state = {
|
||||||
|
playlistName: null,
|
||||||
|
playlistDescription: null,
|
||||||
|
playlistThumbnail: null,
|
||||||
|
|
||||||
|
fileList: [],
|
||||||
|
trackList: [],
|
||||||
|
|
||||||
|
pendingUploads: [],
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragEnd = (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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTitleOnChange = (event) => {
|
||||||
|
this.setState({
|
||||||
|
playlistName: event.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDescriptionOnChange = (event) => {
|
||||||
|
this.setState({
|
||||||
|
playlistDescription: event.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTrackTitleOnChange = (event, uid) => {
|
||||||
|
// find the file in the trackinfo
|
||||||
|
const file = this.state.trackList.find((file) => file.uid === uid)
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
file.title = event.target.value
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
trackList: this.state.trackList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTrackRemove = (uid) => {
|
||||||
|
this.setState({
|
||||||
|
fileList: this.state.fileList.filter((file) => file.uid !== uid),
|
||||||
|
trackList: this.state.trackList.filter((file) => file.uid !== uid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTrackCoverChange = (uid) => {
|
||||||
|
// open a file dialog
|
||||||
|
const fileInput = document.createElement("input")
|
||||||
|
|
||||||
|
fileInput.type = "file"
|
||||||
|
fileInput.accept = "image/*"
|
||||||
|
|
||||||
|
fileInput.onchange = (event) => {
|
||||||
|
const file = event.target.files[0]
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
// upload the file
|
||||||
|
UploadModel.uploadFile(file).then((response) => {
|
||||||
|
// update the file url in the track info
|
||||||
|
const file = this.state.trackList.find((file) => file.uid === uid)
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
file.thumbnail = response.url
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
trackList: this.state.trackList
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInput.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTrack = (uid) => {
|
||||||
|
this.setState({
|
||||||
|
fileList: this.state.fileList.filter((file) => file.uid !== uid),
|
||||||
|
trackList: this.state.trackList.filter((file) => file.uid !== uid),
|
||||||
|
pendingUploads: this.state.pendingUploads.filter((uid) => uid !== uid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploaderOnChange = (change) => {
|
||||||
|
console.log(change)
|
||||||
|
|
||||||
|
switch (change.file.status) {
|
||||||
|
case "uploading": {
|
||||||
|
const { pendingUploads } = this.state
|
||||||
|
|
||||||
|
if (!pendingUploads.includes(change.file.uid)) {
|
||||||
|
pendingUploads.push(change.file.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
pendingUploads: pendingUploads,
|
||||||
|
fileList: [...this.state.fileList, change.file],
|
||||||
|
trackList: [...this.state.trackList, {
|
||||||
|
uid: change.file.uid,
|
||||||
|
title: change.file.name,
|
||||||
|
source: null,
|
||||||
|
status: "uploading",
|
||||||
|
thumbnail: "https://storage.ragestudio.net/comty-static-assets/default_song.png"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "done": {
|
||||||
|
// remove pending file
|
||||||
|
this.setState({
|
||||||
|
pendingUploads: this.state.pendingUploads.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.setState({
|
||||||
|
pendingUploads: this.state.pendingUploads.filter(uid => uid !== 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.handleUpload(change)
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
this.removeTrack(change.file.uid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case "removed": {
|
||||||
|
// remove from file list and if it's pending, remove from pending list
|
||||||
|
this.removeTrack(change.file.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpload = async (req) => {
|
||||||
|
const response = await UploadModel.uploadFile(req.file).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
antd.message.error(error)
|
||||||
|
|
||||||
|
req.onError(error)
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
req.onSuccess(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCanSubmit = () => {
|
||||||
|
const { playlistName, fileList, pendingUploads, trackList } = this.state
|
||||||
|
|
||||||
|
const nameValid = playlistName !== null && playlistName.length !== 0
|
||||||
|
const filesListValid = fileList.length !== 0
|
||||||
|
const isPending = pendingUploads.length !== 0
|
||||||
|
const isTrackListValid = trackList.every((track) => {
|
||||||
|
return track.title !== null && track.title?.length !== 0 && track.source !== null && track.source?.length !== 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return nameValid && filesListValid && !isPending && isTrackListValid
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = async () => {
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const { playlistName, playlistDescription, playlistThumbnail, trackList } = this.state
|
||||||
|
|
||||||
|
let tracksIds = []
|
||||||
|
|
||||||
|
// first, publish the tracks
|
||||||
|
for await (const track of trackList) {
|
||||||
|
console.log(track)
|
||||||
|
|
||||||
|
let trackPublishResponse = null
|
||||||
|
|
||||||
|
if (typeof track._id === "undefined") {
|
||||||
|
console.log(`Track ${track.uid} is not published yet. Publishing it...`)
|
||||||
|
|
||||||
|
trackPublishResponse = await PlaylistModel.publishTrack({
|
||||||
|
title: track.title,
|
||||||
|
thumbnail: track.thumbnail,
|
||||||
|
source: track.source
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
app.message.error(error.response.data.message)
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log(`Track ${track.uid} is already published. Updating...`)
|
||||||
|
|
||||||
|
trackPublishResponse = await PlaylistModel.updateTrack({
|
||||||
|
_id: track._id,
|
||||||
|
title: track.title,
|
||||||
|
thumbnail: track.thumbnail,
|
||||||
|
source: track.source
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
app.message.error(error.response.data.message)
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackPublishResponse) {
|
||||||
|
tracksIds.push(trackPublishResponse._id)
|
||||||
|
|
||||||
|
// update the track id in the track list
|
||||||
|
const trackList = this.state.trackList
|
||||||
|
|
||||||
|
const trackIndex = trackList.findIndex((track) => track.uid === track.uid)
|
||||||
|
|
||||||
|
if (trackIndex !== -1) {
|
||||||
|
trackList[trackIndex]._id = trackPublishResponse._id.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
trackList: trackList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracksIds.length === 0) {
|
||||||
|
antd.message.error("Failed to publish tracks")
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let playlistPublishResponse = null
|
||||||
|
|
||||||
|
if (this.props.query.playlist_id) {
|
||||||
|
console.log(`Playlist ${this.props.query.playlist_id} is already published. Updating...`)
|
||||||
|
|
||||||
|
// update the playlist
|
||||||
|
playlistPublishResponse = await PlaylistModel.updatePlaylist({
|
||||||
|
_id: this.props.query.playlist_id,
|
||||||
|
title: playlistName,
|
||||||
|
description: playlistDescription,
|
||||||
|
thumbnail: playlistThumbnail,
|
||||||
|
list: tracksIds
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
app.message.error(error.response.data.message)
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log(`Playlist is not published yet. Publishing it...`)
|
||||||
|
|
||||||
|
playlistPublishResponse = await PlaylistModel.publish({
|
||||||
|
title: playlistName,
|
||||||
|
description: playlistDescription,
|
||||||
|
thumbnail: playlistThumbnail,
|
||||||
|
list: tracksIds
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
app.message.error(error.response.data.message)
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (playlistPublishResponse) {
|
||||||
|
app.message.success("Playlist published")
|
||||||
|
|
||||||
|
if (typeof this.props.close === "function") {
|
||||||
|
this.props.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeletePlaylist = async () => {
|
||||||
|
const action = async () => {
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteResponse = await PlaylistModel.deletePlaylist(this.props.query.playlist_id).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
antd.message.error(error)
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (deleteResponse) {
|
||||||
|
app.message.success("Playlist deleted")
|
||||||
|
|
||||||
|
if (typeof this.props.close === "function") {
|
||||||
|
this.props.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
antd.Modal.confirm({
|
||||||
|
title: "Delete playlist",
|
||||||
|
content: "Are you sure you want to delete this playlist?",
|
||||||
|
onOk: action
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
__removeExtensionsFromNames = () => {
|
||||||
|
this.setState({
|
||||||
|
trackList: this.state.trackList.map((track) => {
|
||||||
|
track.title = track.title.replace(/\.[^/.]+$/, "")
|
||||||
|
|
||||||
|
return track
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
__removeNumbersFromNames = () => {
|
||||||
|
// remove the order number from the track name ( 01 - trackname.ext => trackname.ext )
|
||||||
|
this.setState({
|
||||||
|
trackList: this.state.trackList.map((track) => {
|
||||||
|
track.title = track.title.replace(/^[0-9]{2} - /, "")
|
||||||
|
|
||||||
|
return track
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData = async (playlist_id) => {
|
||||||
|
const playlist = await PlaylistModel.getPlaylist(playlist_id).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
antd.message.error(error)
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (playlist) {
|
||||||
|
const trackList = playlist.list.map((track) => {
|
||||||
|
return {
|
||||||
|
_id: track._id,
|
||||||
|
uid: track._id,
|
||||||
|
title: track.title,
|
||||||
|
source: track.source,
|
||||||
|
status: "done",
|
||||||
|
thumbnail: track.thumbnail
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
playlistName: playlist.title,
|
||||||
|
playlistDescription: playlist.description,
|
||||||
|
trackList: trackList,
|
||||||
|
fileList: trackList.map((track) => {
|
||||||
|
return {
|
||||||
|
uid: track.uid,
|
||||||
|
name: track.title,
|
||||||
|
status: "done",
|
||||||
|
url: track.source
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
console.log(this.props.query.playlist_id)
|
||||||
|
|
||||||
|
if (this.props.query.playlist_id) {
|
||||||
|
this.loadData(this.props.query.playlist_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
window._hacks = {
|
||||||
|
removeExtensionsFromNames: this.__removeExtensionsFromNames,
|
||||||
|
removeNumbersFromNames: this.__removeNumbersFromNames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window._hacks = null
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div className="playlistCreator">
|
||||||
|
<div className="header">
|
||||||
|
<h1>
|
||||||
|
<Icons.MdOutlineQueueMusic />
|
||||||
|
Creator
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="actions">
|
||||||
|
{
|
||||||
|
this.props.query.playlist_id && <antd.Button
|
||||||
|
type="primary"
|
||||||
|
onClick={this.handleDeletePlaylist}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Delete Playlist
|
||||||
|
</antd.Button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="inputField">
|
||||||
|
<Icons.MdOutlineMusicNote />
|
||||||
|
<antd.Input
|
||||||
|
className="inputText"
|
||||||
|
placeholder="Playlist Title"
|
||||||
|
size="large"
|
||||||
|
bordered={false}
|
||||||
|
onChange={this.handleTitleOnChange}
|
||||||
|
maxLength={120}
|
||||||
|
value={this.state.playlistName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="inputField">
|
||||||
|
<Icons.MdOutlineDescription />
|
||||||
|
<antd.Input
|
||||||
|
className="inputText"
|
||||||
|
placeholder="Description"
|
||||||
|
bordered={false}
|
||||||
|
onChange={this.handleDescriptionOnChange}
|
||||||
|
maxLength={2500}
|
||||||
|
value={this.state.playlistDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="inputField">
|
||||||
|
<Icons.MdImage />
|
||||||
|
{
|
||||||
|
this.state.playlistThumbnail && <div className="coverPreview">
|
||||||
|
<img src={this.state.playlistThumbnail} alt="cover" />
|
||||||
|
|
||||||
|
<antd.Button
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({
|
||||||
|
playlistThumbnail: null
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
icon={<Icons.MdClose />}
|
||||||
|
shape="round"
|
||||||
|
>
|
||||||
|
Remove Cover
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!this.state.playlistThumbnail && <UploadButton
|
||||||
|
onUploadDone={(file) => {
|
||||||
|
this.setState({
|
||||||
|
playlistThumbnail: file.url
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
multiple={false}
|
||||||
|
accept="image/*"
|
||||||
|
icon={<Icons.MdImage />}
|
||||||
|
>
|
||||||
|
Upload cover
|
||||||
|
</UploadButton>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="files">
|
||||||
|
<antd.Upload
|
||||||
|
className="uploader"
|
||||||
|
customRequest={this.handleUpload}
|
||||||
|
onChange={this.handleUploaderOnChange}
|
||||||
|
accept="audio/*"
|
||||||
|
multiple
|
||||||
|
showUploadList={false}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
this.state.fileList.length === 0 ?
|
||||||
|
<UploadHint /> : <antd.Button icon={<Icons.MdCloudUpload />}>
|
||||||
|
Upload files
|
||||||
|
</antd.Button>
|
||||||
|
}
|
||||||
|
</antd.Upload>
|
||||||
|
|
||||||
|
<DragDropContext onDragEnd={this.onDragEnd}>
|
||||||
|
<Droppable droppableId="droppable">
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<div
|
||||||
|
{...provided.droppableProps}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
className="fileList"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
this.state.trackList.map((track, index) => {
|
||||||
|
return <FileListItem
|
||||||
|
index={index}
|
||||||
|
track={track}
|
||||||
|
onClickChangeCover={() => {
|
||||||
|
return this.handleTrackCoverChange(track.uid)
|
||||||
|
}}
|
||||||
|
onTitleChange={(event) => {
|
||||||
|
return this.handleTrackTitleOnChange(event, track.uid)
|
||||||
|
}}
|
||||||
|
onClickRemove={() => {
|
||||||
|
return this.handleTrackRemove(track.uid)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.pendingUploads.length !== 0 && <div className="pendingUploads">
|
||||||
|
<p>
|
||||||
|
<Icons.MdCloudUpload />
|
||||||
|
<span>
|
||||||
|
{this.state.pendingUploads.length} file(s) are being uploaded
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<antd.Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
disabled={!this.checkCanSubmit()}
|
||||||
|
icon={<Icons.MdCampaign />}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onClick={this.handleSubmit}
|
||||||
|
>
|
||||||
|
Publish
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="footer">
|
||||||
|
<p>
|
||||||
|
Uploading files that are not permitted by our <a onClick={() => app.setLocation("/terms")}>Terms of Service</a> may result in your account being suspended.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
281
packages/app/src/pages/music/creator/index.less
Executable file
281
packages/app/src/pages/music/creator/index.less
Executable file
@ -0,0 +1,281 @@
|
|||||||
|
.playlistCreator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputField {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-self: start;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
font-size: 2rem;
|
||||||
|
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
span {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputText {
|
||||||
|
width: 100%;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.coverUploader {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coverPreview {
|
||||||
|
height: 5vh;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.files {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.ant-upload {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-wrapper {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileList {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.fileListItem {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
background-color: var(--background-color-accent);
|
||||||
|
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.uploading {
|
||||||
|
.fileListItem_cover {
|
||||||
|
img {
|
||||||
|
filter: blur(3px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileListItem_loadingIcon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
svg {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileListItem_title_label {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileListItem_cover {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
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: 10px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.fileListItem_title {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileListItem_actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
svg {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileListItem_dragHandle {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,28 @@
|
|||||||
import FeedTab from "./components/feed"
|
import FeedTab from "./components/feed"
|
||||||
import SpacesTabs from "./components/spaces"
|
import SpacesTab from "./components/spaces"
|
||||||
|
import DashboardTab from "./components/dashboard"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
"dashboard": {
|
||||||
|
label: "Dashboard",
|
||||||
|
icon: "MdOutlineDashboard",
|
||||||
|
component: DashboardTab,
|
||||||
|
},
|
||||||
"feed": {
|
"feed": {
|
||||||
title: "Feed",
|
label: "Feed",
|
||||||
icon: "Compass",
|
icon: "Compass",
|
||||||
component: FeedTab
|
component: FeedTab
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
title: "Library",
|
label: "Library",
|
||||||
icon: "MdLibraryMusic",
|
icon: "MdLibraryMusic",
|
||||||
component: FeedTab
|
component: FeedTab,
|
||||||
},
|
disabled: true
|
||||||
"dashboard": {
|
|
||||||
title: "Dashboard",
|
|
||||||
icon: "MdOutlineDashboard",
|
|
||||||
component: FeedTab
|
|
||||||
},
|
},
|
||||||
"spaces": {
|
"spaces": {
|
||||||
title: "Spaces",
|
label: "Spaces",
|
||||||
icon: "MdDeck",
|
icon: "MdDeck",
|
||||||
component: SpacesTabs
|
component: SpacesTab,
|
||||||
|
disabled: true
|
||||||
},
|
},
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user