// Package imports. import PropTypes from 'prop-types'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage, defineMessages, } from 'react-intl'; import spring from 'react-motion/lib/spring'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; import TextIconButton from 'flavours/glitch/components/text_icon_button'; import Dropdown from './dropdown'; // Utils. import Motion from 'flavours/glitch/util/optional_motion'; import { assignHandlers, hiddenComponent, } from 'flavours/glitch/util/react_helpers'; // Messages. const messages = defineMessages({ advanced_options_icon_title: { defaultMessage: 'Advanced options', id: 'advanced_options.icon_title', }, attach: { defaultMessage: 'Attach...', id: 'compose.attach', }, change_privacy: { defaultMessage: 'Adjust status privacy', id: 'privacy.change', }, direct_long: { defaultMessage: 'Post to mentioned users only', id: 'privacy.direct.long', }, direct_short: { defaultMessage: 'Direct', id: 'privacy.direct.short', }, doodle: { defaultMessage: 'Draw something', id: 'compose.attach.doodle', }, local_only_long: { defaultMessage: 'Do not post to other instances', id: 'advanced_options.local-only.long', }, local_only_short: { defaultMessage: 'Local-only', id: 'advanced_options.local-only.short', }, private_long: { defaultMessage: 'Post to followers only', id: 'privacy.private.long', }, private_short: { defaultMessage: 'Followers-only', id: 'privacy.private.short', }, public_long: { defaultMessage: 'Post to public timelines', id: 'privacy.public.long', }, public_short: { defaultMessage: 'Public', id: 'privacy.public.short', }, sensitive: { defaultMessage: 'Mark media as sensitive', id: 'compose_form.sensitive', }, spoiler: { defaultMessage: 'Hide text behind warning', id: 'compose_form.spoiler', }, threaded_mode_long: { defaultMessage: 'Automatically opens a reply on posting', id: 'advanced_options.threaded_mode.long', }, threaded_mode_short: { defaultMessage: 'Threaded mode', id: 'advanced_options.threaded_mode.short', }, unlisted_long: { defaultMessage: 'Do not show in public timelines', id: 'privacy.unlisted.long', }, unlisted_short: { defaultMessage: 'Unlisted', id: 'privacy.unlisted.short', }, upload: { defaultMessage: 'Upload a file', id: 'compose.attach.upload', }, }); // Handlers. const handlers = { // Handles file selection. handleChangeFiles ({ target: { files } }) { const { onUpload } = this.props; if (files.length && onUpload) { onUpload(files); } }, // Handles attachment clicks. handleClickAttach (name) { const { fileElement } = this; const { onDoodleOpen } = this.props; // We switch over the name of the option. switch (name) { case 'upload': if (fileElement) { fileElement.click(); } return; case 'doodle': if (onDoodleOpen) { onDoodleOpen(); } return; } }, // Handles a ref to the file input. handleRefFileElement (fileElement) { this.fileElement = fileElement; }, }; // The component. export default class ComposerOptions extends React.PureComponent { // Constructor. constructor (props) { super(props); assignHandlers(this, handlers); // Instance variables. this.fileElement = null; } // Rendering. render () { const { handleChangeFiles, handleClickAttach, handleRefFileElement, } = this.handlers; const { acceptContentTypes, advancedOptions, disabled, full, hasMedia, intl, onChangeAdvancedOption, onChangeSensitivity, onChangeVisibility, onModalClose, onModalOpen, onToggleSpoiler, privacy, resetFileKey, sensitive, spoiler, } = this.props; // We predefine our privacy items so that we can easily pick the // dropdown icon later. const privacyItems = { direct: { icon: 'envelope', meta: <FormattedMessage {...messages.direct_long} />, name: 'direct', text: <FormattedMessage {...messages.direct_short} />, }, private: { icon: 'lock', meta: <FormattedMessage {...messages.private_long} />, name: 'private', text: <FormattedMessage {...messages.private_short} />, }, public: { icon: 'globe', meta: <FormattedMessage {...messages.public_long} />, name: 'public', text: <FormattedMessage {...messages.public_short} />, }, unlisted: { icon: 'unlock-alt', meta: <FormattedMessage {...messages.unlisted_long} />, name: 'unlisted', text: <FormattedMessage {...messages.unlisted_short} />, }, }; // The result. return ( <div className='composer--options'> <input accept={acceptContentTypes} disabled={disabled || full} key={resetFileKey} onChange={handleChangeFiles} ref={handleRefFileElement} type='file' {...hiddenComponent} /> <Dropdown disabled={disabled || full} icon='paperclip' items={[ { icon: 'cloud-upload', name: 'upload', text: <FormattedMessage {...messages.upload} />, }, { icon: 'paint-brush', name: 'doodle', text: <FormattedMessage {...messages.doodle} />, }, ]} onChange={handleClickAttach} onModalClose={onModalClose} onModalOpen={onModalOpen} title={intl.formatMessage(messages.attach)} /> <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(hasMedia ? 1 : 0.87, { stiffness: 200, damping: 3, }), }} > {({ scale }) => ( <div style={{ display: hasMedia ? null : 'none', transform: `scale(${scale})`, }} > <IconButton active={sensitive} className='sensitive' disabled={spoiler} icon={sensitive ? 'eye-slash' : 'eye'} inverted onClick={onChangeSensitivity} size={18} style={{ height: null, lineHeight: null, }} title={intl.formatMessage(messages.sensitive)} /> </div> )} </Motion> <hr /> <Dropdown disabled={disabled} icon={(privacyItems[privacy] || {}).icon} items={[ privacyItems.public, privacyItems.unlisted, privacyItems.private, privacyItems.direct, ]} onChange={onChangeVisibility} onModalClose={onModalClose} onModalOpen={onModalOpen} title={intl.formatMessage(messages.change_privacy)} value={privacy} /> <TextIconButton active={spoiler} ariaControls='glitch.composer.spoiler.input' label='CW' onClick={onToggleSpoiler} title={intl.formatMessage(messages.spoiler)} /> <Dropdown active={advancedOptions && advancedOptions.some(value => !!value)} disabled={disabled} icon='ellipsis-h' items={advancedOptions ? [ { meta: <FormattedMessage {...messages.local_only_long} />, name: 'do_not_federate', on: advancedOptions.get('do_not_federate'), text: <FormattedMessage {...messages.local_only_short} />, }, { meta: <FormattedMessage {...messages.threaded_mode_long} />, name: 'threaded_mode', on: advancedOptions.get('threaded_mode'), text: <FormattedMessage {...messages.threaded_mode_short} />, }, ] : null} onChange={onChangeAdvancedOption} onModalClose={onModalClose} onModalOpen={onModalOpen} title={intl.formatMessage(messages.advanced_options_icon_title)} /> </div> ); } } // Props. ComposerOptions.propTypes = { acceptContentTypes: PropTypes.string, advancedOptions: ImmutablePropTypes.map, disabled: PropTypes.bool, full: PropTypes.bool, hasMedia: PropTypes.bool, intl: PropTypes.object.isRequired, onChangeAdvancedOption: PropTypes.func, onChangeSensitivity: PropTypes.func, onChangeVisibility: PropTypes.func, onDoodleOpen: PropTypes.func, onModalClose: PropTypes.func, onModalOpen: PropTypes.func, onToggleSpoiler: PropTypes.func, onUpload: PropTypes.func, privacy: PropTypes.string, resetFileKey: PropTypes.number, sensitive: PropTypes.bool, spoiler: PropTypes.bool, };