modal_root.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
  4. import Base from 'mastodon/components/modal_root';
  5. import BundleContainer from '../containers/bundle_container';
  6. import BundleModalError from './bundle_modal_error';
  7. import ModalLoading from './modal_loading';
  8. import ActionsModal from './actions_modal';
  9. import MediaModal from './media_modal';
  10. import VideoModal from './video_modal';
  11. import BoostModal from './boost_modal';
  12. import AudioModal from './audio_modal';
  13. import ConfirmationModal from './confirmation_modal';
  14. import FocalPointModal from './focal_point_modal';
  15. import {
  16. MuteModal,
  17. BlockModal,
  18. ReportModal,
  19. EmbedModal,
  20. ListEditor,
  21. ListAdder,
  22. CompareHistoryModal,
  23. FilterModal,
  24. InteractionModal,
  25. SubscribedLanguagesModal,
  26. ClosedRegistrationsModal,
  27. } from 'mastodon/features/ui/util/async-components';
  28. import { Helmet } from 'react-helmet';
  29. const MODAL_COMPONENTS = {
  30. 'MEDIA': () => Promise.resolve({ default: MediaModal }),
  31. 'VIDEO': () => Promise.resolve({ default: VideoModal }),
  32. 'AUDIO': () => Promise.resolve({ default: AudioModal }),
  33. 'BOOST': () => Promise.resolve({ default: BoostModal }),
  34. 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
  35. 'MUTE': MuteModal,
  36. 'BLOCK': BlockModal,
  37. 'REPORT': ReportModal,
  38. 'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
  39. 'EMBED': EmbedModal,
  40. 'LIST_EDITOR': ListEditor,
  41. 'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
  42. 'LIST_ADDER': ListAdder,
  43. 'COMPARE_HISTORY': CompareHistoryModal,
  44. 'FILTER': FilterModal,
  45. 'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal,
  46. 'INTERACTION': InteractionModal,
  47. 'CLOSED_REGISTRATIONS': ClosedRegistrationsModal,
  48. };
  49. export default class ModalRoot extends React.PureComponent {
  50. static propTypes = {
  51. type: PropTypes.string,
  52. props: PropTypes.object,
  53. onClose: PropTypes.func.isRequired,
  54. ignoreFocus: PropTypes.bool,
  55. };
  56. state = {
  57. backgroundColor: null,
  58. };
  59. getSnapshotBeforeUpdate () {
  60. return { visible: !!this.props.type };
  61. }
  62. componentDidUpdate (prevProps, prevState, { visible }) {
  63. if (visible) {
  64. document.body.classList.add('with-modals--active');
  65. document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
  66. } else {
  67. document.body.classList.remove('with-modals--active');
  68. document.documentElement.style.marginRight = 0;
  69. }
  70. }
  71. setBackgroundColor = color => {
  72. this.setState({ backgroundColor: color });
  73. }
  74. renderLoading = modalId => () => {
  75. return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
  76. }
  77. renderError = (props) => {
  78. const { onClose } = this.props;
  79. return <BundleModalError {...props} onClose={onClose} />;
  80. }
  81. handleClose = (ignoreFocus = false) => {
  82. const { onClose } = this.props;
  83. let message = null;
  84. try {
  85. message = this._modal?.getWrappedInstance?.().getCloseConfirmationMessage?.();
  86. } catch (_) {
  87. // injectIntl defines `getWrappedInstance` but errors out if `withRef`
  88. // isn't set.
  89. // This would be much smoother with react-intl 3+ and `forwardRef`.
  90. }
  91. onClose(message, ignoreFocus);
  92. }
  93. setModalRef = (c) => {
  94. this._modal = c;
  95. }
  96. render () {
  97. const { type, props, ignoreFocus } = this.props;
  98. const { backgroundColor } = this.state;
  99. const visible = !!type;
  100. return (
  101. <Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}>
  102. {visible && (
  103. <>
  104. <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
  105. {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />}
  106. </BundleContainer>
  107. <Helmet>
  108. <meta name='robots' content='noindex' />
  109. </Helmet>
  110. </>
  111. )}
  112. </Base>
  113. );
  114. }
  115. }