improve creator

This commit is contained in:
SrGooglo 2023-04-17 21:51:09 +00:00
parent a3f1892e8a
commit 55755364b5
5 changed files with 1265 additions and 740 deletions

View File

@ -0,0 +1,151 @@
import React from "react"
import * as antd from "antd"
import { Icons } from "components/Icons"
import UploadButton from "components/UploadButton"
export default (props) => {
const [playlistName, setPlaylistName] = React.useState(props.playlist.title)
const [playlistDescription, setPlaylistDescription] = React.useState(props.playlist.description)
const [playlistThumbnail, setPlaylistThumbnail] = React.useState(props.playlist.thumbnail)
const [playlistVisibility, setPlaylistVisibility] = React.useState(props.playlist.visibility)
const handleTitleOnChange = (event) => {
setPlaylistName(event.target.value)
props.onTitleChange(event.target.value)
}
const handleDescriptionOnChange = (event) => {
setPlaylistDescription(event.target.value)
props.onDescriptionChange(event.target.value)
}
const handleCoverChange = (file) => {
setPlaylistThumbnail(file.url)
props.onPlaylistCoverChange(file.url)
}
const handleRemoveCover = () => {
setPlaylistThumbnail(null)
props.onPlaylistCoverChange(null)
}
const handleVisibilityChange = (value) => {
setPlaylistVisibility(value)
props.onVisibilityChange(value)
}
return <div className="playlistCreator_layout_row">
<div
className="playlistCreator_layout_column"
>
<div className="field">
<div className="field_header">
<Icons.MdOutlineMusicNote />
<span>Title</span>
</div>
<antd.Input
className="inputText"
placeholder="Playlist Title"
size="large"
bordered={false}
value={playlistName}
onChange={handleTitleOnChange}
maxLength={120}
/>
</div>
<div className="field">
<div className="field_header">
<Icons.MdOutlineDescription />
<span>Description</span>
</div>
<antd.Input.TextArea
className="inputText"
placeholder="Description (Support Markdown)"
bordered={false}
value={playlistDescription}
onChange={handleDescriptionOnChange}
maxLength={2500}
rows={4}
/>
</div>
<antd.Divider />
<div className="field">
<div className="field_header">
<Icons.Eye />
<span>Visibility</span>
</div>
<antd.Select
value={playlistVisibility}
onChange={handleVisibilityChange}
defaultValue={props.playlist.public ? "public" : "private"}
>
<antd.Select.Option value="public">Public</antd.Select.Option>
<antd.Select.Option value="private">Private</antd.Select.Option>
</antd.Select>
</div>
</div>
<div
className="playlistCreator_layout_column"
style={{
width: "50%",
maxWidth: "300px"
}}
>
<div className="field">
<div className="field_header">
<Icons.MdImage />
<span>Cover</span>
</div>
<div className="coverPreview">
<div className="coverPreview_preview">
<img src={playlistThumbnail ?? "/assets/no_song.png"} alt="Thumbnail" />
</div>
<div className="coverPreview_actions">
<UploadButton
onUploadDone={handleCoverChange}
multiple={false}
accept="image/*"
>
Upload cover
</UploadButton>
<antd.Button
onClick={handleRemoveCover}
disabled={!playlistThumbnail}
icon={<Icons.MdClose />}
type="text"
>
Remove Cover
</antd.Button>
</div>
</div>
</div>
<antd.Divider />
<div className="field">
<antd.Button
onClick={props.onDeletePlaylist}
icon={<Icons.MdDelete />}
danger
>
Delete Playlist
</antd.Button>
</div>
</div>
</div>
}

View File

