rewrite component & support additions uploads

This commit is contained in:
srgooglo 2022-06-06 16:09:54 +02:00
parent a0f641957f
commit 49999e8acb
2 changed files with 379 additions and 95 deletions

View File

@ -3,96 +3,196 @@ import * as antd from "antd"
import { Icons } from "components/Icons" import { Icons } from "components/Icons"
import { User } from "models" import { User } from "models"
import classnames from "classnames" import classnames from "classnames"
import { PostAdditions } from "components/PostCard"
import "./index.less" import "./index.less"
// TODO: Fetch `maxMessageLength` value from server API
const maxMessageLength = 512 const maxMessageLength = 512
const PostCreatorInput = (props) => { export default (props) => {
const [value, setValue] = React.useState("") const api = window.app.request
const canPublish = () => { const [loading, setLoading] = React.useState(false)
return value.length !== 0 && value.length < maxMessageLength const [uploaderVisible, setUploaderVisible] = React.useState(false)
} const [focused, setFocused] = React.useState(false)
const onChange = (e) => { const [userData, setUserData] = React.useState(null)
setValue(e.target.value) const [postData, setPostData] = React.useState({
} message: "",
additions: []
})
const handleSubmit = () => { const updatePostData = (update) => {
if (canPublish()) { setPostData({
if (typeof props.onSubmit === "function") { ...postData,
props.onSubmit(value) ...update
}
setValue("")
}
}
return <div className="textInput">
<div className="avatar">
<img src={props.user?.avatar} />
</div>
<antd.Input.TextArea
//className={classnames("textArea", { ["active"]: canPublish() })}
disabled={props.loading}
value={value}
onPressEnter={handleSubmit}
autoSize={{ minRows: 3, maxRows: 6 }}
dragable="false"
placeholder="What are you thinking?"
onChange={onChange}
allowClear
rows={8}
maxLength={maxMessageLength}
/>
<div>
<antd.Button
type="primary"
disabled={props.loading || !canPublish()}
onClick={handleSubmit}
icon={props.loading ? <Icons.LoadingOutlined spin /> : <Icons.Send />}
/>
</div>
</div>
}
export default class PostCreator extends React.Component {
state = {
loading: false,
}
api = window.app.request
componentDidMount = async () => {
const userData = await User.data()
this.setState({
userData
}) })
} }
onSubmit = async (value) => { const cleanPostData = () => {
await this.setState({ loading: true }) setPostData({
message: "",
additions: []
})
}
const result = this.api.put.post({ const submit = () => {
message: value, setLoading(true)
}).catch(error => {
const response = api.put.post({ ...postData }).catch(error => {
console.error(error) console.error(error)
antd.message.error(error) antd.message.error(error)
return false return false
}) })
this.setState({ loading: false }) setLoading(false)
if (response) {
cleanPostData()
}
} }
render() { const onUploadFile = async (req) => {
return <div className="postCreator"> // get file data
<PostCreatorInput const file = req.file
user={this.state.userData}
loading={this.state.loading} // append to form data
onSubmit={this.onSubmit} const formData = new FormData()
/> formData.append("files", file)
</div>
setLoading(true)
// send request
const request = await api.post.upload(formData, undefined).catch((error) => {
console.error(error)
antd.message.error(error)
req.onError(error)
return false
})
setLoading(false)
if (request) {
return req.onSuccess(request)
}
} }
const canPublish = () => {
const messageLengthValid = postData.message.length !== 0 && postData.message.length < maxMessageLength
return Boolean(messageLengthValid)
}
const onDraggerChange = (change) => {
console.log(change)
switch (change.file.status) {
case "done": {
let additions = postData.additions ?? []
additions.push(...change.file.response)
return updatePostData({ additions })
}
default: {
break
}
}
}
const onChangeMessageInput = (event) => {
console.log(event.target.value)
updatePostData({
message: event.target.value
})
}
const toggleUploader = (to) => {
setUploaderVisible(to ?? !uploaderVisible)
}
const toggleFocus = (to) => {
setFocused(to ?? !focused)
}
React.useEffect(() => {
User.data().then(user => {
setUserData(user)
})
}, [])
return <div
className="postCreator"
onDragOver={(e) => {
e.preventDefault()
toggleUploader(true)
}}
onDragLeave={(e) => {
e.preventDefault()
toggleUploader(false)
}}
onMouseEnter={() => {
toggleFocus(true)
}}
onMouseLeave={() => {
toggleFocus(false)
}}
>
<div className="textInput">
<div className="avatar">
<img src={userData?.avatar} />
</div>
<antd.Input.TextArea
disabled={loading}
value={postData.message}
onPressEnter={submit}
autoSize={{ minRows: 3, maxRows: 6 }}
dragable="false"
placeholder="What are you thinking?"
onChange={onChangeMessageInput}
allowClear
rows={8}
maxLength={maxMessageLength}
/>
<div>
<antd.Button
type="primary"
disabled={loading || !canPublish()}
onClick={submit}
icon={loading ? <Icons.LoadingOutlined spin /> : <Icons.Send />}
/>
</div>
</div>
{postData.additions.length > 0 && <PostAdditions additions={postData.additions} />}
<div className={classnames("actions", { ["hided"]: !focused && !uploaderVisible })}>
<div>
<antd.Button
type={uploaderVisible ? "default" : "primary"}
disabled={loading}
onClick={() => {
toggleUploader()
}}
icon={<Icons.Upload />}
/>
</div>
</div>
<div className={classnames("uploader", { ["hided"]: !uploaderVisible })}>
<antd.Upload.Dragger
multiple={true}
onChange={onDraggerChange}
customRequest={onUploadFile}
>
<p >Click or drag file to this area to upload</p>
</antd.Upload.Dragger>
</div>
</div>
} }

