diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features/composer/options/index.js')
-rw-r--r-- | app/javascript/flavours/glitch/features/composer/options/index.js | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js new file mode 100644 index 000000000..c129622bc --- /dev/null +++ b/app/javascript/flavours/glitch/features/composer/options/index.js @@ -0,0 +1,344 @@ +// 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, +}; |