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 clipboardEventFileToFile from "utils/clipboardEventFileToFile" import PostModel from "models/post" import "./index.less" const DEFAULT_POST_POLICY = { maxMessageLength: 512, acceptedMimeTypes: ["image/gif", "image/png", "image/jpeg", "image/bmp"], maximumFileSize: 10 * 1024 * 1024, maximunFilesPerRequest: 10 } export default class PostCreator extends React.Component { state = { pending: [], loading: false, uploaderVisible: false, postMessage: "", postAttachments: [], fileList: [], postingPolicy: DEFAULT_POST_POLICY } creatorRef = React.createRef() cleanPostData = () => { this.setState({ postMessage: "", postAttachments: [], fileList: [] }) } toogleUploaderVisibility = (to) => { to = to ?? !this.state.uploaderVisible if (to === this.state.uploaderVisible) { return } this.setState({ uploaderVisible: to }) } fetchUploadPolicy = async () => { const policy = await PostModel.getPostingPolicy() this.setState({ postingPolicy: policy }) } canSubmit = () => { const { postMessage, postAttachments, pending, postingPolicy } = this.state const messageLengthValid = postMessage.length !== 0 && postMessage.length < postingPolicy.maxMessageLength if (pending.length !== 0) { return false } if (!messageLengthValid && postAttachments.length === 0) { return false } return true } submit = async () => { if (!this.canSubmit()) return this.setState({ loading: true, uploaderVisible: false }) const { postMessage, postAttachments } = this.state const payload = { message: postMessage, attachments: postAttachments, timestamp: DateTime.local().toISO(), } const response = await PostModel.create(payload).catch(error => { console.error(error) antd.message.error(error) return false }) this.setState({ loading: false }) if (response) { this.cleanPostData() if (typeof this.props.onPost === "function") { this.props.onPost() } } } uploadFile = async (req) => { // hide uploader this.toogleUploaderVisibility(false) const request = await app.cores.remoteStorage.uploadFile(req.file) .catch(error => { console.error(error) antd.message.error(error) req.onError(error) return false }) if (request) { console.log(`Upload done >`, request) return req.onSuccess(request) } } removeAttachment = (file_uid) => { this.setState({ postAttachments: this.state.postAttachments.filter((file) => file.uid !== file_uid), fileList: this.state.fileList.filter((file) => file.uid !== file_uid) }) } addAttachment = (file) => { if (Array.isArray(file)) { return this.setState({ postAttachments: [...this.state.postAttachments, ...file] }) } return this.setState({ postAttachments: [...this.state.postAttachments, file] }) } 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", }) } } onUploaderChange = (change) => { if (this.state.fileList !== change.fileList) { this.setState({ fileList: change.fileList }) } console.log(change) switch (change.file.status) { case "uploading": { this.toogleUploaderVisibility(false) this.setState({ pending: [...this.state.pending, change.file.uid] }) this.uploaderScrollToEnd() break } case "done": { // remove pending file this.setState({ pending: this.state.pending.filter(uid => uid !== change.file.uid) }) if (Array.isArray(change.file.response.files)) { change.file.response.files.forEach((file) => { this.addAttachment(file) }) } else { this.addAttachment(change.file.response) } // scroll to end this.uploaderScrollToEnd() break } case "error": { // remove pending file this.setState({ pending: this.state.pending.filter(uid => uid !== change.file.uid) }) // remove file from list this.removeAttachment(change.file.uid) } default: { break } } } onChangeMessageInput = (event) => { // if the fist character is a space or a whitespace remove it if (event.target.value[0] === " " || event.target.value[0] === "\n") { event.target.value = event.target.value.slice(1) } this.setState({ postMessage: event.target.value }) } handleKeyDown = (e) => { // detect if the user pressed `enter` key and submit the form, but only if the `shift` key is not pressed if (e.keyCode === 13 && !e.shiftKey) { e.preventDefault() e.stopPropagation() this.submit() } } updateFileList = (uid, newValue) => { let updatedFileList = this.state.fileList // find the file in the list const index = updatedFileList.findIndex(file => file.uid === uid) // update the file updatedFileList[index] = newValue // update the state this.setState({ fileList: updatedFileList }) return updatedFileList } handleManualUpload = async (file) => { if (!file) { throw new Error(`No file provided`) } const isValidFormat = (fileType) => { return this.state.postingPolicy.acceptedMimeTypes.includes(fileType) } if (!isValidFormat(file.type)) { throw new Error(`Invalid file format`) } file.thumbUrl = URL.createObjectURL(file) file.uid = `${file.name}-${Math.random() * 1000}` file.status = "uploading" // add file to the uploader this.onUploaderChange({ file, fileList: [...this.state.fileList, file], }) // upload the file await this.uploadFile({ file, onSuccess: (response) => { file.status = "done" file.response = response this.onUploaderChange({ file: file, fileList: this.updateFileList(file.uid, file) }) }, onError: (error) => { file.status = "error" file.error = error this.onUploaderChange({ file: file, fileList: this.updateFileList(file.uid, file) }) } }) return file } handlePaste = async ({ clipboardData }) => { if (clipboardData && clipboardData.items.length > 0) { // check if the clipboard contains a file const hasFile = Array.from(clipboardData.items).some(item => item.kind === "file") if (!hasFile) { return false } for (let index = 0; index < clipboardData.items.length; index++) { const item = clipboardData.items[index] let file = await clipboardEventFileToFile(item).catch((error) => { console.error(error) app.message.error(`Failed to upload file:`, error.message) return false }) this.handleManualUpload(file).catch((error) => { console.error(error) return false }) } } } renderUploadPreviewItem = (item, file, list, actions) => { const uploading = file.status === "uploading" const onClickDelete = () => { this.removeAttachment(file.uid) } return