diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 1cef029d89..13e1fcc524 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -14,10 +14,6 @@ const messages = defineMessages({ class Item extends React.PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { attachment: ImmutablePropTypes.map.isRequired, standalone: PropTypes.bool, @@ -53,7 +49,7 @@ class Item extends React.PureComponent { handleClick = (e) => { const { index, onClick } = this.props; - if (this.context.router && e.button === 0) { + if (e.button === 0) { e.preventDefault(); onClick(index); } diff --git a/app/javascript/mastodon/components/modal_root.js b/app/javascript/mastodon/components/modal_root.js new file mode 100644 index 0000000000..114f74937d --- /dev/null +++ b/app/javascript/mastodon/components/modal_root.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class ModalRoot extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + onClose: PropTypes.func.isRequired, + }; + + state = { + revealed: !!this.props.children, + }; + + activeElement = this.state.revealed ? document.activeElement : null; + + handleKeyUp = (e) => { + if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) + && !!this.props.children) { + this.props.onClose(); + } + } + + componentDidMount () { + window.addEventListener('keyup', this.handleKeyUp, false); + } + + componentWillReceiveProps (nextProps) { + if (!!nextProps.children && !this.props.children) { + this.activeElement = document.activeElement; + + this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true)); + } else if (!nextProps.children) { + this.setState({ revealed: false }); + } + } + + componentDidUpdate (prevProps) { + if (!this.props.children && !!prevProps.children) { + this.getSiblings().forEach(sibling => sibling.removeAttribute('inert')); + this.activeElement.focus(); + this.activeElement = null; + } + if (this.props.children) { + requestAnimationFrame(() => { + this.setState({ revealed: true }); + }); + } + } + + componentWillUnmount () { + window.removeEventListener('keyup', this.handleKeyUp); + } + + getSiblings = () => { + return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node); + } + + setRef = ref => { + this.node = ref; + } + + render () { + const { children, onClose } = this.props; + const { revealed } = this.state; + const visible = !!children; + + if (!visible) { + return ( +
+ ); + } + + return ( +
+
+
+
{children}
+
+
+ ); + } + +} diff --git a/app/javascript/mastodon/containers/media_galleries_container.js b/app/javascript/mastodon/containers/media_galleries_container.js new file mode 100644 index 0000000000..d77bd688bf --- /dev/null +++ b/app/javascript/mastodon/containers/media_galleries_container.js @@ -0,0 +1,68 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import { IntlProvider, addLocaleData } from 'react-intl'; +import { getLocale } from '../locales'; +import MediaGallery from '../components/media_gallery'; +import ModalRoot from '../components/modal_root'; +import MediaModal from '../features/ui/components/media_modal'; +import { fromJS } from 'immutable'; + +const { localeData, messages } = getLocale(); +addLocaleData(localeData); + +export default class MediaGalleriesContainer extends React.PureComponent { + + static propTypes = { + locale: PropTypes.string.isRequired, + galleries: PropTypes.object.isRequired, + }; + + state = { + media: null, + index: null, + }; + + handleOpenMedia = (media, index) => { + document.body.classList.add('media-gallery-standalone__body'); + this.setState({ media, index }); + } + + handleCloseMedia = () => { + document.body.classList.remove('media-gallery-standalone__body'); + this.setState({ media: null, index: null }); + } + + render () { + const { locale, galleries } = this.props; + + return ( + + + {[].map.call(galleries, gallery => { + const { media, ...props } = JSON.parse(gallery.getAttribute('data-props')); + + return ReactDOM.createPortal( + , + gallery + ); + })} + + {this.state.media === null || this.state.index === null ? null : ( + + )} + + + + ); + } + +} diff --git a/app/javascript/mastodon/containers/media_gallery_container.js b/app/javascript/mastodon/containers/media_gallery_container.js deleted file mode 100644 index 812c3d4e50..0000000000 --- a/app/javascript/mastodon/containers/media_gallery_container.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { IntlProvider, addLocaleData } from 'react-intl'; -import { getLocale } from '../locales'; -import MediaGallery from '../components/media_gallery'; -import { fromJS } from 'immutable'; - -const { localeData, messages } = getLocale(); -addLocaleData(localeData); - -export default class MediaGalleryContainer extends React.PureComponent { - - static propTypes = { - locale: PropTypes.string.isRequired, - media: PropTypes.array.isRequired, - }; - - handleOpenMedia = () => {} - - render () { - const { locale, media, ...props } = this.props; - - return ( - - - - ); - } - -} diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index 20bf21153c..4185cba322 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Base from '../../../components/modal_root'; import BundleContainer from '../containers/bundle_container'; import BundleModalError from './bundle_modal_error'; import ModalLoading from './modal_loading'; @@ -39,56 +40,6 @@ export default class ModalRoot extends React.PureComponent { onClose: PropTypes.func.isRequired, }; - state = { - revealed: false, - }; - - handleKeyUp = (e) => { - if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) - && !!this.props.type) { - this.props.onClose(); - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillReceiveProps (nextProps) { - if (!!nextProps.type && !this.props.type) { - this.activeElement = document.activeElement; - - this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true)); - } else if (!nextProps.type) { - this.setState({ revealed: false }); - } - } - - componentDidUpdate (prevProps) { - if (!this.props.type && !!prevProps.type) { - this.getSiblings().forEach(sibling => sibling.removeAttribute('inert')); - this.activeElement.focus(); - this.activeElement = null; - } - if (this.props.type) { - requestAnimationFrame(() => { - this.setState({ revealed: true }); - }); - } - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - getSiblings = () => { - return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node); - } - - setRef = ref => { - this.node = ref; - } - renderLoading = modalId => () => { return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null; } @@ -101,28 +52,16 @@ export default class ModalRoot extends React.PureComponent { render () { const { type, props, onClose } = this.props; - const { revealed } = this.state; const visible = !!type; - if (!visible) { - return ( -
- ); - } - return ( -
-
-
-
- {visible && ( - - {(SpecificComponent) => } - - )} -
-
-
+ + {visible && ( + + {(SpecificComponent) => } + + )} + ); } diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index a47fc28301..7096b9b4f2 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -25,7 +25,6 @@ function main() { const { getLocale } = require('../mastodon/locales'); const { localeData } = getLocale(); const VideoContainer = require('../mastodon/containers/video_container').default; - const MediaGalleryContainer = require('../mastodon/containers/media_gallery_container').default; const CardContainer = require('../mastodon/containers/card_container').default; const React = require('react'); const ReactDOM = require('react-dom'); @@ -76,15 +75,20 @@ function main() { ReactDOM.render(, content); }); - [].forEach.call(document.querySelectorAll('[data-component="MediaGallery"]'), (content) => { - const props = JSON.parse(content.getAttribute('data-props')); - ReactDOM.render(, content); - }); - [].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => { const props = JSON.parse(content.getAttribute('data-props')); ReactDOM.render(, content); }); + + const mediaGalleries = document.querySelectorAll('[data-component="MediaGallery"]'); + + if (mediaGalleries.length > 0) { + const MediaGalleriesContainer = require('../mastodon/containers/media_galleries_container').default; + const content = document.createElement('div'); + + ReactDOM.render(, content); + document.body.appendChild(content); + } }); delegate(document, '.webapp-btn', 'click', ({ target, button }) => { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 20e07a042e..ea6e393924 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3375,13 +3375,14 @@ a.status-card { } .modal-root { + position: relative; transition: opacity 0.3s linear; will-change: opacity; z-index: 9999; } .modal-root__overlay { - position: absolute; + position: fixed; top: 0; left: 0; right: 0; @@ -3390,7 +3391,7 @@ a.status-card { } .modal-root__container { - position: absolute; + position: fixed; top: 0; left: 0; width: 100%; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 6fa1fa38f5..e761f58eb2 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -60,6 +60,10 @@ } } +.media-gallery-standalone__body { + overflow: hidden; +} + .account-header { width: 400px; margin: 0 auto;