View File

@ -1,15 +1,199 @@
.postCreator { .postCreator {
width : 100%; display: flex;
padding : 15px; flex-direction: column;
width: 100%;
padding: 15px;
background-color: var(--background-color-accent); background-color: var(--background-color-accent);
max-width : 600px; max-width: 600px;
border-radius: 7px; border-radius: 7px;
.additions {
margin: 10px 0;
width: 100%;
height: 28vh;
.slick-slider {
.slick-prev {
display: inline !important;
color: #ffffff;
z-index: 100;
top: 0;
left: 0;
height: 100%;
width: 5vw;
opacity: 0;
transition: all 150ms ease-in-out;
&:hover {
opacity: 0.6;
width: 7vw;
}
&:active {
transform: scale(0.5);
}
}
.slick-next {
display: inline !important;
color: #ffffff;
z-index: 100;
top: 0;
right: 0;
height: 100%;
width: 5vw;
opacity: 0;
transition: all 150ms ease-in-out;
&:hover {
opacity: 0.6;
width: 7vw;
}
&:active {
transform: scale(0.5);
}
}
}
.slick-track {
display: flex;
align-items: center;
}
.slick-slide {
height: 100%;
div {
height: 100%;
}
}
.slick-list {
border-radius: 8px;
}
.ant-carousel,
.slick-slider,
.slick-list,
.slick-track {
height: 100%;
}
.addition {
width: 100%;
height: 100%;
// fixtures for media content
img {
width: 100%;
height: 100%;
user-select: none;
-webkit-user-drag: none;
object-fit: cover;
}
video {
width: 100%;
height: 100%;
user-select: none;
-webkit-user-drag: none;
object-fit: cover;
}
audio {
width: 100%;
height: 100%;
user-select: none;
-webkit-user-drag: none;
}
>div {
height: 100%;
}
}
}
.actions {
display: inline-flex;
flex-direction: row;
justify-content: flex-start;
height: 40px;
overflow: hidden;
transition: all 150ms ease-in-out;
>div {
margin-left: 10px;
font-size: 1rem;
svg {
margin: 0 !important;
}
}
&.hided {
height: 0;
}
}
.uploader {
display: flex;
flex-direction: row;
justify-content: flex-start;
height: 5vh;
width: 100%;
overflow: hidden;
transition: all 150ms ease-in-out;
>div {
margin-left: 10px;
font-size: 1rem;
svg {
margin: 0 !important;
}
}
&.hided {
height: 0;
}
span {
width: 100%;
}
.ant-upload {
width: 100%;
}
}
.textInput { .textInput {
display : flex; display: flex;
width : 100%; width: 100%;
transition : height 150ms ease-in-out; transition: height 150ms ease-in-out;
background-color: var(--background-color-accent); background-color: var(--background-color-accent);
svg { svg {
@ -17,14 +201,14 @@
} }
.avatar { .avatar {
width : fit-content; width: fit-content;
height: 45px; height: 45px;
display: flex; display: flex;
img { img {
width : 45px; width: 45px;
height : 45px; height: 45px;
border-radius: 12px; border-radius: 12px;
} }
} }
@ -35,7 +219,7 @@
.textArea { .textArea {
border-radius: 8px !important; border-radius: 8px !important;
transition : all 150ms ease-in-out !important; transition: all 150ms ease-in-out !important;
&.active { &.active {
background-color: var(--background-color-primary); background-color: var(--background-color-primary);
@ -43,27 +227,27 @@
} }
.ant-btn-primary { .ant-btn-primary {
z-index : 10; z-index: 10;
position : relative; position: relative;
border-radius : 0 10px 10px 0; border-radius: 0 10px 10px 0;
height : 100%; height: 100%;
vertical-align: bottom; vertical-align: bottom;
border : none; border: none;
box-shadow : none; box-shadow: none;
} }
.ant-input { .ant-input {
background-color: var(--background-color-accent); background-color: var(--background-color-accent);
z-index : 10; z-index: 10;
position : relative; position: relative;
border-color : transparent !important; border-color: transparent !important;
box-shadow : none; box-shadow: none;
border-radius: 3px 0 0; border-radius: 3px 0 0;
height : 100%; height: 100%;
padding : 5px 10px; padding: 5px 10px;
transition : height 150ms linear; transition: height 150ms linear;
width : 100%; width: 100%;
} }
.ant-btn-primary[disabled] { .ant-btn-primary[disabled] {