From 44750cb5061f593a8c8479eb03e1bedec3ce9bb0 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 13 Oct 2022 21:56:40 +0200 Subject: [PATCH] improve creator --- .../app/src/components/PostCreator/index.jsx | 223 +++++++++++++----- .../app/src/components/PostCreator/index.less | 168 ++++++++++++- 2 files changed, 325 insertions(+), 66 deletions(-) diff --git a/packages/app/src/components/PostCreator/index.jsx b/packages/app/src/components/PostCreator/index.jsx index e49c4455..8480d0cc 100644 --- a/packages/app/src/components/PostCreator/index.jsx +++ b/packages/app/src/components/PostCreator/index.jsx @@ -1,46 +1,46 @@ import React from "react" import * as antd from "antd" import classnames from "classnames" +import { DateTime } from "luxon" +import humanSize from "@tsmx/human-readable" import { Icons } from "components/Icons" -import PostAdditions from "components/PostCard/components/additions" import "./index.less" -// TODO: Handle `cntr+v` to paste data from the clipboard to the post additions -// TODO: Fetch `maxMessageLength` value from server API -const maxMessageLength = 512 +const DEFAULT_POST_POLICY = { + maxMessageLength: 512, + acceptedMimeTypes: ["image/gif", "image/png", "image/jpeg", "image/bmp"], + maximumFileSize: 10 * 1024 * 1024, + maximunFilesPerRequest: 10 +} export default (props) => { const api = window.app.api.withEndpoints("main") - const additionsRef = React.useRef(null) + const creatorRef = React.useRef(null) const [pending, setPending] = React.useState([]) const [loading, setLoading] = React.useState(false) const [uploaderVisible, setUploaderVisible] = React.useState(false) const [focused, setFocused] = React.useState(false) - const [postData, setPostData] = React.useState({ - message: "", - additions: [] - }) + const [postMessage, setPostMessage] = React.useState("") + const [postAttachments, setPostAttachments] = React.useState([]) + const [fileList, setFileList] = React.useState([]) - const [uploadPolicy, setUploadPolicy] = React.useState(null) - - const updatePostData = (update) => { - setPostData({ - ...postData, - ...update - }) - } + const [postingPolicy, setPostingPolicy] = React.useState(DEFAULT_POST_POLICY) const cleanPostData = () => { - setPostData({ - message: "", - date: new Date(), - additions: [] - }) + setPostMessage("") + setPostAttachments([]) + setFileList([]) + } + + const fetchUploadPolicy = async () => { + const policy = await api.get.postingPolicy() + + setPostingPolicy(policy) } const submit = () => { @@ -50,7 +50,13 @@ export default (props) => { setUploaderVisible(false) setFocused(false) - const response = api.post.post({ ...postData }).catch(error => { + const payload = { + message: postMessage, + attachments: postAttachments, + timestamp: DateTime.local().toISO(), + } + + const response = api.post.post(payload).catch(error => { console.error(error) antd.message.error(error) @@ -70,13 +76,14 @@ export default (props) => { const onUploadFile = async (req) => { // hide uploader - setUploaderVisible(false) + //setUploaderVisible(false) // get file data const file = req.file // append to form data const formData = new FormData() + formData.append("files", file) // send request @@ -95,48 +102,75 @@ export default (props) => { } const canSubmit = () => { - const messageLengthValid = postData.message.length !== 0 && postData.message.length < maxMessageLength + const messageLengthValid = postMessage.length !== 0 && postMessage.length < postingPolicy.maxMessageLength - return Boolean(messageLengthValid) && Boolean(pending.length === 0) + if (pending.length !== 0) { + return false + } + + if (!messageLengthValid && postAttachments.length === 0) { + return false + } + + return true } - const removeAddition = (file_uid) => { - updatePostData({ - additions: postData.additions.filter(addition => addition.file.uid !== file_uid) - }) + const removeAttachment = (file_uid) => { + setPostAttachments(postAttachments.filter((file) => file.uid !== file_uid)) } - const onDraggerChange = (change) => { - console.log(change) + const addAttachment = (file) => { + if (Array.isArray(file)) { + return setPostAttachments([...postAttachments, ...file]) + } + + return setPostAttachments([...postAttachments, file]) + } + + const uploaderScrollToEnd = () => { + // scroll to max right + const element = document.querySelector(".ant-upload-list-picture-card") + + // calculate the element's width and scroll to the end + const scrollToLeft = element.scrollWidth - element.clientWidth + + if (element) { + element.scrollTo({ + top: 0, + left: scrollToLeft, + behavior: "smooth", + }) + } + } + + const onUploaderChange = (change) => { + setFileList(change.fileList) switch (change.file.status) { case "uploading": { setPending([...pending, change.file.uid]) + + uploaderScrollToEnd() + break } + case "done": { - let additions = postData.additions ?? [] - - console.log(change.file) - - additions.push(...change.file.response) - // remove pending file setPending(pending.filter(uid => uid !== change.file.uid)) // update post data - updatePostData({ additions }) + addAttachment(change.file.response) - // force update additions - if (additionsRef.current) { - additionsRef.current.forceUpdate() - } + uploaderScrollToEnd() break } case "error": { // remove pending file setPending(pending.filter(uid => uid !== change.file.uid)) + + removeAttachment(change.file.uid) } default: { break @@ -150,9 +184,7 @@ export default (props) => { event.target.value = event.target.value.slice(1) } - updatePostData({ - message: event.target.value - }) + setPostMessage(event.target.value) } const toggleUploader = (to) => { @@ -173,8 +205,72 @@ export default (props) => { } } + const handlePaste = ({ clipboardData }) => { + if (clipboardData && clipboardData.items.length > 0) { + const isValidFormat = (fileType) => DEFAULT_ACCEPTED_FILES.includes(fileType) + toggleUploader(true) + + for (let index = 0; index < clipboardData.items.length; index++) { + if (!isValidFormat(clipboardData.items[index].type)) { + throw new Error(`Sorry, that's not a format we support ${clipboardData.items[index].type}`) + } + + let file = clipboardData.items[index].getAsFile() + + app.message.info("Uploading file...") + + file.thumbUrl = URL.createObjectURL(file) + + file.uid = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + + // upload file + onUploadFile({ + file, + onSuccess: (response) => { + setFileList([...fileList, file]) + addAttachment(response) + } + }) + } + } + } + + const renderUploadPreviewItem = (item, file, list, actions) => { + const uploading = file.status === "uploading" + + const onClickDelete = () => { + actions.remove() + removeAttachment(file.uid) + } + + return
+
+ +
+ +
+ { + uploading && + } + { + !uploading && } + onClick={onClickDelete} + /> + } +
+
+ } + React.useEffect(() => { - //fetchUploadPolicy() + fetchUploadPolicy() + + creatorRef.current.addEventListener("paste", handlePaste) + + return () => { + creatorRef.current.removeEventListener("paste", handlePaste) + } }, []) // set loading to true menwhile pending is not empty @@ -184,6 +280,7 @@ export default (props) => { return
{ e.preventDefault() toggleUploader(true) @@ -205,13 +302,13 @@ export default (props) => {
@@ -224,8 +321,6 @@ export default (props) => {
- {postData.additions.length > 0 && } -
{
- -

Click or drag file to this area to upload

-
+
+ + Add attachment + Max {humanSize.fromBytes(postingPolicy.maximumFileSize)} +
+
} \ No newline at end of file diff --git a/packages/app/src/components/PostCreator/index.less b/packages/app/src/components/PostCreator/index.less index 2def9fe5..81348ebe 100644 --- a/packages/app/src/components/PostCreator/index.less +++ b/packages/app/src/components/PostCreator/index.less @@ -40,15 +40,18 @@ .uploader { display: flex; flex-direction: row; - justify-content: flex-start; - height: 5vh; width: 100%; overflow: hidden; transition: all 150ms ease-in-out; + border: 1px solid var(--border-color); + padding: 10px; + + opacity: 1; + >div { margin-left: 10px; font-size: 1rem; @@ -59,15 +62,168 @@ } &.hided { - height: 0; + height: 0px; + opacity: 0; } - span { - width: 100%; - } .ant-upload { + span { + width: fit-content !important; + } + + .ant-upload-select-picture-card { + background-color: transparent !important; + } + } + + .ant-upload.ant-upload-select-picture-card { + display: flex; + align-self: center; + justify-content: center; + + width: 10vw; + height: 10vw; + + margin-bottom: 0; + + border-color: var(--border-color); + background-color: transparent !important; + + } + + .ant-upload-list-picture-card .ant-upload-list-item-uploading.ant-upload-list-item { + background-color: transparent !important; + } + + .ant-upload-list-item-actions { + display: flex; + align-items: center; + justify-content: center; + + height: 100%; width: 100%; + + font-size: 2rem; + } + + .ant-upload-list { + display: flex; + flex-direction: row; + + width: 100%; + + overflow-x: auto; + overflow-y: hidden; + + white-space: nowrap; + + align-items: center; + justify-content: flex-start; + + border-radius: 10px; + } + + .ant-upload-list-picture-card-container { + width: 10vw; + height: 10vw; + } + + .hint { + + display: flex; + flex-direction: column; + + align-items: center; + justify-content: center; + align-self: center; + + width: 10vw; + height: 10vw; + + text-align: center; + + background-color: transparent; + + font-size: 0.8rem; + + padding: 10px; + + svg { + font-size: 1.5rem; + margin-bottom: 6px; + margin-right: 0 !important; + } + } + + .file { + position: relative; + + width: 10vw; + height: 10vw; + + font-size: 2rem; + + transition: all 150ms ease-in-out; + + border-radius: 10px; + + &.uploading { + img { + opacity: 0.5; + } + + .actions { + backdrop-filter: blur(5px); + opacity: 1; + } + } + + &:hover { + .actions { + opacity: 1; + backdrop-filter: blur(2px); + } + + .preview { + opacity: 0.5; + } + } + + .preview { + position: absolute; + top: 0; + left: 0; + + img { + width: 100%; + height: 100%; + + transition: all 150ms ease-in-out; + border-radius: 10px; + } + } + + .actions { + display: flex; + flex-direction: column; + + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; + + transition: all 150ms ease-in-out; + + backdrop-filter: blur(0); + + opacity: 0; + + svg { + margin: 0; + } + } } }