From a5fdfb31fdc556b6c163749216e070fdb5abf3b0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 30 May 2022 13:11:44 -0500 Subject: [PATCH 01/12] Warning: convert to TSX --- .../features/compose/components/warning.js | 27 ------------------- .../features/compose/components/warning.tsx | 21 +++++++++++++++ 2 files changed, 21 insertions(+), 27 deletions(-) delete mode 100644 app/soapbox/features/compose/components/warning.js create mode 100644 app/soapbox/features/compose/components/warning.tsx diff --git a/app/soapbox/features/compose/components/warning.js b/app/soapbox/features/compose/components/warning.js deleted file mode 100644 index a819b33ec..000000000 --- a/app/soapbox/features/compose/components/warning.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import spring from 'react-motion/lib/spring'; - -import Motion from '../../ui/util/optional_motion'; - -export default class Warning extends React.PureComponent { - - static propTypes = { - message: PropTypes.node.isRequired, - }; - - render() { - const { message } = this.props; - - return ( - - {({ opacity, scaleX, scaleY }) => ( -
- {message} -
- )} -
- ); - } - -} diff --git a/app/soapbox/features/compose/components/warning.tsx b/app/soapbox/features/compose/components/warning.tsx new file mode 100644 index 000000000..b8ec90e09 --- /dev/null +++ b/app/soapbox/features/compose/components/warning.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { spring } from 'react-motion'; + +import Motion from '../../ui/util/optional_motion'; + +interface IWarning { + message: React.ReactNode, +} + +/** Warning message displayed in ComposeForm. */ +const Warning: React.FC = ({ message }) => ( + + {({ opacity, scaleX, scaleY }) => ( +
+ {message} +
+ )} +
+); + +export default Warning; From 1beaccd3acdb4a7a26a67504e7c67a50a6f86700 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 30 May 2022 13:15:35 -0500 Subject: [PATCH 02/12] TextIconButton: convert to TSX --- .../compose/components/text_icon_button.js | 34 ------------------ .../compose/components/text_icon_button.tsx | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 34 deletions(-) delete mode 100644 app/soapbox/features/compose/components/text_icon_button.js create mode 100644 app/soapbox/features/compose/components/text_icon_button.tsx diff --git a/app/soapbox/features/compose/components/text_icon_button.js b/app/soapbox/features/compose/components/text_icon_button.js deleted file mode 100644 index 1ca71fe6f..000000000 --- a/app/soapbox/features/compose/components/text_icon_button.js +++ /dev/null @@ -1,34 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -export default class TextIconButton extends React.PureComponent { - - static propTypes = { - label: PropTypes.string.isRequired, - title: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - ariaControls: PropTypes.string, - unavailable: PropTypes.bool, - }; - - handleClick = (e) => { - e.preventDefault(); - this.props.onClick(); - } - - render() { - const { label, title, active, ariaControls, unavailable } = this.props; - - if (unavailable) { - return null; - } - - return ( - - ); - } - -} diff --git a/app/soapbox/features/compose/components/text_icon_button.tsx b/app/soapbox/features/compose/components/text_icon_button.tsx new file mode 100644 index 000000000..fd49d4ed0 --- /dev/null +++ b/app/soapbox/features/compose/components/text_icon_button.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +interface ITextIconButton { + label: string, + title: string, + active: boolean, + onClick: () => void, + ariaControls: string, + unavailable: boolean, +} + +const TextIconButton: React.FC = ({ + label, + title, + active, + ariaControls, + unavailable, + onClick, +}) => { + const handleClick: React.MouseEventHandler = (e) => { + e.preventDefault(); + onClick(); + }; + + if (unavailable) { + return null; + } + + return ( + + ); +}; + +export default TextIconButton; From 4821703edb59cd1a81e84f4e907540e1bf455374 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 30 May 2022 13:26:24 -0500 Subject: [PATCH 03/12] UploadButton: convert to TSX --- .../compose/components/upload_button.js | 92 ------------------- .../compose/components/upload_button.tsx | 81 ++++++++++++++++ 2 files changed, 81 insertions(+), 92 deletions(-) delete mode 100644 app/soapbox/features/compose/components/upload_button.js create mode 100644 app/soapbox/features/compose/components/upload_button.tsx diff --git a/app/soapbox/features/compose/components/upload_button.js b/app/soapbox/features/compose/components/upload_button.js deleted file mode 100644 index a42e3be1f..000000000 --- a/app/soapbox/features/compose/components/upload_button.js +++ /dev/null @@ -1,92 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; - -import { IconButton } from '../../../components/ui'; - -const messages = defineMessages({ - upload: { id: 'upload_button.label', defaultMessage: 'Add media attachment' }, -}); - -const onlyImages = types => { - return Boolean(types && types.every(type => type.startsWith('image/'))); -}; - -const makeMapStateToProps = () => { - const mapStateToProps = state => ({ - attachmentTypes: state.getIn(['instance', 'configuration', 'media_attachments', 'supported_mime_types']), - }); - - return mapStateToProps; -}; - -export default @connect(makeMapStateToProps) -@injectIntl -class UploadButton extends ImmutablePureComponent { - - static propTypes = { - disabled: PropTypes.bool, - unavailable: PropTypes.bool, - onSelectFile: PropTypes.func.isRequired, - style: PropTypes.object, - resetFileKey: PropTypes.number, - attachmentTypes: ImmutablePropTypes.listOf(PropTypes.string), - intl: PropTypes.object.isRequired, - }; - - handleChange = (e) => { - if (e.target.files.length > 0) { - this.props.onSelectFile(e.target.files); - } - } - - handleClick = () => { - this.fileElement.click(); - } - - setRef = (c) => { - this.fileElement = c; - } - - render() { - const { intl, resetFileKey, attachmentTypes, unavailable, disabled } = this.props; - - if (unavailable) { - return null; - } - - const src = onlyImages(attachmentTypes) - ? require('@tabler/icons/icons/photo.svg') - : require('@tabler/icons/icons/paperclip.svg'); - - return ( -
- - - -
- ); - } - -} diff --git a/app/soapbox/features/compose/components/upload_button.tsx b/app/soapbox/features/compose/components/upload_button.tsx new file mode 100644 index 000000000..a34e2b4ad --- /dev/null +++ b/app/soapbox/features/compose/components/upload_button.tsx @@ -0,0 +1,81 @@ +import React, { useRef } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { IconButton } from 'soapbox/components/ui'; +import { useAppSelector } from 'soapbox/hooks'; + +import type { List as ImmutableList } from 'immutable'; + +const messages = defineMessages({ + upload: { id: 'upload_button.label', defaultMessage: 'Add media attachment' }, +}); + +const onlyImages = (types: ImmutableList) => { + return Boolean(types && types.every(type => type.startsWith('image/'))); +}; + +interface IUploadButton { + disabled: boolean, + unavailable: boolean, + onSelectFile: (files: FileList) => void, + style: React.CSSProperties, + resetFileKey: number, +} + +const UploadButton: React.FC = ({ + disabled, + unavailable, + onSelectFile, + resetFileKey, +}) => { + const intl = useIntl(); + + const fileElement = useRef(null); + const attachmentTypes = useAppSelector(state => state.instance.configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList); + + const handleChange: React.ChangeEventHandler = (e) => { + if (e.target.files?.length) { + onSelectFile(e.target.files); + } + }; + + const handleClick = () => { + fileElement.current?.click(); + }; + + if (unavailable) { + return null; + } + + const src = onlyImages(attachmentTypes) + ? require('@tabler/icons/icons/photo.svg') + : require('@tabler/icons/icons/paperclip.svg'); + + return ( +
+ + + +
+ ); +}; + +export default UploadButton; From e813eecacbf45d20460dd50d49df1e0d09209e5e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 30 May 2022 13:53:14 -0500 Subject: [PATCH 04/12] Upload: convert to TSX --- .../features/compose/components/upload.js | 212 ------------------ .../features/compose/components/upload.tsx | 205 +++++++++++++++++ 2 files changed, 205 insertions(+), 212 deletions(-) delete mode 100644 app/soapbox/features/compose/components/upload.js create mode 100644 app/soapbox/features/compose/components/upload.tsx diff --git a/app/soapbox/features/compose/components/upload.js b/app/soapbox/features/compose/components/upload.js deleted file mode 100644 index a293509f9..000000000 --- a/app/soapbox/features/compose/components/upload.js +++ /dev/null @@ -1,212 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import spring from 'react-motion/lib/spring'; -import { withRouter } from 'react-router-dom'; - -import Blurhash from 'soapbox/components/blurhash'; -import Icon from 'soapbox/components/icon'; -import IconButton from 'soapbox/components/icon_button'; - -import Motion from '../../ui/util/optional_motion'; - -const bookIcon = require('@tabler/icons/icons/book.svg'); -const fileAnalyticsIcon = require('@tabler/icons/icons/file-analytics.svg'); -const fileCodeIcon = require('@tabler/icons/icons/file-code.svg'); -const fileTextIcon = require('@tabler/icons/icons/file-text.svg'); -const fileZipIcon = require('@tabler/icons/icons/file-zip.svg'); -const presentationIcon = require('@tabler/icons/icons/presentation.svg'); - -export const MIMETYPE_ICONS = { - 'application/x-freearc': fileZipIcon, - 'application/x-bzip': fileZipIcon, - 'application/x-bzip2': fileZipIcon, - 'application/gzip': fileZipIcon, - 'application/vnd.rar': fileZipIcon, - 'application/x-tar': fileZipIcon, - 'application/zip': fileZipIcon, - 'application/x-7z-compressed': fileZipIcon, - 'application/x-csh': fileCodeIcon, - 'application/html': fileCodeIcon, - 'text/javascript': fileCodeIcon, - 'application/json': fileCodeIcon, - 'application/ld+json': fileCodeIcon, - 'application/x-httpd-php': fileCodeIcon, - 'application/x-sh': fileCodeIcon, - 'application/xhtml+xml': fileCodeIcon, - 'application/xml': fileCodeIcon, - 'application/epub+zip': bookIcon, - 'application/vnd.oasis.opendocument.spreadsheet': fileAnalyticsIcon, - 'application/vnd.ms-excel': fileAnalyticsIcon, - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': fileAnalyticsIcon, - 'application/pdf': fileTextIcon, - 'application/vnd.oasis.opendocument.presentation': presentationIcon, - 'application/vnd.ms-powerpoint': presentationIcon, - 'application/vnd.openxmlformats-officedocument.presentationml.presentation': presentationIcon, - 'text/plain': fileTextIcon, - 'application/rtf': fileTextIcon, - 'application/msword': fileTextIcon, - 'application/x-abiword': fileTextIcon, - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': fileTextIcon, - 'application/vnd.oasis.opendocument.text': fileTextIcon, -}; - -const messages = defineMessages({ - description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' }, - delete: { id: 'upload_form.undo', defaultMessage: 'Delete' }, -}); - -export default @injectIntl @withRouter -class Upload extends ImmutablePureComponent { - - static propTypes = { - media: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, - onUndo: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, - onOpenFocalPoint: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - }; - - state = { - hovered: false, - focused: false, - dirtyDescription: null, - }; - - handleKeyDown = (e) => { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - } - - handleSubmit = () => { - this.handleInputBlur(); - this.props.onSubmit(this.props.history); - } - - handleUndoClick = e => { - e.stopPropagation(); - this.props.onUndo(this.props.media.get('id')); - } - - handleFocalPointClick = e => { - e.stopPropagation(); - this.props.onOpenFocalPoint(this.props.media.get('id')); - } - - handleInputChange = e => { - this.setState({ dirtyDescription: e.target.value }); - } - - handleMouseEnter = () => { - this.setState({ hovered: true }); - } - - handleMouseLeave = () => { - this.setState({ hovered: false }); - } - - handleInputFocus = () => { - this.setState({ focused: true }); - } - - handleClick = () => { - this.setState({ focused: true }); - } - - handleInputBlur = () => { - const { dirtyDescription } = this.state; - - this.setState({ focused: false, dirtyDescription: null }); - - if (dirtyDescription !== null) { - this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription); - } - } - - handleOpenModal = () => { - this.props.onOpenModal(this.props.media); - } - - render() { - const { intl, media, descriptionLimit } = this.props; - const active = this.state.hovered || this.state.focused; - const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''; - const focusX = media.getIn(['meta', 'focus', 'x']); - const focusY = media.getIn(['meta', 'focus', 'y']); - const x = ((focusX / 2) + .5) * 100; - const y = ((focusY / -2) + .5) * 100; - const mediaType = media.get('type'); - const uploadIcon = mediaType === 'unknown' && ( - - ); - - return ( -
- - - {({ scale }) => ( -
-
- } - /> - - {/* Only display the "Preview" button for a valid attachment with a URL */} - {(mediaType !== 'unknown' && Boolean(media.get('url'))) && ( - } - /> - )} -
- -
-