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;