import React, {Component} from 'react'; import { Dialog, DialogContent, DialogActions, withStyles, Button, CardHeader, Avatar, TextField, Toolbar, IconButton, Fade, Typography, Tooltip, Menu, MenuItem, GridList, ListSubheader, GridListTile } from '@material-ui/core'; import {parse as parseParams, ParsedQuery} from 'query-string'; import {styles} from './Compose.styles'; import { UAccount } from '../types/Account'; import { Visibility } from '../types/Visibility'; import CameraAltIcon from '@material-ui/icons/CameraAlt'; import TagFacesIcon from '@material-ui/icons/TagFaces'; import HowToVoteIcon from '@material-ui/icons/HowToVote'; import VisibilityIcon from '@material-ui/icons/Visibility'; import WarningIcon from '@material-ui/icons/Warning'; import Mastodon from 'megalodon'; import {withSnackbar} from 'notistack'; import { Attachment } from '../types/Attachment'; import { PollWizard } from '../types/Poll'; import filedialog from 'file-dialog'; import ComposeMediaAttachment from '../components/ComposeMediaAttachment'; import EmojiPicker from '../components/EmojiPicker'; interface IComposerState { account: UAccount; visibility: Visibility; sensitive: boolean; sensitiveText?: string; visibilityMenu: boolean; text: string; remainingChars: number; reply?: string; acct?: string; attachments?: [Attachment]; poll?: PollWizard; showEmojis: boolean; } class Composer extends Component { client: Mastodon; constructor(props: any) { super(props); this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') + "/api/v1"); this.state = { account: JSON.parse(localStorage.getItem('account') as string), visibility: "public", sensitive: false, visibilityMenu: false, text: '', remainingChars: 500, showEmojis: false } } componentDidMount() { let state = this.getComposerParams(this.props); let text = state.acct? `@${state.acct}: `: ''; this.setState({ reply: state.reply, acct: state.acct, visibility: state.visibility, text, remainingChars: 500 - text.length }) } componentWillReceiveProps(props: any) { let state = this.getComposerParams(props); let text = state.acct? `@${state.acct}: `: ''; this.setState({ reply: state.reply, acct: state.acct, visibility: state.visibility, text, remainingChars: 500 - text.length }) } checkComposerParams(location?: string): ParsedQuery { let params = ""; if (location !== undefined && typeof(location) === "string") { params = location.replace("#/compose", ""); } else { params = window.location.hash.replace("#/compose", ""); } return parseParams(params); } getComposerParams(props: any) { let params = this.checkComposerParams(props.location); let reply: string = ""; let acct: string = ""; let visibility: Visibility = "public"; if (params.reply) { reply = params.reply.toString(); } if (params.acct) { acct = params.acct.toString(); } if (params.visibility) { visibility = params.visibility.toString() as Visibility; } return { reply, acct, visibility } } updateTextFromField(text: string) { this.setState({ text, remainingChars: 500 - text.length }); } updateWarningFromField(sensitiveText: string) { this.setState({ sensitiveText }); } changeVisibility(visibility: Visibility) { this.setState({ visibility }); } uploadMedia() { filedialog({ multiple: false, accept: "image/*, video/*" }).then((media: FileList) => { let mediaForm = new FormData(); mediaForm.append('file', media[0]); const uploading = this.props.enqueueSnackbar("Uploading media...", { persist: true }) this.client.post('/media', mediaForm).then((resp: any) => { let attachment: Attachment = resp.data; let attachments = this.state.attachments; if (attachments) { attachments.push(attachment); } else { attachments = [attachment]; } this.setState({ attachments }); this.props.closeSnackbar(uploading); this.props.enqueueSnackbar('Media uploaded.'); }).catch((err: Error) => { this.props.enqueueSnackbar("Couldn't upload media: " + err.name); }) }).catch((err: Error) => { this.props.enqueueSnackbar("Couldn't get media: " + err.name); console.error(err.message); }); } getOnlyMediaIds() { let ids: string[] = []; if (this.state.attachments) { this.state.attachments.map((attachment: Attachment) => { ids.push(attachment.id); }); } return ids; } fetchAttachmentAfterUpdate(attachment: Attachment) { let attachments = this.state.attachments; if (attachments) { attachments.forEach((attach: Attachment) => { if (attach.id === attachment.id && attachments) { attachments[attachments.indexOf(attach)] = attachment; } }) this.setState({ attachments }); } } deleteMediaAttachment(attachment: Attachment) { let attachments = this.state.attachments; if (attachments) { attachments.forEach((attach: Attachment) => { if (attach.id === attachment.id && attachments) { attachments.splice(attachments.indexOf(attach), 1); } this.setState({ attachments }); }) this.props.enqueueSnackbar("Attachment removed."); } } insertEmoji(e: any) { if (e.custom) { let text = this.state.text + e.colons this.setState({ text, remainingChars: 500 - text.length }); } else { let text = this.state.text + e.native this.setState({ text, remainingChars: 500 - text.length }); } } post() { this.client.post('/statuses', { status: this.state.text, media_ids: this.getOnlyMediaIds(), visibility: this.state.visibility, sensitive: this.state.sensitive, spoiler_text: this.state.sensitiveText, in_reply_to_id: this.state.reply }).then(() => { this.props.enqueueSnackbar('Posted!'); window.history.back(); }).catch((err: Error) => { this.props.enqueueSnackbar("Couldn't post: " + err.name); console.log(err.message); }) } toggleSensitive() { this.setState({ sensitive: !this.state.sensitive }); } toggleVisibilityMenu() { this.setState({ visibilityMenu: !this.state.visibilityMenu }); } toggleEmojis() { this.setState({ showEmojis: !this.state.showEmojis }); } render() { const {classes} = this.props; return ( window.history.back()}> } title={`${this.state.account.display_name} (@${this.state.account.acct})`} subheader={this.state.visibility.charAt(0).toUpperCase() + this.state.visibility.substr(1)} /> { this.state.sensitive? this.updateWarningFromField(event.target.value)} > : null } { this.state.visibility === "direct"? Don't forget to add the usernames of the accounts you want to message in your post. : null } this.updateTextFromField(event.target.value)} inputProps = { { maxLength: 500 } } value={this.state.text} /> {`${this.state.remainingChars} character${this.state.remainingChars === 1? '': 's'} remaining`} { this.state.attachments && this.state.attachments.length > 0?
Attachments { this.state.attachments.map((attachment: Attachment) => { let c = this.fetchAttachmentAfterUpdate(attachment)} onDeleteCallback={(attachment: Attachment) => this.deleteMediaAttachment(attachment)} />; return (c); }) }
: null }
this.uploadMedia()} id="compose-media"> this.toggleEmojis()} className={classes.desktopOnly}> this.toggleEmojis()} className={classes.composeEmoji} > this.insertEmoji(emoji)}/> 0} id="compose-poll"> this.toggleVisibilityMenu()}> this.toggleSensitive()} id="compose-warning"> this.toggleVisibilityMenu()}> this.changeVisibility('direct')}>Direct (direct message) this.changeVisibility('private')}>Private (followers only) this.changeVisibility('unlisted')}>Unlisted this.changeVisibility('public')}>Public
) } } export default withStyles(styles)(withSnackbar(Composer));