mirror of
https://github.com/mastodon/mastodon.git
synced 2024-12-12 13:26:45 +00:00
Change design of modal loading and error screens in web UI (#33092)
This commit is contained in:
parent
eef8d2c855
commit
7f2cfcccab
22
app/javascript/mastodon/components/gif.tsx
Normal file
22
app/javascript/mastodon/components/gif.tsx
Normal file
|
@ -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 (
|
||||||
|
<img
|
||||||
|
className={className}
|
||||||
|
src={hovering || animate ? src : staticSrc}
|
||||||
|
alt=''
|
||||||
|
role='presentation'
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -9,58 +9,7 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import Column from 'mastodon/components/column';
|
import Column from 'mastodon/components/column';
|
||||||
import { autoPlayGif } from 'mastodon/initial_state';
|
import { GIF } from 'mastodon/components/gif';
|
||||||
|
|
||||||
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 (
|
|
||||||
<img
|
|
||||||
className={className}
|
|
||||||
src={(hovering || animate) ? src : staticSrc}
|
|
||||||
alt=''
|
|
||||||
role='presentation'
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class CopyButton extends PureComponent {
|
class CopyButton extends PureComponent {
|
||||||
|
|
||||||
|
|
|
@ -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 <ModalLoading />
|
|
||||||
// (make sure they have the same dimensions)
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal error-modal'>
|
|
||||||
<div className='error-modal__body'>
|
|
||||||
<IconButton title={formatMessage(messages.retry)} icon='refresh' iconComponent={RefreshIcon} onClick={this.handleRetry} size={64} />
|
|
||||||
{formatMessage(messages.error)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='error-modal__footer'>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className='error-modal__nav onboarding-modal__skip'
|
|
||||||
>
|
|
||||||
{formatMessage(messages.close)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(BundleModalError);
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { LoadingIndicator } from '../../../components/loading_indicator';
|
|
||||||
|
|
||||||
// Keep the markup in sync with <BundleModalError />
|
|
||||||
// (make sure they have the same dimensions)
|
|
||||||
const ModalLoading = () => (
|
|
||||||
<div className='modal-root__modal error-modal'>
|
|
||||||
<div className='error-modal__body'>
|
|
||||||
<LoadingIndicator />
|
|
||||||
</div>
|
|
||||||
<div className='error-modal__footer'>
|
|
||||||
<div>
|
|
||||||
<button className='error-modal__nav onboarding-modal__skip' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ModalLoading;
|
|
|
@ -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 (
|
||||||
|
<div className='modal-root__modal modal-placeholder' aria-busy={loading}>
|
||||||
|
{loading ? (
|
||||||
|
<LoadingIndicator />
|
||||||
|
) : (
|
||||||
|
<div className='modal-placeholder__error'>
|
||||||
|
<GIF
|
||||||
|
src='/oops.gif'
|
||||||
|
staticSrc='/oops.png'
|
||||||
|
className='modal-placeholder__error__image'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='modal-placeholder__error__message'>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id='bundle_modal_error.message'
|
||||||
|
defaultMessage='Something went wrong while loading this screen.'
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className='modal-placeholder__error__message__actions'>
|
||||||
|
<Button onClick={handleRetry}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='bundle_modal_error.retry'
|
||||||
|
defaultMessage='Try again'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleClose} className='button button-tertiary'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='bundle_modal_error.close'
|
||||||
|
defaultMessage='Close'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -26,7 +26,6 @@ import BundleContainer from '../containers/bundle_container';
|
||||||
import ActionsModal from './actions_modal';
|
import ActionsModal from './actions_modal';
|
||||||
import AudioModal from './audio_modal';
|
import AudioModal from './audio_modal';
|
||||||
import { BoostModal } from './boost_modal';
|
import { BoostModal } from './boost_modal';
|
||||||
import BundleModalError from './bundle_modal_error';
|
|
||||||
import {
|
import {
|
||||||
ConfirmationModal,
|
ConfirmationModal,
|
||||||
ConfirmDeleteStatusModal,
|
ConfirmDeleteStatusModal,
|
||||||
|
@ -40,7 +39,7 @@ import {
|
||||||
import FocalPointModal from './focal_point_modal';
|
import FocalPointModal from './focal_point_modal';
|
||||||
import ImageModal from './image_modal';
|
import ImageModal from './image_modal';
|
||||||
import MediaModal from './media_modal';
|
import MediaModal from './media_modal';
|
||||||
import ModalLoading from './modal_loading';
|
import { ModalPlaceholder } from './modal_placeholder';
|
||||||
import VideoModal from './video_modal';
|
import VideoModal from './video_modal';
|
||||||
|
|
||||||
export const MODAL_COMPONENTS = {
|
export const MODAL_COMPONENTS = {
|
||||||
|
@ -105,14 +104,16 @@ export default class ModalRoot extends PureComponent {
|
||||||
this.setState({ backgroundColor: color });
|
this.setState({ backgroundColor: color });
|
||||||
};
|
};
|
||||||
|
|
||||||
renderLoading = modalId => () => {
|
renderLoading = () => {
|
||||||
return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
const { onClose } = this.props;
|
||||||
|
|
||||||
|
return <ModalPlaceholder loading onClose={onClose} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderError = (props) => {
|
renderError = (props) => {
|
||||||
const { onClose } = this.props;
|
const { onClose } = this.props;
|
||||||
|
|
||||||
return <BundleModalError {...props} onClose={onClose} />;
|
return <ModalPlaceholder {...props} onClose={onClose} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClose = (ignoreFocus = false) => {
|
handleClose = (ignoreFocus = false) => {
|
||||||
|
@ -134,7 +135,7 @@ export default class ModalRoot extends PureComponent {
|
||||||
<Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}>
|
<Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}>
|
||||||
{visible && (
|
{visible && (
|
||||||
<>
|
<>
|
||||||
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
|
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
|
||||||
{(SpecificComponent) => {
|
{(SpecificComponent) => {
|
||||||
const ref = typeof SpecificComponent !== 'function' ? this.setModalRef : undefined;
|
const ref = typeof SpecificComponent !== 'function' ? this.setModalRef : undefined;
|
||||||
return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={ref} />;
|
return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={ref} />;
|
||||||
|
|
|
@ -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.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_column_error.routing.title": "404",
|
||||||
"bundle_modal_error.close": "Close",
|
"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",
|
"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.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.",
|
"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.",
|
||||||
|
|
|
@ -5849,116 +5849,41 @@ a.status-card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-modal,
|
.modal-placeholder {
|
||||||
.error-modal,
|
width: 588px;
|
||||||
.embed-modal {
|
min-height: 478px;
|
||||||
background: $ui-secondary-color;
|
flex-direction: column;
|
||||||
color: $inverted-text-color;
|
background: var(--modal-background-color);
|
||||||
border-radius: 8px;
|
backdrop-filter: var(--background-filter);
|
||||||
overflow: hidden;
|
border: 1px solid var(--modal-border-color);
|
||||||
|
border-radius: 16px;
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
padding: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
align-items: center;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
display: flex;
|
|
||||||
opacity: 0;
|
&__image {
|
||||||
user-select: text;
|
width: 70%;
|
||||||
|
max-width: 350px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.error-modal__body {
|
&__message {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
text-wrap: balance;
|
||||||
|
|
||||||
.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-size: 14px;
|
||||||
font-weight: 500;
|
line-height: 20px;
|
||||||
padding: 10px 25px;
|
letter-spacing: 0.25px;
|
||||||
line-height: inherit;
|
|
||||||
height: auto;
|
|
||||||
margin: -10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
&:hover,
|
&__actions {
|
||||||
&:focus,
|
margin-top: 24px;
|
||||||
&:active {
|
display: flex;
|
||||||
color: darken($lighter-text-color, 4%);
|
gap: 10px;
|
||||||
background-color: darken($ui-secondary-color, 16%);
|
align-items: center;
|
||||||
}
|
|
||||||
|
|
||||||
&.onboarding-modal__done,
|
|
||||||
&.onboarding-modal__next {
|
|
||||||
color: $inverted-text-color;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
color: lighten($inverted-text-color, 4%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-modal__footer {
|
|
||||||
justify-content: center;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue