diff --git a/app/javascript/mastodon/components/gif.tsx b/app/javascript/mastodon/components/gif.tsx new file mode 100644 index 0000000000..8fbcb8c76b --- /dev/null +++ b/app/javascript/mastodon/components/gif.tsx @@ -0,0 +1,22 @@ +import { useHovering } from '@/hooks/useHovering'; +import { autoPlayGif } from 'mastodon/initial_state'; + +export const GIF: React.FC<{ + src: string; + staticSrc: string; + className: string; + animate?: boolean; +}> = ({ src, staticSrc, className, animate = autoPlayGif }) => { + const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate); + + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx index eb6682d77b..beaf185638 100644 --- a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx +++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx @@ -9,58 +9,7 @@ import { Link } from 'react-router-dom'; import { Button } from 'mastodon/components/button'; import Column from 'mastodon/components/column'; -import { autoPlayGif } from 'mastodon/initial_state'; - -class GIF extends 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 ( - - ); - } - -} +import { GIF } from 'mastodon/components/gif'; class CopyButton extends PureComponent { diff --git a/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx b/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx deleted file mode 100644 index d1c9e36883..0000000000 --- a/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { defineMessages, injectIntl } from 'react-intl'; - -import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; - -import { IconButton } from '../../../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 PureComponent { - - 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/mastodon/features/ui/components/modal_loading.jsx b/app/javascript/mastodon/features/ui/components/modal_loading.jsx deleted file mode 100644 index 7d19e73513..0000000000 --- a/app/javascript/mastodon/features/ui/components/modal_loading.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import { LoadingIndicator } from '../../../components/loading_indicator'; - -// Keep the markup in sync with -// (make sure they have the same dimensions) -const ModalLoading = () => ( -
-
- -
-
-
-
-
-
-); - -export default ModalLoading; diff --git a/app/javascript/mastodon/features/ui/components/modal_placeholder.tsx b/app/javascript/mastodon/features/ui/components/modal_placeholder.tsx new file mode 100644 index 0000000000..13ec6ca2c8 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/modal_placeholder.tsx @@ -0,0 +1,61 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Button } from 'mastodon/components/button'; +import { GIF } from 'mastodon/components/gif'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; + +export const ModalPlaceholder: React.FC<{ + loading: boolean; + onClose: (arg0: string | undefined, arg1: boolean) => void; + onRetry?: () => void; +}> = ({ loading, onClose, onRetry }) => { + const handleClose = useCallback(() => { + onClose(undefined, false); + }, [onClose]); + + const handleRetry = useCallback(() => { + if (onRetry) onRetry(); + }, [onRetry]); + + return ( +
+ {loading ? ( + + ) : ( +
+ + +
+

+ +

+ +
+ + +
+
+
+ )} +
+ ); +}; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 8a97ec4565..c3446d9be2 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -26,7 +26,6 @@ import BundleContainer from '../containers/bundle_container'; import ActionsModal from './actions_modal'; import AudioModal from './audio_modal'; import { BoostModal } from './boost_modal'; -import BundleModalError from './bundle_modal_error'; import { ConfirmationModal, ConfirmDeleteStatusModal, @@ -40,7 +39,7 @@ import { import FocalPointModal from './focal_point_modal'; import ImageModal from './image_modal'; import MediaModal from './media_modal'; -import ModalLoading from './modal_loading'; +import { ModalPlaceholder } from './modal_placeholder'; import VideoModal from './video_modal'; export const MODAL_COMPONENTS = { @@ -105,14 +104,16 @@ export default class ModalRoot extends PureComponent { this.setState({ backgroundColor: color }); }; - renderLoading = modalId => () => { - return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null; + renderLoading = () => { + const { onClose } = this.props; + + return ; }; renderError = (props) => { const { onClose } = this.props; - return ; + return ; }; handleClose = (ignoreFocus = false) => { @@ -134,7 +135,7 @@ export default class ModalRoot extends PureComponent { {visible && ( <> - + {(SpecificComponent) => { const ref = typeof SpecificComponent !== 'function' ? this.setModalRef : undefined; return ; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 71ab620226..44910c6280 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -129,7 +129,7 @@ "bundle_column_error.routing.body": "The requested page could not be found. Are you sure the URL in the address bar is correct?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.message": "Something went wrong while loading this screen.", "bundle_modal_error.retry": "Try again", "closed_registrations.other_server_instructions": "Since Mastodon is decentralized, you can create an account on another server and still interact with this one.", "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index df3a770648..b5ab74858d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -5849,119 +5849,44 @@ a.status-card { } } -.onboarding-modal, -.error-modal, -.embed-modal { - background: $ui-secondary-color; - color: $inverted-text-color; - border-radius: 8px; - overflow: hidden; - display: flex; +.modal-placeholder { + width: 588px; + min-height: 478px; flex-direction: column; -} + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + border-radius: 16px; -.error-modal__body { - height: 80vh; - width: 80vw; - max-width: 520px; - max-height: 420px; - position: relative; - - & > div { - position: absolute; - top: 0; - inset-inline-start: 0; - width: 100%; - height: 100%; - box-sizing: border-box; - padding: 25px; - flex-direction: column; - align-items: center; - justify-content: center; + &__error { + padding: 24px; display: flex; - opacity: 0; - user-select: text; - } -} + align-items: center; + flex-direction: column; -.error-modal__body { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; -} - -.onboarding-modal__paginator, -.error-modal__footer { - flex: 0 0 auto; - background: darken($ui-secondary-color, 8%); - display: flex; - padding: 25px; - - & > div { - min-width: 33px; - } - - .onboarding-modal__nav, - .error-modal__nav { - color: $lighter-text-color; - border: 0; - font-size: 14px; - font-weight: 500; - padding: 10px 25px; - line-height: inherit; - height: auto; - margin: -10px; - border-radius: 4px; - background-color: transparent; - - &:hover, - &:focus, - &:active { - color: darken($lighter-text-color, 4%); - background-color: darken($ui-secondary-color, 16%); + &__image { + width: 70%; + max-width: 350px; } - &.onboarding-modal__done, - &.onboarding-modal__next { - color: $inverted-text-color; + &__message { + text-align: center; + text-wrap: balance; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; - &:hover, - &:focus, - &:active { - color: lighten($inverted-text-color, 4%); + &__actions { + margin-top: 24px; + display: flex; + gap: 10px; + align-items: center; + justify-content: center; } } } } -.error-modal__footer { - justify-content: center; -} - -.display-case { - text-align: center; - font-size: 15px; - margin-bottom: 15px; - - &__label { - font-weight: 500; - color: $inverted-text-color; - margin-bottom: 5px; - text-transform: uppercase; - font-size: 12px; - } - - &__case { - background: $ui-base-color; - color: $secondary-text-color; - font-weight: 500; - padding: 10px; - border-radius: 4px; - } -} - .safety-action-modal { width: 600px; flex-direction: column;