From 81ef21a0c802f1d905f37a2a818544a8b400793c Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Sat, 25 Feb 2023 14:34:32 +0100 Subject: [Glitch] Rename JSX files with proper `.jsx` extension Port 44a7d87cb1f5df953b6c14c16c59e2e4ead1bcb9 to glitch-soc Signed-off-by: Claire --- .../glitch/features/ui/components/actions_modal.js | 91 --- .../features/ui/components/actions_modal.jsx | 91 +++ .../glitch/features/ui/components/audio_modal.js | 58 -- .../glitch/features/ui/components/audio_modal.jsx | 58 ++ .../glitch/features/ui/components/block_modal.js | 103 ---- .../glitch/features/ui/components/block_modal.jsx | 103 ++++ .../glitch/features/ui/components/boost_modal.js | 139 ----- .../glitch/features/ui/components/boost_modal.jsx | 139 +++++ .../glitch/features/ui/components/bundle.js | 107 ---- .../glitch/features/ui/components/bundle.jsx | 107 ++++ .../features/ui/components/bundle_column_error.js | 162 ------ .../features/ui/components/bundle_column_error.jsx | 162 ++++++ .../features/ui/components/bundle_modal_error.js | 53 -- .../features/ui/components/bundle_modal_error.jsx | 53 ++ .../glitch/features/ui/components/column.js | 75 --- .../glitch/features/ui/components/column.jsx | 75 +++ .../glitch/features/ui/components/column_header.js | 38 -- .../features/ui/components/column_header.jsx | 38 ++ .../glitch/features/ui/components/column_link.js | 55 -- .../glitch/features/ui/components/column_link.jsx | 55 ++ .../features/ui/components/column_loading.js | 32 -- .../features/ui/components/column_loading.jsx | 32 ++ .../features/ui/components/column_subheading.js | 16 - .../features/ui/components/column_subheading.jsx | 16 + .../glitch/features/ui/components/columns_area.js | 183 ------ .../glitch/features/ui/components/columns_area.jsx | 183 ++++++ .../ui/components/compare_history_modal.js | 99 ---- .../ui/components/compare_history_modal.jsx | 99 ++++ .../glitch/features/ui/components/compose_panel.js | 58 -- .../features/ui/components/compose_panel.jsx | 58 ++ .../features/ui/components/confirmation_modal.js | 88 --- .../features/ui/components/confirmation_modal.jsx | 88 +++ .../ui/components/deprecated_settings_modal.js | 86 --- .../ui/components/deprecated_settings_modal.jsx | 86 +++ .../ui/components/disabled_account_banner.js | 92 --- .../ui/components/disabled_account_banner.jsx | 92 +++ .../glitch/features/ui/components/doodle_modal.js | 614 --------------------- .../glitch/features/ui/components/doodle_modal.jsx | 614 +++++++++++++++++++++ .../features/ui/components/drawer_loading.js | 11 - .../features/ui/components/drawer_loading.jsx | 11 + .../glitch/features/ui/components/embed_modal.js | 97 ---- .../glitch/features/ui/components/embed_modal.jsx | 97 ++++ .../features/ui/components/favourite_modal.js | 101 ---- .../features/ui/components/favourite_modal.jsx | 101 ++++ .../glitch/features/ui/components/filter_modal.js | 134 ----- .../glitch/features/ui/components/filter_modal.jsx | 134 +++++ .../features/ui/components/focal_point_modal.js | 418 -------------- .../features/ui/components/focal_point_modal.jsx | 418 ++++++++++++++ .../ui/components/follow_requests_column_link.js | 51 -- .../ui/components/follow_requests_column_link.jsx | 51 ++ .../glitch/features/ui/components/header.js | 88 --- .../glitch/features/ui/components/header.jsx | 88 +++ .../glitch/features/ui/components/image_loader.js | 168 ------ .../glitch/features/ui/components/image_loader.jsx | 168 ++++++ .../glitch/features/ui/components/image_modal.js | 59 -- .../glitch/features/ui/components/image_modal.jsx | 59 ++ .../glitch/features/ui/components/link_footer.js | 102 ---- .../glitch/features/ui/components/link_footer.jsx | 102 ++++ .../glitch/features/ui/components/list_panel.js | 55 -- .../glitch/features/ui/components/list_panel.jsx | 55 ++ .../glitch/features/ui/components/media_modal.js | 252 --------- .../glitch/features/ui/components/media_modal.jsx | 252 +++++++++ .../glitch/features/ui/components/modal_loading.js | 20 - .../features/ui/components/modal_loading.jsx | 20 + .../glitch/features/ui/components/modal_root.js | 144 ----- .../glitch/features/ui/components/modal_root.jsx | 144 +++++ .../glitch/features/ui/components/mute_modal.js | 140 ----- .../glitch/features/ui/components/mute_modal.jsx | 140 +++++ .../features/ui/components/navigation_panel.js | 104 ---- .../features/ui/components/navigation_panel.jsx | 104 ++++ .../features/ui/components/onboarding_modal.js | 321 ----------- .../features/ui/components/onboarding_modal.jsx | 321 +++++++++++ .../glitch/features/ui/components/report_modal.js | 221 -------- .../glitch/features/ui/components/report_modal.jsx | 221 ++++++++ .../features/ui/components/sign_in_banner.js | 40 -- .../features/ui/components/sign_in_banner.jsx | 40 ++ .../glitch/features/ui/components/upload_area.js | 52 -- .../glitch/features/ui/components/upload_area.jsx | 52 ++ .../glitch/features/ui/components/video_modal.js | 66 --- .../glitch/features/ui/components/video_modal.jsx | 66 +++ .../features/ui/components/zoomable_image.js | 450 --------------- .../features/ui/components/zoomable_image.jsx | 450 +++++++++++++++ 82 files changed, 5243 insertions(+), 5243 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/ui/components/actions_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/audio_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/block_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/block_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/boost_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/column.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/column_header.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_header.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/column_link.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_link.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/column_loading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_loading.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/column_subheading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_subheading.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/columns_area.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/columns_area.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/compose_panel.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/doodle_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/doodle_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/drawer_loading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/drawer_loading.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/embed_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/favourite_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/filter_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/header.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/header.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/image_loader.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/image_loader.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/image_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/image_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/link_footer.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/link_footer.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/list_panel.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/list_panel.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/media_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/media_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/modal_loading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/modal_loading.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/modal_root.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/modal_root.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/mute_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/mute_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/navigation_panel.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/report_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/report_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/sign_in_banner.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/upload_area.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/upload_area.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/video_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/video_modal.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/zoomable_image.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx (limited to 'app/javascript/flavours/glitch/features/ui/components') diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js deleted file mode 100644 index c6e3ee37c..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import StatusContent from 'flavours/glitch/components/status_content'; -import Avatar from 'flavours/glitch/components/avatar'; -import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; -import DisplayName from 'flavours/glitch/components/display_name'; -import classNames from 'classnames'; -import IconButton from 'flavours/glitch/components/icon_button'; - -export default class ActionsModal extends ImmutablePureComponent { - - static propTypes = { - status: ImmutablePropTypes.map, - onClick: PropTypes.func, - actions: PropTypes.arrayOf(PropTypes.shape({ - active: PropTypes.bool, - href: PropTypes.string, - icon: PropTypes.string, - meta: PropTypes.string, - name: PropTypes.string, - text: PropTypes.string, - })), - renderItemContents: PropTypes.func, - }; - - renderAction = (action, i) => { - if (action === null) { - return
  • ; - } - - const { icon = null, text, meta = null, active = false, href = '#' } = action; - let contents = this.props.renderItemContents && this.props.renderItemContents(action, i); - - if (!contents) { - contents = ( - - {icon && } -
    -
    {text}
    -
    {meta}
    -
    -
    - ); - } - - return ( -
  • - - {contents} - -
  • - ); - }; - - render () { - const status = this.props.status && ( -
    -
    -
    - - - -
    - - -
    - -
    - - -
    -
    - - -
    - ); - - return ( -
    - {status} - -
      - {this.props.actions.map(this.renderAction)} -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx new file mode 100644 index 000000000..c6e3ee37c --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx @@ -0,0 +1,91 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import StatusContent from 'flavours/glitch/components/status_content'; +import Avatar from 'flavours/glitch/components/avatar'; +import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; +import DisplayName from 'flavours/glitch/components/display_name'; +import classNames from 'classnames'; +import IconButton from 'flavours/glitch/components/icon_button'; + +export default class ActionsModal extends ImmutablePureComponent { + + static propTypes = { + status: ImmutablePropTypes.map, + onClick: PropTypes.func, + actions: PropTypes.arrayOf(PropTypes.shape({ + active: PropTypes.bool, + href: PropTypes.string, + icon: PropTypes.string, + meta: PropTypes.string, + name: PropTypes.string, + text: PropTypes.string, + })), + renderItemContents: PropTypes.func, + }; + + renderAction = (action, i) => { + if (action === null) { + return
  • ; + } + + const { icon = null, text, meta = null, active = false, href = '#' } = action; + let contents = this.props.renderItemContents && this.props.renderItemContents(action, i); + + if (!contents) { + contents = ( + + {icon && } +
    +
    {text}
    +
    {meta}
    +
    +
    + ); + } + + return ( +
  • + + {contents} + +
  • + ); + }; + + render () { + const status = this.props.status && ( +
    +
    +
    + + + +
    + + +
    + +
    + + +
    +
    + + +
    + ); + + return ( +
    + {status} + +
      + {this.props.actions.map(this.renderAction)} +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js b/app/javascript/flavours/glitch/features/ui/components/audio_modal.js deleted file mode 100644 index fc98cc6af..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import Audio from 'flavours/glitch/features/audio'; -import { connect } from 'react-redux'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import Footer from 'flavours/glitch/features/picture_in_picture/components/footer'; - -const mapStateToProps = (state, { statusId }) => ({ - accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']), -}); - -export default @connect(mapStateToProps) -class AudioModal extends ImmutablePureComponent { - - static propTypes = { - media: ImmutablePropTypes.map.isRequired, - statusId: PropTypes.string.isRequired, - accountStaticAvatar: PropTypes.string.isRequired, - options: PropTypes.shape({ - autoPlay: PropTypes.bool, - }), - onClose: PropTypes.func.isRequired, - onChangeBackgroundColor: PropTypes.func.isRequired, - }; - - static contextTypes = { - router: PropTypes.object, - }; - - render () { - const { media, accountStaticAvatar, statusId, onClose } = this.props; - const options = this.props.options || {}; - - return ( -
    -
    -
    - -
    - {statusId &&
    } -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx new file mode 100644 index 000000000..fc98cc6af --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import Audio from 'flavours/glitch/features/audio'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Footer from 'flavours/glitch/features/picture_in_picture/components/footer'; + +const mapStateToProps = (state, { statusId }) => ({ + accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']), +}); + +export default @connect(mapStateToProps) +class AudioModal extends ImmutablePureComponent { + + static propTypes = { + media: ImmutablePropTypes.map.isRequired, + statusId: PropTypes.string.isRequired, + accountStaticAvatar: PropTypes.string.isRequired, + options: PropTypes.shape({ + autoPlay: PropTypes.bool, + }), + onClose: PropTypes.func.isRequired, + onChangeBackgroundColor: PropTypes.func.isRequired, + }; + + static contextTypes = { + router: PropTypes.object, + }; + + render () { + const { media, accountStaticAvatar, statusId, onClose } = this.props; + const options = this.props.options || {}; + + return ( +
    +
    +
    + +
    + {statusId &&
    } +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/block_modal.js b/app/javascript/flavours/glitch/features/ui/components/block_modal.js deleted file mode 100644 index 6c9d2043c..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/block_modal.js +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import { makeGetAccount } from '../../../selectors'; -import Button from '../../../components/button'; -import { closeModal } from '../../../actions/modal'; -import { blockAccount } from '../../../actions/accounts'; -import { initReport } from '../../../actions/reports'; - - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = state => ({ - account: getAccount(state, state.getIn(['blocks', 'new', 'account_id'])), - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = dispatch => { - return { - onConfirm(account) { - dispatch(blockAccount(account.get('id'))); - }, - - onBlockAndReport(account) { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account)); - }, - - onClose() { - dispatch(closeModal()); - }, - }; -}; - -export default @connect(makeMapStateToProps, mapDispatchToProps) -@injectIntl -class BlockModal extends React.PureComponent { - - static propTypes = { - account: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, - onBlockAndReport: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - componentDidMount() { - this.button.focus(); - } - - handleClick = () => { - this.props.onClose(); - this.props.onConfirm(this.props.account); - }; - - handleSecondary = () => { - this.props.onClose(); - this.props.onBlockAndReport(this.props.account); - }; - - handleCancel = () => { - this.props.onClose(); - }; - - setRef = (c) => { - this.button = c; - }; - - render () { - const { account } = this.props; - - return ( -
    -
    -

    - @{account.get('acct')} }} - /> -

    -
    - -
    - - - -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx new file mode 100644 index 000000000..6c9d2043c --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import { makeGetAccount } from '../../../selectors'; +import Button from '../../../components/button'; +import { closeModal } from '../../../actions/modal'; +import { blockAccount } from '../../../actions/accounts'; +import { initReport } from '../../../actions/reports'; + + +const makeMapStateToProps = () => { + const getAccount = makeGetAccount(); + + const mapStateToProps = state => ({ + account: getAccount(state, state.getIn(['blocks', 'new', 'account_id'])), + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = dispatch => { + return { + onConfirm(account) { + dispatch(blockAccount(account.get('id'))); + }, + + onBlockAndReport(account) { + dispatch(blockAccount(account.get('id'))); + dispatch(initReport(account)); + }, + + onClose() { + dispatch(closeModal()); + }, + }; +}; + +export default @connect(makeMapStateToProps, mapDispatchToProps) +@injectIntl +class BlockModal extends React.PureComponent { + + static propTypes = { + account: PropTypes.object.isRequired, + onClose: PropTypes.func.isRequired, + onBlockAndReport: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.button.focus(); + } + + handleClick = () => { + this.props.onClose(); + this.props.onConfirm(this.props.account); + }; + + handleSecondary = () => { + this.props.onClose(); + this.props.onBlockAndReport(this.props.account); + }; + + handleCancel = () => { + this.props.onClose(); + }; + + setRef = (c) => { + this.button = c; + }; + + render () { + const { account } = this.props; + + return ( +
    +
    +

    + @{account.get('acct')} }} + /> +

    +
    + +
    + + + +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js deleted file mode 100644 index a65b84e20..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ /dev/null @@ -1,139 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Button from 'flavours/glitch/components/button'; -import StatusContent from 'flavours/glitch/components/status_content'; -import Avatar from 'flavours/glitch/components/avatar'; -import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; -import DisplayName from 'flavours/glitch/components/display_name'; -import AttachmentList from 'flavours/glitch/components/attachment_list'; -import Icon from 'flavours/glitch/components/icon'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import PrivacyDropdown from 'flavours/glitch/features/compose/components/privacy_dropdown'; -import classNames from 'classnames'; -import { changeBoostPrivacy } from 'flavours/glitch/actions/boosts'; -import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; - -const messages = defineMessages({ - cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, - reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, -}); - -const mapStateToProps = state => { - return { - privacy: state.getIn(['boosts', 'new', 'privacy']), - }; -}; - -const mapDispatchToProps = dispatch => { - return { - onChangeBoostPrivacy(value) { - dispatch(changeBoostPrivacy(value)); - }, - }; -}; - -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl -class BoostModal extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - status: ImmutablePropTypes.map.isRequired, - onReblog: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - missingMediaDescription: PropTypes.bool, - intl: PropTypes.object.isRequired, - }; - - componentDidMount() { - this.button.focus(); - } - - handleReblog = () => { - this.props.onReblog(this.props.status, this.props.privacy); - this.props.onClose(); - }; - - handleAccountClick = (e) => { - if (e.button === 0) { - e.preventDefault(); - this.props.onClose(); - let state = { ...this.context.router.history.location.state }; - state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state); - } - }; - - _findContainer = () => { - return document.getElementsByClassName('modal-root__container')[0]; - }; - - setRef = (c) => { - this.button = c; - }; - - render () { - const { status, missingMediaDescription, privacy, intl } = this.props; - const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; - - return ( -
    -
    -
    - - - - - {status.get('media_attachments').size > 0 && ( - - )} -
    -
    - -
    -
    - { missingMediaDescription ? - - : - Shift + }} /> - } -
    - - {status.get('visibility') !== 'private' && !status.get('reblogged') && ( - - )} -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx new file mode 100644 index 000000000..a65b84e20 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx @@ -0,0 +1,139 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import Button from 'flavours/glitch/components/button'; +import StatusContent from 'flavours/glitch/components/status_content'; +import Avatar from 'flavours/glitch/components/avatar'; +import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; +import DisplayName from 'flavours/glitch/components/display_name'; +import AttachmentList from 'flavours/glitch/components/attachment_list'; +import Icon from 'flavours/glitch/components/icon'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PrivacyDropdown from 'flavours/glitch/features/compose/components/privacy_dropdown'; +import classNames from 'classnames'; +import { changeBoostPrivacy } from 'flavours/glitch/actions/boosts'; +import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; + +const messages = defineMessages({ + cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, + reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, +}); + +const mapStateToProps = state => { + return { + privacy: state.getIn(['boosts', 'new', 'privacy']), + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onChangeBoostPrivacy(value) { + dispatch(changeBoostPrivacy(value)); + }, + }; +}; + +export default @connect(mapStateToProps, mapDispatchToProps) +@injectIntl +class BoostModal extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + status: ImmutablePropTypes.map.isRequired, + onReblog: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + missingMediaDescription: PropTypes.bool, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.button.focus(); + } + + handleReblog = () => { + this.props.onReblog(this.props.status, this.props.privacy); + this.props.onClose(); + }; + + handleAccountClick = (e) => { + if (e.button === 0) { + e.preventDefault(); + this.props.onClose(); + let state = { ...this.context.router.history.location.state }; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state); + } + }; + + _findContainer = () => { + return document.getElementsByClassName('modal-root__container')[0]; + }; + + setRef = (c) => { + this.button = c; + }; + + render () { + const { status, missingMediaDescription, privacy, intl } = this.props; + const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; + + return ( +
    +
    +
    + + + + + {status.get('media_attachments').size > 0 && ( + + )} +
    +
    + +
    +
    + { missingMediaDescription ? + + : + Shift + }} /> + } +
    + + {status.get('visibility') !== 'private' && !status.get('reblogged') && ( + + )} +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle.js b/app/javascript/flavours/glitch/features/ui/components/bundle.js deleted file mode 100644 index 27b13ecfe..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/bundle.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const emptyComponent = () => null; -const noop = () => { }; - -class Bundle extends React.Component { - - static propTypes = { - fetchComponent: PropTypes.func.isRequired, - loading: PropTypes.func, - error: PropTypes.func, - children: PropTypes.func.isRequired, - renderDelay: PropTypes.number, - onFetch: PropTypes.func, - onFetchSuccess: PropTypes.func, - onFetchFail: PropTypes.func, - }; - - static defaultProps = { - loading: emptyComponent, - error: emptyComponent, - renderDelay: 0, - onFetch: noop, - onFetchSuccess: noop, - onFetchFail: noop, - }; - - static cache = {}; - - state = { - mod: undefined, - forceRender: false, - }; - - componentWillMount() { - this.load(this.props); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.fetchComponent !== this.props.fetchComponent) { - this.load(nextProps); - } - } - - componentWillUnmount () { - if (this.timeout) { - clearTimeout(this.timeout); - } - } - - load = (props) => { - const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props; - - if (fetchComponent === undefined) { - this.setState({ mod: null }); - return Promise.resolve(); - } - - onFetch(); - - if (Bundle.cache[fetchComponent.name]) { - const mod = Bundle.cache[fetchComponent.name]; - - this.setState({ mod: mod.default }); - onFetchSuccess(); - return Promise.resolve(); - } - - this.setState({ mod: undefined }); - - if (renderDelay !== 0) { - this.timestamp = new Date(); - this.timeout = setTimeout(() => this.setState({ forceRender: true }), renderDelay); - } - - return fetchComponent() - .then((mod) => { - Bundle.cache[fetchComponent.name] = mod; - this.setState({ mod: mod.default }); - onFetchSuccess(); - }) - .catch((error) => { - this.setState({ mod: null }); - onFetchFail(error); - }); - }; - - render() { - const { loading: Loading, error: Error, children, renderDelay } = this.props; - const { mod, forceRender } = this.state; - const elapsed = this.timestamp ? (new Date() - this.timestamp) : renderDelay; - - if (mod === undefined) { - return (elapsed >= renderDelay || forceRender) ? : null; - } - - if (mod === null) { - return ; - } - - return children(mod); - } - -} - -export default Bundle; diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle.jsx b/app/javascript/flavours/glitch/features/ui/components/bundle.jsx new file mode 100644 index 000000000..27b13ecfe --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/bundle.jsx @@ -0,0 +1,107 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const emptyComponent = () => null; +const noop = () => { }; + +class Bundle extends React.Component { + + static propTypes = { + fetchComponent: PropTypes.func.isRequired, + loading: PropTypes.func, + error: PropTypes.func, + children: PropTypes.func.isRequired, + renderDelay: PropTypes.number, + onFetch: PropTypes.func, + onFetchSuccess: PropTypes.func, + onFetchFail: PropTypes.func, + }; + + static defaultProps = { + loading: emptyComponent, + error: emptyComponent, + renderDelay: 0, + onFetch: noop, + onFetchSuccess: noop, + onFetchFail: noop, + }; + + static cache = {}; + + state = { + mod: undefined, + forceRender: false, + }; + + componentWillMount() { + this.load(this.props); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.fetchComponent !== this.props.fetchComponent) { + this.load(nextProps); + } + } + + componentWillUnmount () { + if (this.timeout) { + clearTimeout(this.timeout); + } + } + + load = (props) => { + const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props; + + if (fetchComponent === undefined) { + this.setState({ mod: null }); + return Promise.resolve(); + } + + onFetch(); + + if (Bundle.cache[fetchComponent.name]) { + const mod = Bundle.cache[fetchComponent.name]; + + this.setState({ mod: mod.default }); + onFetchSuccess(); + return Promise.resolve(); + } + + this.setState({ mod: undefined }); + + if (renderDelay !== 0) { + this.timestamp = new Date(); + this.timeout = setTimeout(() => this.setState({ forceRender: true }), renderDelay); + } + + return fetchComponent() + .then((mod) => { + Bundle.cache[fetchComponent.name] = mod; + this.setState({ mod: mod.default }); + onFetchSuccess(); + }) + .catch((error) => { + this.setState({ mod: null }); + onFetchFail(error); + }); + }; + + render() { + const { loading: Loading, error: Error, children, renderDelay } = this.props; + const { mod, forceRender } = this.state; + const elapsed = this.timestamp ? (new Date() - this.timestamp) : renderDelay; + + if (mod === undefined) { + return (elapsed >= renderDelay || forceRender) ? : null; + } + + if (mod === null) { + return ; + } + + return children(mod); + } + +} + +export default Bundle; diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js deleted file mode 100644 index 88304dc36..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js +++ /dev/null @@ -1,162 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import Column from 'flavours/glitch/components/column'; -import Button from 'flavours/glitch/components/button'; -import { Helmet } from 'react-helmet'; -import { Link } from 'react-router-dom'; -import classNames from 'classnames'; -import { autoPlayGif } from 'flavours/glitch/initial_state'; - -class GIF extends React.PureComponent { - - static propTypes = { - src: PropTypes.string.isRequired, - staticSrc: PropTypes.string.isRequired, - className: PropTypes.string, - animate: PropTypes.bool, - }; - - static defaultProps = { - animate: autoPlayGif, - }; - - state = { - hovering: false, - }; - - handleMouseEnter = () => { - const { animate } = this.props; - - if (!animate) { - this.setState({ hovering: true }); - } - }; - - handleMouseLeave = () => { - const { animate } = this.props; - - if (!animate) { - this.setState({ hovering: false }); - } - }; - - render () { - const { src, staticSrc, className, animate } = this.props; - const { hovering } = this.state; - - return ( - - ); - } - -} - -class CopyButton extends React.PureComponent { - - static propTypes = { - children: PropTypes.node.isRequired, - value: PropTypes.string.isRequired, - }; - - state = { - copied: false, - }; - - handleClick = () => { - const { value } = this.props; - navigator.clipboard.writeText(value); - this.setState({ copied: true }); - this.timeout = setTimeout(() => this.setState({ copied: false }), 700); - }; - - componentWillUnmount () { - if (this.timeout) clearTimeout(this.timeout); - } - - render () { - const { children } = this.props; - const { copied } = this.state; - - return ( - - ); - } - -} - -export default @injectIntl -class BundleColumnError extends React.PureComponent { - - static propTypes = { - errorType: PropTypes.oneOf(['routing', 'network', 'error']), - onRetry: PropTypes.func, - intl: PropTypes.object.isRequired, - multiColumn: PropTypes.bool, - stacktrace: PropTypes.string, - }; - - static defaultProps = { - errorType: 'routing', - }; - - handleRetry = () => { - const { onRetry } = this.props; - - if (onRetry) { - onRetry(); - } - }; - - render () { - const { errorType, multiColumn, stacktrace } = this.props; - - let title, body; - - switch(errorType) { - case 'routing': - title = ; - body = ; - break; - case 'network': - title = ; - body = ; - break; - case 'error': - title = ; - body = ; - break; - } - - return ( - -
    - - -
    -

    {title}

    -

    {body}

    - -
    - {errorType === 'network' && } - {errorType === 'error' && } - -
    -
    -
    - - - - -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx new file mode 100644 index 000000000..88304dc36 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx @@ -0,0 +1,162 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import Column from 'flavours/glitch/components/column'; +import Button from 'flavours/glitch/components/button'; +import { Helmet } from 'react-helmet'; +import { Link } from 'react-router-dom'; +import classNames from 'classnames'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; + +class GIF extends React.PureComponent { + + static propTypes = { + src: PropTypes.string.isRequired, + staticSrc: PropTypes.string.isRequired, + className: PropTypes.string, + animate: PropTypes.bool, + }; + + static defaultProps = { + animate: autoPlayGif, + }; + + state = { + hovering: false, + }; + + handleMouseEnter = () => { + const { animate } = this.props; + + if (!animate) { + this.setState({ hovering: true }); + } + }; + + handleMouseLeave = () => { + const { animate } = this.props; + + if (!animate) { + this.setState({ hovering: false }); + } + }; + + render () { + const { src, staticSrc, className, animate } = this.props; + const { hovering } = this.state; + + return ( + + ); + } + +} + +class CopyButton extends React.PureComponent { + + static propTypes = { + children: PropTypes.node.isRequired, + value: PropTypes.string.isRequired, + }; + + state = { + copied: false, + }; + + handleClick = () => { + const { value } = this.props; + navigator.clipboard.writeText(value); + this.setState({ copied: true }); + this.timeout = setTimeout(() => this.setState({ copied: false }), 700); + }; + + componentWillUnmount () { + if (this.timeout) clearTimeout(this.timeout); + } + + render () { + const { children } = this.props; + const { copied } = this.state; + + return ( + + ); + } + +} + +export default @injectIntl +class BundleColumnError extends React.PureComponent { + + static propTypes = { + errorType: PropTypes.oneOf(['routing', 'network', 'error']), + onRetry: PropTypes.func, + intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, + stacktrace: PropTypes.string, + }; + + static defaultProps = { + errorType: 'routing', + }; + + handleRetry = () => { + const { onRetry } = this.props; + + if (onRetry) { + onRetry(); + } + }; + + render () { + const { errorType, multiColumn, stacktrace } = this.props; + + let title, body; + + switch(errorType) { + case 'routing': + title = ; + body = ; + break; + case 'network': + title = ; + body = ; + break; + case 'error': + title = ; + body = ; + break; + } + + return ( + +
    + + +
    +

    {title}

    +

    {body}

    + +
    + {errorType === 'network' && } + {errorType === 'error' && } + +
    +
    +
    + + + + +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js b/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js deleted file mode 100644 index b79105450..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; - -import IconButton from 'flavours/glitch/components/icon_button'; - -const messages = defineMessages({ - error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' }, - retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' }, - close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' }, -}); - -class BundleModalError extends React.Component { - - static propTypes = { - onRetry: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleRetry = () => { - this.props.onRetry(); - }; - - render () { - const { onClose, intl: { formatMessage } } = this.props; - - // Keep the markup in sync with - // (make sure they have the same dimensions) - return ( -
    -
    - - {formatMessage(messages.error)} -
    - -
    -
    - -
    -
    -
    - ); - } - -} - -export default injectIntl(BundleModalError); diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx b/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx new file mode 100644 index 000000000..b79105450 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; + +import IconButton from 'flavours/glitch/components/icon_button'; + +const messages = defineMessages({ + error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' }, + retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' }, + close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' }, +}); + +class BundleModalError extends React.Component { + + static propTypes = { + onRetry: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleRetry = () => { + this.props.onRetry(); + }; + + render () { + const { onClose, intl: { formatMessage } } = this.props; + + // Keep the markup in sync with + // (make sure they have the same dimensions) + return ( +
    +
    + + {formatMessage(messages.error)} +
    + +
    +
    + +
    +
    +
    + ); + } + +} + +export default injectIntl(BundleModalError); diff --git a/app/javascript/flavours/glitch/features/ui/components/column.js b/app/javascript/flavours/glitch/features/ui/components/column.js deleted file mode 100644 index cc2abc43a..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/column.js +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import ColumnHeader from './column_header'; -import PropTypes from 'prop-types'; -import { debounce } from 'lodash'; -import { scrollTop } from 'flavours/glitch/scroll'; -import { isMobile } from 'flavours/glitch/is_mobile'; - -export default class Column extends React.PureComponent { - - static propTypes = { - heading: PropTypes.string, - icon: PropTypes.string, - children: PropTypes.node, - active: PropTypes.bool, - hideHeadingOnMobile: PropTypes.bool, - name: PropTypes.string, - bindToDocument: PropTypes.bool, - }; - - handleHeaderClick = () => { - const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); - - if (!scrollable) { - return; - } - - this._interruptScrollAnimation = scrollTop(scrollable); - }; - - scrollTop () { - const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); - - if (!scrollable) { - return; - } - - this._interruptScrollAnimation = scrollTop(scrollable); - } - - - handleScroll = debounce(() => { - if (typeof this._interruptScrollAnimation !== 'undefined') { - this._interruptScrollAnimation(); - } - }, 200); - - setRef = (c) => { - this.node = c; - }; - - render () { - const { heading, icon, children, active, hideHeadingOnMobile, name } = this.props; - - const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); - - const columnHeaderId = showHeading && heading.replace(/ /g, '-'); - const header = showHeading && ( - - ); - return ( -
    - {header} - {children} -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/column.jsx b/app/javascript/flavours/glitch/features/ui/components/column.jsx new file mode 100644 index 000000000..cc2abc43a --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/column.jsx @@ -0,0 +1,75 @@ +import React from 'react'; +import ColumnHeader from './column_header'; +import PropTypes from 'prop-types'; +import { debounce } from 'lodash'; +import { scrollTop } from 'flavours/glitch/scroll'; +import { isMobile } from 'flavours/glitch/is_mobile'; + +export default class Column extends React.PureComponent { + + static propTypes = { + heading: PropTypes.string, + icon: PropTypes.string, + children: PropTypes.node, + active: PropTypes.bool, + hideHeadingOnMobile: PropTypes.bool, + name: PropTypes.string, + bindToDocument: PropTypes.bool, + }; + + handleHeaderClick = () => { + const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); + + if (!scrollable) { + return; + } + + this._interruptScrollAnimation = scrollTop(scrollable); + }; + + scrollTop () { + const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); + + if (!scrollable) { + return; + } + + this._interruptScrollAnimation = scrollTop(scrollable); + } + + + handleScroll = debounce(() => { + if (typeof this._interruptScrollAnimation !== 'undefined') { + this._interruptScrollAnimation(); + } + }, 200); + + setRef = (c) => { + this.node = c; + }; + + render () { + const { heading, icon, children, active, hideHeadingOnMobile, name } = this.props; + + const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); + + const columnHeaderId = showHeading && heading.replace(/ /g, '-'); + const header = showHeading && ( + + ); + return ( +
    + {header} + {children} +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/column_header.js b/app/javascript/flavours/glitch/features/ui/components/column_header.js deleted file mode 100644 index 151476f8b..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/column_header.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import Icon from 'flavours/glitch/components/icon'; - -export default class ColumnHeader extends React.PureComponent { - - static propTypes = { - icon: PropTypes.string, - type: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func, - columnHeaderId: PropTypes.string, - }; - - handleClick = () => { - this.props.onClick(); - }; - - render () { - const { icon, type, active, columnHeaderId } = this.props; - let iconElement = ''; - - if (icon) { - iconElement = ; - } - - return ( -

    - -

    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/column_header.jsx b/app/javascript/flavours/glitch/features/ui/components/column_header.jsx new file mode 100644 index 000000000..151476f8b --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/column_header.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Icon from 'flavours/glitch/components/icon'; + +export default class ColumnHeader extends React.PureComponent { + + static propTypes = { + icon: PropTypes.string, + type: PropTypes.string, + active: PropTypes.bool, + onClick: PropTypes.func, + columnHeaderId: PropTypes.string, + }; + + handleClick = () => { + this.props.onClick(); + }; + + render () { + const { icon, type, active, columnHeaderId } = this.props; + let iconElement = ''; + + if (icon) { + iconElement = ; + } + + return ( +

    + +

    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/column_link.js b/app/javascript/flavours/glitch/features/ui/components/column_link.js deleted file mode 100644 index dcdac077f..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/column_link.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { NavLink } from 'react-router-dom'; -import Icon from 'flavours/glitch/components/icon'; -import classNames from 'classnames'; - -const ColumnLink = ({ icon, text, to, onClick, href, method, badge, transparent, ...other }) => { - const className = classNames('column-link', { 'column-link--transparent': transparent }); - const badgeElement = typeof badge !== 'undefined' ? {badge} : null; - const iconElement = typeof icon === 'string' ? : icon; - - if (href) { - return ( - - {iconElement} - {text} - {badgeElement} - - ); - } else if (to) { - return ( - - {iconElement} - {text} - {badgeElement} - - ); - } else { - const handleOnClick = (e) => { - e.preventDefault(); - e.stopPropagation(); - return onClick(e); - }; - return ( - - {iconElement} - {text} - {badgeElement} - - ); - } -}; - -ColumnLink.propTypes = { - icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired, - text: PropTypes.string.isRequired, - to: PropTypes.string, - onClick: PropTypes.func, - href: PropTypes.string, - method: PropTypes.string, - badge: PropTypes.node, - transparent: PropTypes.bool, -}; - -export default ColumnLink; diff --git a/app/javascript/flavours/glitch/features/ui/components/column_link.jsx b/app/javascript/flavours/glitch/features/ui/components/column_link.jsx new file mode 100644 index 000000000..dcdac077f --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/column_link.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { NavLink } from 'react-router-dom'; +import Icon from 'flavours/glitch/components/icon'; +import classNames from 'classnames'; + +const ColumnLink = ({ icon, text, to, onClick, href, method, badge, transparent, ...other }) => { + const className = classNames('column-link', { 'column-link--transparent': transparent }); + const badgeElement = typeof badge !== 'undefined' ? {badge} : null; + const iconElement = typeof icon === 'string' ? : icon; + + if (href) { + return ( + + {iconElement} + {text} + {badgeElement} + + ); + } else if (to) { + return ( + + {iconElement} + {text} + {badgeElement} + + ); + } else { + const handleOnClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + return onClick(e); + }; + return ( + + {iconElement} + {text} + {badgeElement} + + ); + } +}; + +ColumnLink.propTypes = { + icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired, + text: PropTypes.string.isRequired, + to: PropTypes.string, + onClick: PropTypes.func, + href: PropTypes.string, + method: PropTypes.string, + badge: PropTypes.node, + transparent: PropTypes.bool, +}; + +export default ColumnLink; diff --git a/app/javascript/flavours/glitch/features/ui/components/column_loading.js b/app/javascript/flavours/glitch/features/ui/components/column_loading.js deleted file mode 100644 index b07385397..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/column_loading.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import Column from 'flavours/glitch/components/column'; -import ColumnHeader from 'flavours/glitch/components/column_header'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -export default class ColumnLoading extends ImmutablePureComponent { - - static propTypes = { - title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), - icon: PropTypes.string, - multiColumn: PropTypes.bool, - }; - - static defaultProps = { - title: '', - icon: '', - }; - - render() { - let { title, icon, multiColumn } = this.props; - - return ( - - -
    - - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/column_loading.jsx b/app/javascript/flavours/glitch/features/ui/components/column_loading.jsx new file mode 100644 index 000000000..b07385397 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/column_loading.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import Column from 'flavours/glitch/components/column'; +import ColumnHeader from 'flavours/glitch/components/column_header'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +export default class ColumnLoading extends ImmutablePureComponent { + + static propTypes = { + title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), + icon: PropTypes.string, + multiColumn: PropTypes.bool, + }; + + static defaultProps = { + title: '', + icon: '', + }; + + render() { + let { title, icon, multiColumn } = this.props; + + return ( + + +
    + + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/column_subheading.js b/app/javascript/flavours/glitch/features/ui/components/column_subheading.js deleted file mode 100644 index 8160c4aa3..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/column_subheading.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const ColumnSubheading = ({ text }) => { - return ( -
    - {text} -
    - ); -}; - -ColumnSubheading.propTypes = { - text: PropTypes.string.isRequired, -}; - -export default ColumnSubheading; diff --git a/app/javascript/flavours/glitch/features/ui/components/column_subheading.jsx b/app/javascript/flavours/glitch/features/ui/components/column_subheading.jsx new file mode 100644 index 000000000..8160c4aa3 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/column_subheading.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const ColumnSubheading = ({ text }) => { + return ( +
    + {text} +
    + ); +}; + +ColumnSubheading.propTypes = { + text: PropTypes.string.isRequired, +}; + +export default ColumnSubheading; diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js deleted file mode 100644 index 3b3b0d58f..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ /dev/null @@ -1,183 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import BundleContainer from '../containers/bundle_container'; -import ColumnLoading from './column_loading'; -import DrawerLoading from './drawer_loading'; -import BundleColumnError from './bundle_column_error'; -import { - Compose, - Notifications, - HomeTimeline, - CommunityTimeline, - PublicTimeline, - HashtagTimeline, - DirectTimeline, - FavouritedStatuses, - BookmarkedStatuses, - ListTimeline, - Directory, -} from '../../ui/util/async-components'; -import ComposePanel from './compose_panel'; -import NavigationPanel from './navigation_panel'; - -import { supportsPassiveEvents } from 'detect-passive-events'; -import { scrollRight } from 'flavours/glitch/scroll'; - -const componentMap = { - 'COMPOSE': Compose, - 'HOME': HomeTimeline, - 'NOTIFICATIONS': Notifications, - 'PUBLIC': PublicTimeline, - 'REMOTE': PublicTimeline, - 'COMMUNITY': CommunityTimeline, - 'HASHTAG': HashtagTimeline, - 'DIRECT': DirectTimeline, - 'FAVOURITES': FavouritedStatuses, - 'BOOKMARKS': BookmarkedStatuses, - 'LIST': ListTimeline, - 'DIRECTORY': Directory, -}; - -export default class ColumnsArea extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object.isRequired, - }; - - static propTypes = { - columns: ImmutablePropTypes.list.isRequired, - singleColumn: PropTypes.bool, - children: PropTypes.node, - navbarUnder: PropTypes.bool, - openSettings: PropTypes.func, - }; - - // Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS - mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)'); - - state = { - renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches), - }; - - componentDidMount() { - if (!this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - - if (this.mediaQuery) { - if (this.mediaQuery.addEventListener) { - this.mediaQuery.addEventListener('change', this.handleLayoutChange); - } else { - this.mediaQuery.addListener(this.handleLayoutChange); - } - this.setState({ renderComposePanel: !this.mediaQuery.matches }); - } - - this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); - } - - componentWillUpdate(nextProps) { - if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - } - - componentDidUpdate(prevProps) { - if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - } - - componentWillUnmount () { - if (!this.props.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - - if (this.mediaQuery) { - if (this.mediaQuery.removeEventListener) { - this.mediaQuery.removeEventListener('change', this.handleLayoutChange); - } else { - this.mediaQuery.removeListener(this.handleLayoutChange); - } - } - } - - handleChildrenContentChange() { - if (!this.props.singleColumn) { - const modifier = this.isRtlLayout ? -1 : 1; - this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); - } - } - - handleLayoutChange = (e) => { - this.setState({ renderComposePanel: !e.matches }); - }; - - handleWheel = () => { - if (typeof this._interruptScrollAnimation !== 'function') { - return; - } - - this._interruptScrollAnimation(); - }; - - setRef = (node) => { - this.node = node; - }; - - renderLoading = columnId => () => { - return columnId === 'COMPOSE' ? : ; - }; - - renderError = (props) => { - return ; - }; - - render () { - const { columns, children, singleColumn, navbarUnder, openSettings } = this.props; - const { renderComposePanel } = this.state; - - if (singleColumn) { - return ( -
    -
    -
    - {renderComposePanel && } -
    -
    - -
    -
    -
    {children}
    -
    - -
    -
    - -
    -
    -
    - ); - } - - return ( -
    - {columns.map(column => { - const params = column.get('params', null) === null ? null : column.get('params').toJS(); - const other = params && params.other ? params.other : {}; - - return ( - - {SpecificComponent => } - - ); - })} - - {React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))} -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx new file mode 100644 index 000000000..3b3b0d58f --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx @@ -0,0 +1,183 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import BundleContainer from '../containers/bundle_container'; +import ColumnLoading from './column_loading'; +import DrawerLoading from './drawer_loading'; +import BundleColumnError from './bundle_column_error'; +import { + Compose, + Notifications, + HomeTimeline, + CommunityTimeline, + PublicTimeline, + HashtagTimeline, + DirectTimeline, + FavouritedStatuses, + BookmarkedStatuses, + ListTimeline, + Directory, +} from '../../ui/util/async-components'; +import ComposePanel from './compose_panel'; +import NavigationPanel from './navigation_panel'; + +import { supportsPassiveEvents } from 'detect-passive-events'; +import { scrollRight } from 'flavours/glitch/scroll'; + +const componentMap = { + 'COMPOSE': Compose, + 'HOME': HomeTimeline, + 'NOTIFICATIONS': Notifications, + 'PUBLIC': PublicTimeline, + 'REMOTE': PublicTimeline, + 'COMMUNITY': CommunityTimeline, + 'HASHTAG': HashtagTimeline, + 'DIRECT': DirectTimeline, + 'FAVOURITES': FavouritedStatuses, + 'BOOKMARKS': BookmarkedStatuses, + 'LIST': ListTimeline, + 'DIRECTORY': Directory, +}; + +export default class ColumnsArea extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object.isRequired, + }; + + static propTypes = { + columns: ImmutablePropTypes.list.isRequired, + singleColumn: PropTypes.bool, + children: PropTypes.node, + navbarUnder: PropTypes.bool, + openSettings: PropTypes.func, + }; + + // Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS + mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)'); + + state = { + renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches), + }; + + componentDidMount() { + if (!this.props.singleColumn) { + this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); + } + + if (this.mediaQuery) { + if (this.mediaQuery.addEventListener) { + this.mediaQuery.addEventListener('change', this.handleLayoutChange); + } else { + this.mediaQuery.addListener(this.handleLayoutChange); + } + this.setState({ renderComposePanel: !this.mediaQuery.matches }); + } + + this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); + } + + componentWillUpdate(nextProps) { + if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) { + this.node.removeEventListener('wheel', this.handleWheel); + } + } + + componentDidUpdate(prevProps) { + if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { + this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); + } + } + + componentWillUnmount () { + if (!this.props.singleColumn) { + this.node.removeEventListener('wheel', this.handleWheel); + } + + if (this.mediaQuery) { + if (this.mediaQuery.removeEventListener) { + this.mediaQuery.removeEventListener('change', this.handleLayoutChange); + } else { + this.mediaQuery.removeListener(this.handleLayoutChange); + } + } + } + + handleChildrenContentChange() { + if (!this.props.singleColumn) { + const modifier = this.isRtlLayout ? -1 : 1; + this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); + } + } + + handleLayoutChange = (e) => { + this.setState({ renderComposePanel: !e.matches }); + }; + + handleWheel = () => { + if (typeof this._interruptScrollAnimation !== 'function') { + return; + } + + this._interruptScrollAnimation(); + }; + + setRef = (node) => { + this.node = node; + }; + + renderLoading = columnId => () => { + return columnId === 'COMPOSE' ? : ; + }; + + renderError = (props) => { + return ; + }; + + render () { + const { columns, children, singleColumn, navbarUnder, openSettings } = this.props; + const { renderComposePanel } = this.state; + + if (singleColumn) { + return ( +
    +
    +
    + {renderComposePanel && } +
    +
    + +
    +
    +
    {children}
    +
    + +
    +
    + +
    +
    +
    + ); + } + + return ( +
    + {columns.map(column => { + const params = column.get('params', null) === null ? null : column.get('params').toJS(); + const other = params && params.other ? params.other : {}; + + return ( + + {SpecificComponent => } + + ); + })} + + {React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))} +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js deleted file mode 100644 index baf7f25be..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; -import { FormattedMessage } from 'react-intl'; -import { closeModal } from 'flavours/glitch/actions/modal'; -import emojify from 'flavours/glitch/features/emoji/emoji'; -import escapeTextContentForBrowser from 'escape-html'; -import InlineAccount from 'flavours/glitch/components/inline_account'; -import IconButton from 'flavours/glitch/components/icon_button'; -import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; -import MediaAttachments from 'flavours/glitch/components/media_attachments'; - -const mapStateToProps = (state, { statusId }) => ({ - versions: state.getIn(['history', statusId, 'items']), -}); - -const mapDispatchToProps = dispatch => ({ - - onClose() { - dispatch(closeModal()); - }, - -}); - -export default @connect(mapStateToProps, mapDispatchToProps) -class CompareHistoryModal extends React.PureComponent { - - static propTypes = { - onClose: PropTypes.func.isRequired, - index: PropTypes.number.isRequired, - statusId: PropTypes.string.isRequired, - versions: ImmutablePropTypes.list.isRequired, - }; - - render () { - const { index, versions, onClose } = this.props; - const currentVersion = versions.get(index); - - const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => { - obj[`:${emoji.get('shortcode')}:`] = emoji.toJS(); - return obj; - }, {}); - - const content = { __html: emojify(currentVersion.get('content'), emojiMap) }; - const spoilerContent = { __html: emojify(escapeTextContentForBrowser(currentVersion.get('spoiler_text')), emojiMap) }; - - const formattedDate = ; - const formattedName = ; - - const label = currentVersion.get('original') ? ( - - ) : ( - - ); - - return ( -
    -
    - - {label} -
    - -
    -
    - {currentVersion.get('spoiler_text').length > 0 && ( - -
    -
    - - )} - -
    - - {!!currentVersion.get('poll') && ( -
    -
      - {currentVersion.getIn(['poll', 'options']).map(option => ( -
    • - - - -
    • - ))} -
    -
    - )} - - -
    -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx new file mode 100644 index 000000000..baf7f25be --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx @@ -0,0 +1,99 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; +import { closeModal } from 'flavours/glitch/actions/modal'; +import emojify from 'flavours/glitch/features/emoji/emoji'; +import escapeTextContentForBrowser from 'escape-html'; +import InlineAccount from 'flavours/glitch/components/inline_account'; +import IconButton from 'flavours/glitch/components/icon_button'; +import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; +import MediaAttachments from 'flavours/glitch/components/media_attachments'; + +const mapStateToProps = (state, { statusId }) => ({ + versions: state.getIn(['history', statusId, 'items']), +}); + +const mapDispatchToProps = dispatch => ({ + + onClose() { + dispatch(closeModal()); + }, + +}); + +export default @connect(mapStateToProps, mapDispatchToProps) +class CompareHistoryModal extends React.PureComponent { + + static propTypes = { + onClose: PropTypes.func.isRequired, + index: PropTypes.number.isRequired, + statusId: PropTypes.string.isRequired, + versions: ImmutablePropTypes.list.isRequired, + }; + + render () { + const { index, versions, onClose } = this.props; + const currentVersion = versions.get(index); + + const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => { + obj[`:${emoji.get('shortcode')}:`] = emoji.toJS(); + return obj; + }, {}); + + const content = { __html: emojify(currentVersion.get('content'), emojiMap) }; + const spoilerContent = { __html: emojify(escapeTextContentForBrowser(currentVersion.get('spoiler_text')), emojiMap) }; + + const formattedDate = ; + const formattedName = ; + + const label = currentVersion.get('original') ? ( + + ) : ( + + ); + + return ( +
    +
    + + {label} +
    + +
    +
    + {currentVersion.get('spoiler_text').length > 0 && ( + +
    +
    + + )} + +
    + + {!!currentVersion.get('poll') && ( +
    +
      + {currentVersion.getIn(['poll', 'options']).map(option => ( +
    • + + + +
    • + ))} +
    +
    + )} + + +
    +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js deleted file mode 100644 index 34c194c99..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; -import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; -import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container'; -import LinkFooter from './link_footer'; -import ServerBanner from 'flavours/glitch/components/server_banner'; -import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose'; - -export default @connect() -class ComposePanel extends React.PureComponent { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - - static propTypes = { - dispatch: PropTypes.func.isRequired, - }; - - componentDidMount () { - const { dispatch } = this.props; - dispatch(mountCompose()); - } - - componentWillUnmount () { - const { dispatch } = this.props; - dispatch(unmountCompose()); - } - - render() { - const { signedIn } = this.context.identity; - - return ( -
    - - - {!signedIn && ( - - -
    - - )} - - {signedIn && ( - - - - - )} - - -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx new file mode 100644 index 000000000..34c194c99 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; +import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; +import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container'; +import LinkFooter from './link_footer'; +import ServerBanner from 'flavours/glitch/components/server_banner'; +import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose'; + +export default @connect() +class ComposePanel extends React.PureComponent { + + static contextTypes = { + identity: PropTypes.object.isRequired, + }; + + static propTypes = { + dispatch: PropTypes.func.isRequired, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(mountCompose()); + } + + componentWillUnmount () { + const { dispatch } = this.props; + dispatch(unmountCompose()); + } + + render() { + const { signedIn } = this.context.identity; + + return ( +
    + + + {!signedIn && ( + + +
    + + )} + + {signedIn && ( + + + + + )} + + +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js deleted file mode 100644 index 94935de5d..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import Button from 'flavours/glitch/components/button'; - -export default @injectIntl -class ConfirmationModal extends React.PureComponent { - - static propTypes = { - message: PropTypes.node.isRequired, - confirm: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - secondary: PropTypes.string, - onSecondary: PropTypes.func, - closeWhenConfirm: PropTypes.bool, - onDoNotAsk: PropTypes.func, - intl: PropTypes.object.isRequired, - }; - - static defaultProps = { - closeWhenConfirm: true, - }; - - componentDidMount() { - this.button.focus(); - } - - handleClick = () => { - if (this.props.closeWhenConfirm) { - this.props.onClose(); - } - this.props.onConfirm(); - if (this.props.onDoNotAsk && this.doNotAskCheckbox.checked) { - this.props.onDoNotAsk(); - } - }; - - handleSecondary = () => { - this.props.onClose(); - this.props.onSecondary(); - }; - - handleCancel = () => { - this.props.onClose(); - }; - - setRef = (c) => { - this.button = c; - }; - - setDoNotAskRef = (c) => { - this.doNotAskCheckbox = c; - }; - - render () { - const { message, confirm, secondary, onDoNotAsk } = this.props; - - return ( -
    -
    - {message} -
    - -
    - { onDoNotAsk && ( -
    - - -
    - )} -
    - - {secondary !== undefined && ( -
    -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx new file mode 100644 index 000000000..94935de5d --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import Button from 'flavours/glitch/components/button'; + +export default @injectIntl +class ConfirmationModal extends React.PureComponent { + + static propTypes = { + message: PropTypes.node.isRequired, + confirm: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + secondary: PropTypes.string, + onSecondary: PropTypes.func, + closeWhenConfirm: PropTypes.bool, + onDoNotAsk: PropTypes.func, + intl: PropTypes.object.isRequired, + }; + + static defaultProps = { + closeWhenConfirm: true, + }; + + componentDidMount() { + this.button.focus(); + } + + handleClick = () => { + if (this.props.closeWhenConfirm) { + this.props.onClose(); + } + this.props.onConfirm(); + if (this.props.onDoNotAsk && this.doNotAskCheckbox.checked) { + this.props.onDoNotAsk(); + } + }; + + handleSecondary = () => { + this.props.onClose(); + this.props.onSecondary(); + }; + + handleCancel = () => { + this.props.onClose(); + }; + + setRef = (c) => { + this.button = c; + }; + + setDoNotAskRef = (c) => { + this.doNotAskCheckbox = c; + }; + + render () { + const { message, confirm, secondary, onDoNotAsk } = this.props; + + return ( +
    +
    + {message} +
    + +
    + { onDoNotAsk && ( +
    + + +
    + )} +
    + + {secondary !== undefined && ( +
    +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js deleted file mode 100644 index 37f52b014..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { preferenceLink } from 'flavours/glitch/utils/backend_links'; -import Button from 'flavours/glitch/components/button'; -import Icon from 'flavours/glitch/components/icon'; -import illustration from 'flavours/glitch/images/logo_warn_glitch.svg'; - -const messages = defineMessages({ - discardChanges: { id: 'confirmations.deprecated_settings.confirm', defaultMessage: 'Use Mastodon preferences' }, - user_setting_expand_spoilers: { id: 'settings.enable_content_warnings_auto_unfold', defaultMessage: 'Automatically unfold content-warnings' }, - user_setting_disable_swiping: { id: 'settings.swipe_to_change_columns', defaultMessage: 'Allow swiping to change columns (Mobile only)' }, -}); - -export default @injectIntl -class DeprecatedSettingsModal extends React.PureComponent { - - static propTypes = { - settings: ImmutablePropTypes.list.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - componentDidMount() { - this.button.focus(); - } - - handleClick = () => { - this.props.onConfirm(); - this.props.onClose(); - }; - - setRef = (c) => { - this.button = c; - }; - - render () { - const { settings, intl } = this.props; - - return ( -
    -
    - - - - - - - ), - preferences: ( - - - - ), - }} - /> - -
    -
      - { settings.map((setting_name) => ( -
    • - -
    • - )) } -
    -
    -
    - -
    -
    -
    -
    -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx new file mode 100644 index 000000000..37f52b014 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx @@ -0,0 +1,86 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { preferenceLink } from 'flavours/glitch/utils/backend_links'; +import Button from 'flavours/glitch/components/button'; +import Icon from 'flavours/glitch/components/icon'; +import illustration from 'flavours/glitch/images/logo_warn_glitch.svg'; + +const messages = defineMessages({ + discardChanges: { id: 'confirmations.deprecated_settings.confirm', defaultMessage: 'Use Mastodon preferences' }, + user_setting_expand_spoilers: { id: 'settings.enable_content_warnings_auto_unfold', defaultMessage: 'Automatically unfold content-warnings' }, + user_setting_disable_swiping: { id: 'settings.swipe_to_change_columns', defaultMessage: 'Allow swiping to change columns (Mobile only)' }, +}); + +export default @injectIntl +class DeprecatedSettingsModal extends React.PureComponent { + + static propTypes = { + settings: ImmutablePropTypes.list.isRequired, + onClose: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.button.focus(); + } + + handleClick = () => { + this.props.onConfirm(); + this.props.onClose(); + }; + + setRef = (c) => { + this.button = c; + }; + + render () { + const { settings, intl } = this.props; + + return ( +
    +
    + + + + + + + ), + preferences: ( + + + + ), + }} + /> + +
    +
      + { settings.map((setting_name) => ( +
    • + +
    • + )) } +
    +
    +
    + +
    +
    +
    +
    +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js deleted file mode 100644 index 35933bedb..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; -import { disabledAccountId, movedToAccountId, domain } from 'flavours/glitch/initial_state'; -import { openModal } from 'flavours/glitch/actions/modal'; -import { logOut } from 'flavours/glitch/utils/log_out'; - -const messages = defineMessages({ - logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, - logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' }, -}); - -const mapStateToProps = (state) => ({ - disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']), - movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined, -}); - -const mapDispatchToProps = (dispatch, { intl }) => ({ - onLogout () { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), - })); - }, -}); - -export default @injectIntl -@connect(mapStateToProps, mapDispatchToProps) -class DisabledAccountBanner extends React.PureComponent { - - static propTypes = { - disabledAcct: PropTypes.string.isRequired, - movedToAcct: PropTypes.string, - onLogout: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleLogOutClick = e => { - e.preventDefault(); - e.stopPropagation(); - - this.props.onLogout(); - - return false; - }; - - render () { - const { disabledAcct, movedToAcct } = this.props; - - const disabledAccountLink = ( - - {disabledAcct}@{domain} - - ); - - return ( -
    -

    - {movedToAcct ? ( - {movedToAcct.includes('@') ? movedToAcct : `${movedToAcct}@${domain}`}, - }} - /> - ) : ( - - )} -

    - - - - -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx new file mode 100644 index 000000000..35933bedb --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { Link } from 'react-router-dom'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { disabledAccountId, movedToAccountId, domain } from 'flavours/glitch/initial_state'; +import { openModal } from 'flavours/glitch/actions/modal'; +import { logOut } from 'flavours/glitch/utils/log_out'; + +const messages = defineMessages({ + logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, + logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' }, +}); + +const mapStateToProps = (state) => ({ + disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']), + movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined, +}); + +const mapDispatchToProps = (dispatch, { intl }) => ({ + onLogout () { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.logoutMessage), + confirm: intl.formatMessage(messages.logoutConfirm), + closeWhenConfirm: false, + onConfirm: () => logOut(), + })); + }, +}); + +export default @injectIntl +@connect(mapStateToProps, mapDispatchToProps) +class DisabledAccountBanner extends React.PureComponent { + + static propTypes = { + disabledAcct: PropTypes.string.isRequired, + movedToAcct: PropTypes.string, + onLogout: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleLogOutClick = e => { + e.preventDefault(); + e.stopPropagation(); + + this.props.onLogout(); + + return false; + }; + + render () { + const { disabledAcct, movedToAcct } = this.props; + + const disabledAccountLink = ( + + {disabledAcct}@{domain} + + ); + + return ( +
    +

    + {movedToAcct ? ( + {movedToAcct.includes('@') ? movedToAcct : `${movedToAcct}@${domain}`}, + }} + /> + ) : ( + + )} +

    + + + + +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js deleted file mode 100644 index c8ea33a0e..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js +++ /dev/null @@ -1,614 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Button from 'flavours/glitch/components/button'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import Atrament from 'atrament'; // the doodling library -import { connect } from 'react-redux'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { doodleSet, uploadCompose } from 'flavours/glitch/actions/compose'; -import IconButton from 'flavours/glitch/components/icon_button'; -import { debounce, mapValues } from 'lodash'; -import classNames from 'classnames'; - -// palette nicked from MyPaint, CC0 -const palette = [ - ['rgb( 0, 0, 0)', 'Black'], - ['rgb( 38, 38, 38)', 'Gray 15'], - ['rgb( 77, 77, 77)', 'Grey 30'], - ['rgb(128, 128, 128)', 'Grey 50'], - ['rgb(171, 171, 171)', 'Grey 67'], - ['rgb(217, 217, 217)', 'Grey 85'], - ['rgb(255, 255, 255)', 'White'], - ['rgb(128, 0, 0)', 'Maroon'], - ['rgb(209, 0, 0)', 'English-red'], - ['rgb(255, 54, 34)', 'Tomato'], - ['rgb(252, 60, 3)', 'Orange-red'], - ['rgb(255, 140, 105)', 'Salmon'], - ['rgb(252, 232, 32)', 'Cadium-yellow'], - ['rgb(243, 253, 37)', 'Lemon yellow'], - ['rgb(121, 5, 35)', 'Dark crimson'], - ['rgb(169, 32, 62)', 'Deep carmine'], - ['rgb(255, 140, 0)', 'Orange'], - ['rgb(255, 168, 18)', 'Dark tangerine'], - ['rgb(217, 144, 88)', 'Persian orange'], - ['rgb(194, 178, 128)', 'Sand'], - ['rgb(255, 229, 180)', 'Peach'], - ['rgb(100, 54, 46)', 'Bole'], - ['rgb(108, 41, 52)', 'Dark cordovan'], - ['rgb(163, 65, 44)', 'Chestnut'], - ['rgb(228, 136, 100)', 'Dark salmon'], - ['rgb(255, 195, 143)', 'Apricot'], - ['rgb(255, 219, 188)', 'Unbleached silk'], - ['rgb(242, 227, 198)', 'Straw'], - ['rgb( 53, 19, 13)', 'Bistre'], - ['rgb( 84, 42, 14)', 'Dark chocolate'], - ['rgb(102, 51, 43)', 'Burnt sienna'], - ['rgb(184, 66, 0)', 'Sienna'], - ['rgb(216, 153, 12)', 'Yellow ochre'], - ['rgb(210, 180, 140)', 'Tan'], - ['rgb(232, 204, 144)', 'Dark wheat'], - ['rgb( 0, 49, 83)', 'Prussian blue'], - ['rgb( 48, 69, 119)', 'Dark grey blue'], - ['rgb( 0, 71, 171)', 'Cobalt blue'], - ['rgb( 31, 117, 254)', 'Blue'], - ['rgb(120, 180, 255)', 'Bright french blue'], - ['rgb(171, 200, 255)', 'Bright steel blue'], - ['rgb(208, 231, 255)', 'Ice blue'], - ['rgb( 30, 51, 58)', 'Medium jungle green'], - ['rgb( 47, 79, 79)', 'Dark slate grey'], - ['rgb( 74, 104, 93)', 'Dark grullo green'], - ['rgb( 0, 128, 128)', 'Teal'], - ['rgb( 67, 170, 176)', 'Turquoise'], - ['rgb(109, 174, 199)', 'Cerulean frost'], - ['rgb(173, 217, 186)', 'Tiffany green'], - ['rgb( 22, 34, 29)', 'Gray-asparagus'], - ['rgb( 36, 48, 45)', 'Medium dark teal'], - ['rgb( 74, 104, 93)', 'Xanadu'], - ['rgb(119, 198, 121)', 'Mint'], - ['rgb(175, 205, 182)', 'Timberwolf'], - ['rgb(185, 245, 246)', 'Celeste'], - ['rgb(193, 255, 234)', 'Aquamarine'], - ['rgb( 29, 52, 35)', 'Cal Poly Pomona'], - ['rgb( 1, 68, 33)', 'Forest green'], - ['rgb( 42, 128, 0)', 'Napier green'], - ['rgb(128, 128, 0)', 'Olive'], - ['rgb( 65, 156, 105)', 'Sea green'], - ['rgb(189, 246, 29)', 'Green-yellow'], - ['rgb(231, 244, 134)', 'Bright chartreuse'], - ['rgb(138, 23, 137)', 'Purple'], - ['rgb( 78, 39, 138)', 'Violet'], - ['rgb(193, 75, 110)', 'Dark thulian pink'], - ['rgb(222, 49, 99)', 'Cerise'], - ['rgb(255, 20, 147)', 'Deep pink'], - ['rgb(255, 102, 204)', 'Rose pink'], - ['rgb(255, 203, 219)', 'Pink'], - ['rgb(255, 255, 255)', 'White'], - ['rgb(229, 17, 1)', 'RGB Red'], - ['rgb( 0, 255, 0)', 'RGB Green'], - ['rgb( 0, 0, 255)', 'RGB Blue'], - ['rgb( 0, 255, 255)', 'CMYK Cyan'], - ['rgb(255, 0, 255)', 'CMYK Magenta'], - ['rgb(255, 255, 0)', 'CMYK Yellow'], -]; - -// re-arrange to the right order for display -let palReordered = []; -for (let row = 0; row < 7; row++) { - for (let col = 0; col < 11; col++) { - palReordered.push(palette[col * 7 + row]); - } - palReordered.push(null); // null indicates a
    -} - -// Utility for converting base64 image to binary for upload -// https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f -function dataURLtoFile(dataurl, filename) { - let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], - bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); - while(n--){ - u8arr[n] = bstr.charCodeAt(n); - } - return new File([u8arr], filename, { type: mime }); -} -/** Doodle canvas size options */ -const DOODLE_SIZES = { - normal: [500, 500, 'Square 500'], - tootbanner: [702, 330, 'Tootbanner'], - s640x480: [640, 480, '640×480 - 480p'], - s800x600: [800, 600, '800×600 - SVGA'], - s720x480: [720, 405, '720x405 - 16:9'], -}; - - -const mapStateToProps = state => ({ - options: state.getIn(['compose', 'doodle']), -}); - -const mapDispatchToProps = dispatch => ({ - /** Set options in the redux store */ - setOpt: (opts) => dispatch(doodleSet(opts)), - /** Submit doodle for upload */ - submit: (file) => dispatch(uploadCompose([file])), -}); - -/** - * Doodling dialog with drawing canvas - * - * Keyboard shortcuts: - * - Delete: Clear screen, fill with background color - * - Backspace, Ctrl+Z: Undo one step - * - Ctrl held while drawing: Use background color - * - Shift held while clicking screen: Use fill tool - * - * Palette: - * - Left mouse button: pick foreground - * - Ctrl + left mouse button: pick background - * - Right mouse button: pick background - */ -export default @connect(mapStateToProps, mapDispatchToProps) -class DoodleModal extends ImmutablePureComponent { - - static propTypes = { - options: ImmutablePropTypes.map, - onClose: PropTypes.func.isRequired, - setOpt: PropTypes.func.isRequired, - submit: PropTypes.func.isRequired, - }; - - //region Option getters/setters - - /** Foreground color */ - get fg () { - return this.props.options.get('fg'); - } - set fg (value) { - this.props.setOpt({ fg: value }); - } - - /** Background color */ - get bg () { - return this.props.options.get('bg'); - } - set bg (value) { - this.props.setOpt({ bg: value }); - } - - /** Swap Fg and Bg for drawing */ - get swapped () { - return this.props.options.get('swapped'); - } - set swapped (value) { - this.props.setOpt({ swapped: value }); - } - - /** Mode - 'draw' or 'fill' */ - get mode () { - return this.props.options.get('mode'); - } - set mode (value) { - this.props.setOpt({ mode: value }); - } - - /** Base line weight */ - get weight () { - return this.props.options.get('weight'); - } - set weight (value) { - this.props.setOpt({ weight: value }); - } - - /** Drawing opacity */ - get opacity () { - return this.props.options.get('opacity'); - } - set opacity (value) { - this.props.setOpt({ opacity: value }); - } - - /** Adaptive stroke - change width with speed */ - get adaptiveStroke () { - return this.props.options.get('adaptiveStroke'); - } - set adaptiveStroke (value) { - this.props.setOpt({ adaptiveStroke: value }); - } - - /** Smoothing (for mouse drawing) */ - get smoothing () { - return this.props.options.get('smoothing'); - } - set smoothing (value) { - this.props.setOpt({ smoothing: value }); - } - - /** Size preset */ - get size () { - return this.props.options.get('size'); - } - set size (value) { - this.props.setOpt({ size: value }); - } - - //endregion - - /** Key up handler */ - handleKeyUp = (e) => { - if (e.target.nodeName === 'INPUT') return; - - if (e.key === 'Delete') { - e.preventDefault(); - this.handleClearBtn(); - return; - } - - if (e.key === 'Backspace' || (e.key === 'z' && (e.ctrlKey || e.metaKey))) { - e.preventDefault(); - this.undo(); - } - - if (e.key === 'Control' || e.key === 'Meta') { - this.controlHeld = false; - this.swapped = false; - } - - if (e.key === 'Shift') { - this.shiftHeld = false; - this.mode = 'draw'; - } - }; - - /** Key down handler */ - handleKeyDown = (e) => { - if (e.key === 'Control' || e.key === 'Meta') { - this.controlHeld = true; - this.swapped = true; - } - - if (e.key === 'Shift') { - this.shiftHeld = true; - this.mode = 'fill'; - } - }; - - /** - * Component installed in the DOM, do some initial set-up - */ - componentDidMount () { - this.controlHeld = false; - this.shiftHeld = false; - this.swapped = false; - window.addEventListener('keyup', this.handleKeyUp, false); - window.addEventListener('keydown', this.handleKeyDown, false); - } - - /** - * Tear component down - */ - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp, false); - window.removeEventListener('keydown', this.handleKeyDown, false); - if (this.sketcher) this.sketcher.destroy(); - } - - /** - * Set reference to the canvas element. - * This is called during component init - * - * @param elem - canvas element - */ - setCanvasRef = (elem) => { - this.canvas = elem; - if (elem) { - elem.addEventListener('dirty', () => { - this.saveUndo(); - this.sketcher._dirty = false; - }); - - elem.addEventListener('click', () => { - // sketcher bug - does not fire dirty on fill - if (this.mode === 'fill') { - this.saveUndo(); - } - }); - - // prevent context menu - elem.addEventListener('contextmenu', (e) => { - e.preventDefault(); - }); - - elem.addEventListener('mousedown', (e) => { - if (e.button === 2) { - this.swapped = true; - } - }); - - elem.addEventListener('mouseup', (e) => { - if (e.button === 2) { - this.swapped = this.controlHeld; - } - }); - - this.initSketcher(elem); - this.mode = 'draw'; // Reset mode - it's confusing if left at 'fill' - } - }; - - /** - * Set up the sketcher instance - * - * @param canvas - canvas element. Null if we're just resizing - */ - initSketcher (canvas = null) { - const sizepreset = DOODLE_SIZES[this.size]; - - if (this.sketcher) this.sketcher.destroy(); - this.sketcher = new Atrament(canvas || this.canvas, sizepreset[0], sizepreset[1]); - - if (canvas) { - this.ctx = this.sketcher.context; - this.updateSketcherSettings(); - } - - this.clearScreen(); - } - - /** - * Done button handler - */ - onDoneButton = () => { - const dataUrl = this.sketcher.toImage(); - const file = dataURLtoFile(dataUrl, 'doodle.png'); - this.props.submit(file); - this.props.onClose(); // close dialog - }; - - /** - * Cancel button handler - */ - onCancelButton = () => { - if (this.undos.length > 1 && !confirm('Discard doodle? All changes will be lost!')) { - return; - } - - this.props.onClose(); // close dialog - }; - - /** - * Update sketcher options based on state - */ - updateSketcherSettings () { - if (!this.sketcher) return; - - if (this.oldSize !== this.size) this.initSketcher(); - - this.sketcher.color = (this.swapped ? this.bg : this.fg); - this.sketcher.opacity = this.opacity; - this.sketcher.weight = this.weight; - this.sketcher.mode = this.mode; - this.sketcher.smoothing = this.smoothing; - this.sketcher.adaptiveStroke = this.adaptiveStroke; - - this.oldSize = this.size; - } - - /** - * Fill screen with background color - */ - clearScreen = () => { - this.ctx.fillStyle = this.bg; - this.ctx.fillRect(-1, -1, this.canvas.width+2, this.canvas.height+2); - this.undos = []; - - this.doSaveUndo(); - }; - - /** - * Undo one step - */ - undo = () => { - if (this.undos.length > 1) { - this.undos.pop(); - const buf = this.undos.pop(); - - this.sketcher.clear(); - this.ctx.putImageData(buf, 0, 0); - this.doSaveUndo(); - } - }; - - /** - * Save canvas content into the undo buffer immediately - */ - doSaveUndo = () => { - this.undos.push(this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)); - }; - - /** - * Called on each canvas change. - * Saves canvas content to the undo buffer after some period of inactivity. - */ - saveUndo = debounce(() => { - this.doSaveUndo(); - }, 100); - - /** - * Palette left click. - * Selects Fg color (or Bg, if Control/Meta is held) - * - * @param e - event - */ - onPaletteClick = (e) => { - const c = e.target.dataset.color; - - if (this.controlHeld) { - this.bg = c; - } else { - this.fg = c; - } - - e.target.blur(); - e.preventDefault(); - }; - - /** - * Palette right click. - * Selects Bg color - * - * @param e - event - */ - onPaletteRClick = (e) => { - this.bg = e.target.dataset.color; - e.target.blur(); - e.preventDefault(); - }; - - /** - * Handle click on the Draw mode button - * - * @param e - event - */ - setModeDraw = (e) => { - this.mode = 'draw'; - e.target.blur(); - }; - - /** - * Handle click on the Fill mode button - * - * @param e - event - */ - setModeFill = (e) => { - this.mode = 'fill'; - e.target.blur(); - }; - - /** - * Handle click on Smooth checkbox - * - * @param e - event - */ - tglSmooth = (e) => { - this.smoothing = !this.smoothing; - e.target.blur(); - }; - - /** - * Handle click on Adaptive checkbox - * - * @param e - event - */ - tglAdaptive = (e) => { - this.adaptiveStroke = !this.adaptiveStroke; - e.target.blur(); - }; - - /** - * Handle change of the Weight input field - * - * @param e - event - */ - setWeight = (e) => { - this.weight = +e.target.value || 1; - }; - - /** - * Set size - clalback from the select box - * - * @param e - event - */ - changeSize = (e) => { - let newSize = e.target.value; - if (newSize === this.oldSize) return; - - if (this.undos.length > 1 && !confirm('Change canvas size? This will erase your current drawing!')) { - return; - } - - this.size = newSize; - }; - - handleClearBtn = () => { - if (this.undos.length > 1 && !confirm('Clear canvas? This will erase your current drawing!')) { - return; - } - - this.clearScreen(); - }; - - /** - * Render the component - */ - render () { - this.updateSketcherSettings(); - - return ( -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - - - - -
    -
    - - - - -
    -
    - - - - -
    -
    - -
    -
    -
    - - - - -
    -
    - { - palReordered.map((c, i) => - c === null ? -
    : -
    -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.jsx new file mode 100644 index 000000000..c8ea33a0e --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.jsx @@ -0,0 +1,614 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Button from 'flavours/glitch/components/button'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Atrament from 'atrament'; // the doodling library +import { connect } from 'react-redux'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { doodleSet, uploadCompose } from 'flavours/glitch/actions/compose'; +import IconButton from 'flavours/glitch/components/icon_button'; +import { debounce, mapValues } from 'lodash'; +import classNames from 'classnames'; + +// palette nicked from MyPaint, CC0 +const palette = [ + ['rgb( 0, 0, 0)', 'Black'], + ['rgb( 38, 38, 38)', 'Gray 15'], + ['rgb( 77, 77, 77)', 'Grey 30'], + ['rgb(128, 128, 128)', 'Grey 50'], + ['rgb(171, 171, 171)', 'Grey 67'], + ['rgb(217, 217, 217)', 'Grey 85'], + ['rgb(255, 255, 255)', 'White'], + ['rgb(128, 0, 0)', 'Maroon'], + ['rgb(209, 0, 0)', 'English-red'], + ['rgb(255, 54, 34)', 'Tomato'], + ['rgb(252, 60, 3)', 'Orange-red'], + ['rgb(255, 140, 105)', 'Salmon'], + ['rgb(252, 232, 32)', 'Cadium-yellow'], + ['rgb(243, 253, 37)', 'Lemon yellow'], + ['rgb(121, 5, 35)', 'Dark crimson'], + ['rgb(169, 32, 62)', 'Deep carmine'], + ['rgb(255, 140, 0)', 'Orange'], + ['rgb(255, 168, 18)', 'Dark tangerine'], + ['rgb(217, 144, 88)', 'Persian orange'], + ['rgb(194, 178, 128)', 'Sand'], + ['rgb(255, 229, 180)', 'Peach'], + ['rgb(100, 54, 46)', 'Bole'], + ['rgb(108, 41, 52)', 'Dark cordovan'], + ['rgb(163, 65, 44)', 'Chestnut'], + ['rgb(228, 136, 100)', 'Dark salmon'], + ['rgb(255, 195, 143)', 'Apricot'], + ['rgb(255, 219, 188)', 'Unbleached silk'], + ['rgb(242, 227, 198)', 'Straw'], + ['rgb( 53, 19, 13)', 'Bistre'], + ['rgb( 84, 42, 14)', 'Dark chocolate'], + ['rgb(102, 51, 43)', 'Burnt sienna'], + ['rgb(184, 66, 0)', 'Sienna'], + ['rgb(216, 153, 12)', 'Yellow ochre'], + ['rgb(210, 180, 140)', 'Tan'], + ['rgb(232, 204, 144)', 'Dark wheat'], + ['rgb( 0, 49, 83)', 'Prussian blue'], + ['rgb( 48, 69, 119)', 'Dark grey blue'], + ['rgb( 0, 71, 171)', 'Cobalt blue'], + ['rgb( 31, 117, 254)', 'Blue'], + ['rgb(120, 180, 255)', 'Bright french blue'], + ['rgb(171, 200, 255)', 'Bright steel blue'], + ['rgb(208, 231, 255)', 'Ice blue'], + ['rgb( 30, 51, 58)', 'Medium jungle green'], + ['rgb( 47, 79, 79)', 'Dark slate grey'], + ['rgb( 74, 104, 93)', 'Dark grullo green'], + ['rgb( 0, 128, 128)', 'Teal'], + ['rgb( 67, 170, 176)', 'Turquoise'], + ['rgb(109, 174, 199)', 'Cerulean frost'], + ['rgb(173, 217, 186)', 'Tiffany green'], + ['rgb( 22, 34, 29)', 'Gray-asparagus'], + ['rgb( 36, 48, 45)', 'Medium dark teal'], + ['rgb( 74, 104, 93)', 'Xanadu'], + ['rgb(119, 198, 121)', 'Mint'], + ['rgb(175, 205, 182)', 'Timberwolf'], + ['rgb(185, 245, 246)', 'Celeste'], + ['rgb(193, 255, 234)', 'Aquamarine'], + ['rgb( 29, 52, 35)', 'Cal Poly Pomona'], + ['rgb( 1, 68, 33)', 'Forest green'], + ['rgb( 42, 128, 0)', 'Napier green'], + ['rgb(128, 128, 0)', 'Olive'], + ['rgb( 65, 156, 105)', 'Sea green'], + ['rgb(189, 246, 29)', 'Green-yellow'], + ['rgb(231, 244, 134)', 'Bright chartreuse'], + ['rgb(138, 23, 137)', 'Purple'], + ['rgb( 78, 39, 138)', 'Violet'], + ['rgb(193, 75, 110)', 'Dark thulian pink'], + ['rgb(222, 49, 99)', 'Cerise'], + ['rgb(255, 20, 147)', 'Deep pink'], + ['rgb(255, 102, 204)', 'Rose pink'], + ['rgb(255, 203, 219)', 'Pink'], + ['rgb(255, 255, 255)', 'White'], + ['rgb(229, 17, 1)', 'RGB Red'], + ['rgb( 0, 255, 0)', 'RGB Green'], + ['rgb( 0, 0, 255)', 'RGB Blue'], + ['rgb( 0, 255, 255)', 'CMYK Cyan'], + ['rgb(255, 0, 255)', 'CMYK Magenta'], + ['rgb(255, 255, 0)', 'CMYK Yellow'], +]; + +// re-arrange to the right order for display +let palReordered = []; +for (let row = 0; row < 7; row++) { + for (let col = 0; col < 11; col++) { + palReordered.push(palette[col * 7 + row]); + } + palReordered.push(null); // null indicates a
    +} + +// Utility for converting base64 image to binary for upload +// https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f +function dataURLtoFile(dataurl, filename) { + let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); + while(n--){ + u8arr[n] = bstr.charCodeAt(n); + } + return new File([u8arr], filename, { type: mime }); +} +/** Doodle canvas size options */ +const DOODLE_SIZES = { + normal: [500, 500, 'Square 500'], + tootbanner: [702, 330, 'Tootbanner'], + s640x480: [640, 480, '640×480 - 480p'], + s800x600: [800, 600, '800×600 - SVGA'], + s720x480: [720, 405, '720x405 - 16:9'], +}; + + +const mapStateToProps = state => ({ + options: state.getIn(['compose', 'doodle']), +}); + +const mapDispatchToProps = dispatch => ({ + /** Set options in the redux store */ + setOpt: (opts) => dispatch(doodleSet(opts)), + /** Submit doodle for upload */ + submit: (file) => dispatch(uploadCompose([file])), +}); + +/** + * Doodling dialog with drawing canvas + * + * Keyboard shortcuts: + * - Delete: Clear screen, fill with background color + * - Backspace, Ctrl+Z: Undo one step + * - Ctrl held while drawing: Use background color + * - Shift held while clicking screen: Use fill tool + * + * Palette: + * - Left mouse button: pick foreground + * - Ctrl + left mouse button: pick background + * - Right mouse button: pick background + */ +export default @connect(mapStateToProps, mapDispatchToProps) +class DoodleModal extends ImmutablePureComponent { + + static propTypes = { + options: ImmutablePropTypes.map, + onClose: PropTypes.func.isRequired, + setOpt: PropTypes.func.isRequired, + submit: PropTypes.func.isRequired, + }; + + //region Option getters/setters + + /** Foreground color */ + get fg () { + return this.props.options.get('fg'); + } + set fg (value) { + this.props.setOpt({ fg: value }); + } + + /** Background color */ + get bg () { + return this.props.options.get('bg'); + } + set bg (value) { + this.props.setOpt({ bg: value }); + } + + /** Swap Fg and Bg for drawing */ + get swapped () { + return this.props.options.get('swapped'); + } + set swapped (value) { + this.props.setOpt({ swapped: value }); + } + + /** Mode - 'draw' or 'fill' */ + get mode () { + return this.props.options.get('mode'); + } + set mode (value) { + this.props.setOpt({ mode: value }); + } + + /** Base line weight */ + get weight () { + return this.props.options.get('weight'); + } + set weight (value) { + this.props.setOpt({ weight: value }); + } + + /** Drawing opacity */ + get opacity () { + return this.props.options.get('opacity'); + } + set opacity (value) { + this.props.setOpt({ opacity: value }); + } + + /** Adaptive stroke - change width with speed */ + get adaptiveStroke () { + return this.props.options.get('adaptiveStroke'); + } + set adaptiveStroke (value) { + this.props.setOpt({ adaptiveStroke: value }); + } + + /** Smoothing (for mouse drawing) */ + get smoothing () { + return this.props.options.get('smoothing'); + } + set smoothing (value) { + this.props.setOpt({ smoothing: value }); + } + + /** Size preset */ + get size () { + return this.props.options.get('size'); + } + set size (value) { + this.props.setOpt({ size: value }); + } + + //endregion + + /** Key up handler */ + handleKeyUp = (e) => { + if (e.target.nodeName === 'INPUT') return; + + if (e.key === 'Delete') { + e.preventDefault(); + this.handleClearBtn(); + return; + } + + if (e.key === 'Backspace' || (e.key === 'z' && (e.ctrlKey || e.metaKey))) { + e.preventDefault(); + this.undo(); + } + + if (e.key === 'Control' || e.key === 'Meta') { + this.controlHeld = false; + this.swapped = false; + } + + if (e.key === 'Shift') { + this.shiftHeld = false; + this.mode = 'draw'; + } + }; + + /** Key down handler */ + handleKeyDown = (e) => { + if (e.key === 'Control' || e.key === 'Meta') { + this.controlHeld = true; + this.swapped = true; + } + + if (e.key === 'Shift') { + this.shiftHeld = true; + this.mode = 'fill'; + } + }; + + /** + * Component installed in the DOM, do some initial set-up + */ + componentDidMount () { + this.controlHeld = false; + this.shiftHeld = false; + this.swapped = false; + window.addEventListener('keyup', this.handleKeyUp, false); + window.addEventListener('keydown', this.handleKeyDown, false); + } + + /** + * Tear component down + */ + componentWillUnmount () { + window.removeEventListener('keyup', this.handleKeyUp, false); + window.removeEventListener('keydown', this.handleKeyDown, false); + if (this.sketcher) this.sketcher.destroy(); + } + + /** + * Set reference to the canvas element. + * This is called during component init + * + * @param elem - canvas element + */ + setCanvasRef = (elem) => { + this.canvas = elem; + if (elem) { + elem.addEventListener('dirty', () => { + this.saveUndo(); + this.sketcher._dirty = false; + }); + + elem.addEventListener('click', () => { + // sketcher bug - does not fire dirty on fill + if (this.mode === 'fill') { + this.saveUndo(); + } + }); + + // prevent context menu + elem.addEventListener('contextmenu', (e) => { + e.preventDefault(); + }); + + elem.addEventListener('mousedown', (e) => { + if (e.button === 2) { + this.swapped = true; + } + }); + + elem.addEventListener('mouseup', (e) => { + if (e.button === 2) { + this.swapped = this.controlHeld; + } + }); + + this.initSketcher(elem); + this.mode = 'draw'; // Reset mode - it's confusing if left at 'fill' + } + }; + + /** + * Set up the sketcher instance + * + * @param canvas - canvas element. Null if we're just resizing + */ + initSketcher (canvas = null) { + const sizepreset = DOODLE_SIZES[this.size]; + + if (this.sketcher) this.sketcher.destroy(); + this.sketcher = new Atrament(canvas || this.canvas, sizepreset[0], sizepreset[1]); + + if (canvas) { + this.ctx = this.sketcher.context; + this.updateSketcherSettings(); + } + + this.clearScreen(); + } + + /** + * Done button handler + */ + onDoneButton = () => { + const dataUrl = this.sketcher.toImage(); + const file = dataURLtoFile(dataUrl, 'doodle.png'); + this.props.submit(file); + this.props.onClose(); // close dialog + }; + + /** + * Cancel button handler + */ + onCancelButton = () => { + if (this.undos.length > 1 && !confirm('Discard doodle? All changes will be lost!')) { + return; + } + + this.props.onClose(); // close dialog + }; + + /** + * Update sketcher options based on state + */ + updateSketcherSettings () { + if (!this.sketcher) return; + + if (this.oldSize !== this.size) this.initSketcher(); + + this.sketcher.color = (this.swapped ? this.bg : this.fg); + this.sketcher.opacity = this.opacity; + this.sketcher.weight = this.weight; + this.sketcher.mode = this.mode; + this.sketcher.smoothing = this.smoothing; + this.sketcher.adaptiveStroke = this.adaptiveStroke; + + this.oldSize = this.size; + } + + /** + * Fill screen with background color + */ + clearScreen = () => { + this.ctx.fillStyle = this.bg; + this.ctx.fillRect(-1, -1, this.canvas.width+2, this.canvas.height+2); + this.undos = []; + + this.doSaveUndo(); + }; + + /** + * Undo one step + */ + undo = () => { + if (this.undos.length > 1) { + this.undos.pop(); + const buf = this.undos.pop(); + + this.sketcher.clear(); + this.ctx.putImageData(buf, 0, 0); + this.doSaveUndo(); + } + }; + + /** + * Save canvas content into the undo buffer immediately + */ + doSaveUndo = () => { + this.undos.push(this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)); + }; + + /** + * Called on each canvas change. + * Saves canvas content to the undo buffer after some period of inactivity. + */ + saveUndo = debounce(() => { + this.doSaveUndo(); + }, 100); + + /** + * Palette left click. + * Selects Fg color (or Bg, if Control/Meta is held) + * + * @param e - event + */ + onPaletteClick = (e) => { + const c = e.target.dataset.color; + + if (this.controlHeld) { + this.bg = c; + } else { + this.fg = c; + } + + e.target.blur(); + e.preventDefault(); + }; + + /** + * Palette right click. + * Selects Bg color + * + * @param e - event + */ + onPaletteRClick = (e) => { + this.bg = e.target.dataset.color; + e.target.blur(); + e.preventDefault(); + }; + + /** + * Handle click on the Draw mode button + * + * @param e - event + */ + setModeDraw = (e) => { + this.mode = 'draw'; + e.target.blur(); + }; + + /** + * Handle click on the Fill mode button + * + * @param e - event + */ + setModeFill = (e) => { + this.mode = 'fill'; + e.target.blur(); + }; + + /** + * Handle click on Smooth checkbox + * + * @param e - event + */ + tglSmooth = (e) => { + this.smoothing = !this.smoothing; + e.target.blur(); + }; + + /** + * Handle click on Adaptive checkbox + * + * @param e - event + */ + tglAdaptive = (e) => { + this.adaptiveStroke = !this.adaptiveStroke; + e.target.blur(); + }; + + /** + * Handle change of the Weight input field + * + * @param e - event + */ + setWeight = (e) => { + this.weight = +e.target.value || 1; + }; + + /** + * Set size - clalback from the select box + * + * @param e - event + */ + changeSize = (e) => { + let newSize = e.target.value; + if (newSize === this.oldSize) return; + + if (this.undos.length > 1 && !confirm('Change canvas size? This will erase your current drawing!')) { + return; + } + + this.size = newSize; + }; + + handleClearBtn = () => { + if (this.undos.length > 1 && !confirm('Clear canvas? This will erase your current drawing!')) { + return; + } + + this.clearScreen(); + }; + + /** + * Render the component + */ + render () { + this.updateSketcherSettings(); + + return ( +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + +
    +
    + + + + +
    +
    + + + + +
    +
    + +
    +
    +
    + + + + +
    +
    + { + palReordered.map((c, i) => + c === null ? +
    : +
    +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/drawer_loading.js b/app/javascript/flavours/glitch/features/ui/components/drawer_loading.js deleted file mode 100644 index 08b0d2347..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/drawer_loading.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const DrawerLoading = () => ( -
    -
    -
    -
    -
    -); - -export default DrawerLoading; diff --git a/app/javascript/flavours/glitch/features/ui/components/drawer_loading.jsx b/app/javascript/flavours/glitch/features/ui/components/drawer_loading.jsx new file mode 100644 index 000000000..08b0d2347 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/drawer_loading.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const DrawerLoading = () => ( +
    +
    +
    +
    +
    +); + +export default DrawerLoading; diff --git a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js b/app/javascript/flavours/glitch/features/ui/components/embed_modal.js deleted file mode 100644 index 92bfa79c4..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; -import api from 'flavours/glitch/api'; -import IconButton from 'flavours/glitch/components/icon_button'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' }, -}); - -export default @injectIntl -class EmbedModal extends ImmutablePureComponent { - - static propTypes = { - url: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - onError: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - loading: false, - oembed: null, - }; - - componentDidMount () { - const { url } = this.props; - - this.setState({ loading: true }); - - api().post('/api/web/embed', { url }).then(res => { - this.setState({ loading: false, oembed: res.data }); - - const iframeDocument = this.iframe.contentWindow.document; - - iframeDocument.open(); - iframeDocument.write(res.data.html); - iframeDocument.close(); - - iframeDocument.body.style.margin = 0; - this.iframe.width = iframeDocument.body.scrollWidth; - this.iframe.height = iframeDocument.body.scrollHeight; - }).catch(error => { - this.props.onError(error); - }); - } - - setIframeRef = c => { - this.iframe = c; - }; - - handleTextareaClick = (e) => { - e.target.select(); - }; - - render () { - const { intl, onClose } = this.props; - const { oembed } = this.state; - - return ( -
    -
    - - -
    - -
    -

    - -

    - - - -

    - -

    - -