added music feed & creator

This commit is contained in:
SrGooglo 2023-02-24 14:40:25 +00:00
parent 5f27846c4e
commit 6fd8670ee7
8 changed files with 1320 additions and 65 deletions

View File

@ -15,10 +15,6 @@ export default class MusicDashboard extends React.Component {
primaryPanelRef = React.createRef()
componentDidMount() {
app.eventBus.emit("style.compactMode", false)
}
renderActiveTab() {
const tab = Tabs[this.state.activeTab]
@ -68,18 +64,17 @@ export default class MusicDashboard extends React.Component {
selectedKeys={[this.state.activeTab]}
activeKey={this.state.activeTab}
onClick={({ key }) => this.handleTabChange(key)}
>
{Object.keys(Tabs).map((key) => {
items={Object.keys(Tabs).map((key) => {
const tab = Tabs[key]
return <antd.Menu.Item
key={key}
icon={createIconRender(tab.icon)}
>
{tab.title}
</antd.Menu.Item>
return {
key,
icon: createIconRender(tab.icon),
label: tab.label,
disabled: tab.disabled
}
})}
</antd.Menu>
/>
</div>
</div>
</div>

View 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>
}

View 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;
}
}
}
}
}
}

View File

@ -2,6 +2,7 @@ import React from "react"
import * as antd from "antd"
import { ImageViewer } from "components"
import { Icons } from "components/Icons"
import { Translation } from "react-i18next"
import FeedModel from "models/feed"
@ -18,6 +19,14 @@ const PlaylistItem = (props) => {
return app.setLocation(`/play/${playlist._id}`)
}
const onClickPlay = (e) => {
e.stopPropagation()
console.log(playlist.list)
app.cores.player.startPlaylist(playlist.list)
}
return <div
id={playlist._id}
key={props.key}
@ -25,7 +34,7 @@ const PlaylistItem = (props) => {
onClick={onClick}
>
<div className="playlistItem_cover">
<ImageViewer src={playlist.cover ?? "/assets/no_song.png"} />
<ImageViewer src={playlist.thumbnail ?? "/assets/no_song.png"} />
</div>
<div className="playlistItem_info">
<div className="playlistItem_info_title">
@ -35,6 +44,14 @@ const PlaylistItem = (props) => {
{playlist.user.username}
</div>
</div>
<div className="playlistItem_actions">
<antd.Button
icon={<Icons.Play />}
type="primary"
shape="circle"
onClick={onClickPlay}
/>
</div>
</div>
}
@ -74,23 +91,12 @@ export default () => {
return <div className="playlistExplorer">
<div className="playlistExplorer_section">
<div className="playlistExplorer_section_header">
<h1><Icons.Compass /> Releases from your artist</h1>
</div>
<div className="playlistExplorer_section_list">
{
list.map((playlist, index) => {
return <PlaylistItem
key={index}
playlist={playlist}
/>
})
}
</div>
</div>
<div className="playlistExplorer_section">
<div className="playlistExplorer_section_header">
<h1><Icons.MdOutlineTrendingUp /> Discover new trends</h1>
<h1>
<Icons.MdOutlineMarkunreadMailbox />
<Translation>
{(t) => t("Releases from your artists")}
</Translation>
</h1>
</div>
<div className="playlistExplorer_section_list">
{

View File

@ -22,18 +22,11 @@
.playlistExplorer_section_list {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
//flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
overflow-x: scroll;
padding: 10px 0;
>.playlistItem {
margin-right: 20px;
}
}
}
}
@ -41,36 +34,58 @@
.playlistItem {
position: relative;
display: flex;
flex-direction: column;
flex-direction: row;
cursor: pointer;
width: 200px;
min-width: 200px;
width: 45%;
height: 10vh;
border-radius: 12px;
transition: all 0.2s ease-in-out;
&:hover {
transform: translate(0, -5px);
outline: 2px solid var(--background-color-accent);
}
margin-right: 20px;
background-color: var(--background-color-accent);
border-radius: 8px;
.playlistItem_cover {
width: 100%;
height: 100%;
margin-bottom: 20px;
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 {
width: 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;
border-radius: 8px;
}
@ -104,4 +119,21 @@
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;
}
}
}
}

View 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>
}
}

View 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;
}
}

View File

@ -1,25 +1,28 @@
import FeedTab from "./components/feed"
import SpacesTabs from "./components/spaces"
import SpacesTab from "./components/spaces"
import DashboardTab from "./components/dashboard"
export default {
"dashboard": {
label: "Dashboard",
icon: "MdOutlineDashboard",
component: DashboardTab,
},
"feed": {
title: "Feed",
label: "Feed",
icon: "Compass",
component: FeedTab
},
"library": {
title: "Library",
label: "Library",
icon: "MdLibraryMusic",
component: FeedTab
},
"dashboard": {
title: "Dashboard",
icon: "MdOutlineDashboard",
component: FeedTab
component: FeedTab,
disabled: true
},
"spaces": {
title: "Spaces",
label: "Spaces",
icon: "MdDeck",
component: SpacesTabs
component: SpacesTab,
disabled: true
},
}