@ -0,0 +1,323 @@
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 "./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 FileItemEditor = (props) => {
const [track, setTrack] = React.useState(props.track ?? {})
const handleChange = (key, value) => {
setTrack((oldData) => {
return {
...oldData,
[key]: value
}
})
}
const onClose = () => {
if (typeof props.close === "function") {
props.close()
}
}
const onSave = async () => {
await props.onSave(track)
if (typeof props.close === "function") {
props.close()
}
}
return <div className="fileItemEditor">
<div className="fileItemEditor_field">
<div className="fileItemEditor_field_header">
<Icons.MdImage />
<span>Thumbnail</span>
</div>
<div className="fileItemEditor_field_thumnail">
<img src={track.thumbnail} />
</div>
<div className="fileItemEditor_actions">
<UploadButton
accept="image/*"
onUploadDone={(file) => handleChange("thumbnail", file.url)}
/>
{
track.thumbnail && <antd.Button
icon={<Icons.MdClose />}
type="text"
onClick={() => handleChange("thumbnail", null)}
>
Remove
</antd.Button>
}
</div>
</div>
<div className="fileItemEditor_field">
<div className="fileItemEditor_field_header">
<Icons.MdOutlineMusicNote />
<span>Title</span>
</div>
<antd.Input
value={track.title}
placeholder="Track title"
onChange={(e) => handleChange("title", e.target.value)}
/>
</div>
<div className="fileItemEditor_field">
<div className="fileItemEditor_field_header">
<Icons.User />
<span>Artist</span>
</div>
<antd.Input
value={track.artist}
placeholder="Artist"
onChange={(e) => handleChange("artist", e.target.value)}
/>
</div>
<div className="fileItemEditor_field">
<div className="fileItemEditor_field_header">
<Icons.MdAlbum />
<span>Album</span>
</div>
<antd.Input
value={track.album}
placeholder="Album"
onChange={(e) => handleChange("album", e.target.value)}
/>
</div>
<div className="fileItemEditor_field">
<div className="fileItemEditor_field_header">
<Icons.MdExplicit />
<span>Explicit</span>
</div>
<antd.Switch
checked={track.explicit}
onChange={(value) => handleChange("explicit", value)}
/>
</div>
<div className="fileItemEditor_actions">
<antd.Button
type="text"
icon={<Icons.MdClose />}
onClick={onClose}
>
Cancel
</antd.Button>
<antd.Button
type="primary"
icon={<Icons.MdCheck />}
onClick={onSave}
>
Save
</antd.Button>
</div>
</div>
}
const FileListItem = (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"
/>
</div>
<div className="fileListItem_details">
<div className="fileListItem_namings">
<h4>
<span
style={{
marginRight: "0.6rem",
fontSize: "0.7rem"
}}
>
{props.index + 1} -
</span>
{
props.track?.title ?? "Unknown title"
}
</h4>
<p
style={{
display: "flex",
alignItems: "center",
gap: "0.6rem",
}}
>
<span>
{
props.track?.artist ?? "Unknown artist"
}
</span>
<span>-</span>
<span>
{
props.track?.album ?? "Unknown album"
}
</span>
</p>
</div>
<div className="fileListItem_actions">
<antd.Button
type="primary"
icon={<Icons.MdEdit />}
onClick={props.onClickEdit}
disabled={isUploading}
/>
<antd.Popconfirm
title="Delete this track?"
description="Are you sure to delete this track?"
onConfirm={props.onClickRemove}
okText="Yes"
cancelText="No"
>
<antd.Button
icon={<Icons.MdDelete />}
/>
</antd.Popconfirm>
</div>
</div>
<div
{...provided.dragHandleProps}
className="fileListItem_dragHandle"
>
<Icons.MdDragIndicator />
</div>
</div>
}}
</Draggable>
}
export default (props) => {
const onClickEditTrack = (track) => {
console.log("Editing track", track)
app.DrawerController.open("track_editor", FileItemEditor, {
type: "drawer",
componentProps: {
track,
onSave: (newTrackData) => {
props.handleTrackInfoChange(newTrackData.uid, newTrackData)
}
},
})
}
return <div className="tracksUploads">
<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 className="uploadBox">
<antd.Upload
className="uploader"
customRequest={props.handleUploadTrack}
onChange={props.onTrackUploaderChange}
accept="audio/*"
multiple
showUploadList={false}
>
{
props.fileList.length === 0 ?
<UploadHint /> : <antd.Button
className="uploadMoreButton"
icon={<Icons.Plus />}
/>
}
</antd.Upload>
<div className="fileList_wrapper">
<DragDropContext onDragEnd={props.handleTrackDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className="fileList"
>
{
props.trackList.map((track, index) => {
return <FileListItem
index={index}
track={track}
onClickChangeCover={() => {
return props.handleTrackCoverChange(track.uid)
}}
onTitleChange={(event) => {
return props.handleTrackInfoChange(track.uid, "title", event.target.value)
}}
onClickRemove={() => {
return props.handleTrackRemove(track.uid)
}}
onClickEdit={() => {
return onClickEditTrack(track)
}}
/>
})
}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
</div>
}

View File

@ -0,0 +1,344 @@
.tracksUploads {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
.uploadBox {
position: relative;
width: 100%;
//height: 100%;
padding: 10px;
overflow-y: hidden;
border: 1px solid var(--border-color);
border-radius: 8px;
.ant-upload {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
.uploadMoreButton {
width: 100%;
height: 30px;
margin-bottom: 10px;
background-color: transparent;
outline: 1px solid var(--border-color);
box-shadow: none;
&:hover {
height: 70px;
background-color: var(--background-color-accent);
}
}
}
.ant-upload-wrapper {
margin-bottom: 10px;
}
.fileList_wrapper {
position: relative;
border-radius: 8px;
overflow: hidden;
// add a blur effect on top and bottom to decorate overflown content
&::before,
&::after {
z-index: 50;
content: "";
position: absolute;
width: 100%;
height: 10px;
}
&::before {
top: 0;
left: 0;
background: linear-gradient(0deg,
rgba(255, 255, 255, 0) 0%,
var(--background-color-primary) 40%);
}
&::after {
bottom: 0;
left: 0;
background: linear-gradient(180deg,
rgba(255, 255, 255, 0) 0%,
var(--background-color-primary) 40%);
}
}
.fileList {
display: flex;
flex-direction: column;
max-height: 60vh;
overflow-y: scroll;
padding: 15px 0;
gap: 10px;
.fileListItem {
position: relative;
z-index: 49;
display: inline-flex;
flex-direction: row;
align-items: center;
padding: 10px;
border-radius: 8px;
background-color: var(--background-color-accent);
color: var(--text-color);
border: 1px solid var(--border-color);
gap: 10px;
&.uploading {
.fileListItem_cover {
img {
filter: blur(3px);
}
pointer-events: none;
}
}
.fileListItem_loadingIcon {
position: absolute;
top: 0;
right: 0;
padding: 10px;
svg {
margin: 0 !important;
}
}
.ant-btn {
svg {
margin: 0 !important;
}
}
.fileListItem_cover {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: all 0.2s ease-in-out;
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: 20px;
width: 100%;
.fileListItem_namings {
width: 100%;
p {
font-size: 0.8rem;
}
}
.fileListItem_artist {
width: 100%;
span {
font-size: 0.8rem;
margin-right: 10px;
}
}
}
.fileListItem_actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.ant-btn {
svg {
margin: 0 !important;
}
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
}
}
.fileListItem_dragHandle {
color: var(--text-color);
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;
}
}
}
.fileItemEditor {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
.fileItemEditor_actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
align-self: center;
gap: 10px;
}
.fileItemEditor_field {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
width: 100%;
.fileItemEditor_field_header {
display: inline-flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
width: 100%;
//margin-bottom: 10px;
h3 {
font-size: 1.2rem;
}
}
.fileItemEditor_field_actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
}
.fileItemEditor_field_thumnail {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
img {
width: 15vw;
height: 15vw;
max-width: 330px;
max-height: 330px;
object-fit: cover;
border-radius: 12px;
background-color: black;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,68 @@
.playlistCreator {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
//justify-content: center;
width: 100%;
width: 50vw;
height: 100%;
min-width: 800px;
transition: all 0.3s ease-in-out;
.stepContent {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 20px 0;
}
.stepActions {
position: sticky;
bottom: 0;
right: 0;
display: flex;
flex-direction: row;
align-items: center;
//justify-content: flex-end;
width: 100%;
padding: 20px;
gap: 10px;
background-color: var(--background-color-accent);
border-radius: 12px;
}
.playlistCreator_layout_row {
display: flex;
flex-direction: row;
width: 100%;
gap: 20px;
.playlistCreator_layout_column {
display: flex;
flex-direction: column;
width: 100%;
gap: 20px;
}
}
.actions {
display: flex;
flex-direction: row;
@ -19,256 +73,80 @@
gap: 20px;
}
.inputField {
.field {
display: inline-flex;
flex-direction: row;
flex-direction: column;
align-self: start;
align-items: center;
justify-content: flex-start;
width: 100%;
margin-bottom: 20px;
font-size: 2rem;
gap: 10px;
color: var(--text-color);
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
color: var(--text-color);
.field_header {
font-size: 1rem;
}
.inputText {
width: 100%;
color: var(--text-color);
}
.coverUploader {
width: 100px;
}
background-color: transparent;
.coverPreview {
height: 5vh;
outline: 1px solid var(--border-color);
}
}
.coverPreview {
display: flex;
flex-direction: column;
gap: 10px;
.coverPreview_preview {
align-self: center;
width: 15vw;
height: 15vw;
max-width: 300px;
max-height: 300px;
img {
height: 100%;
border-radius: 10px;
margin-right: 10px;
}
width: 15vw;
height: 15vw;
svg {
margin: 0 !important;
max-width: 300px;
max-height: 300px;
border-radius: 12px;
object-fit: cover;
}
}
}
.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 {
.coverPreview_actions {
display: flex;
flex-direction: column;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 10px;
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);
}
pointer-events: none;
}
}
.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_cover_mask {
opacity: 0;
backdrop-filter: blur(3px);
}
&:hover {
.fileListItem_cover_mask {
opacity: 1;
}
}
}
.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;
margin: 0 !important;
}
}
.footer {
position: relative;
padding: 10px;
.ant-steps-icon {
svg {
margin: 0 !important;
}
}
}