mirror of https://github.com/mastodon/mastodon
Change how the modal stack is handled
This commit is contained in:
parent
3dcf5e12b1
commit
b21c226396
|
@ -9,7 +9,6 @@ export type ModalType = keyof typeof MODAL_COMPONENTS;
|
||||||
interface OpenModalPayload {
|
interface OpenModalPayload {
|
||||||
modalType: ModalType;
|
modalType: ModalType;
|
||||||
modalProps: ModalProps;
|
modalProps: ModalProps;
|
||||||
previousModalProps?: ModalProps;
|
|
||||||
}
|
}
|
||||||
export const openModal = createAction<OpenModalPayload>('MODAL_OPEN');
|
export const openModal = createAction<OpenModalPayload>('MODAL_OPEN');
|
||||||
|
|
||||||
|
|
|
@ -262,11 +262,6 @@ const Preview: React.FC<{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interface RestoreProps {
|
|
||||||
previousDescription: string;
|
|
||||||
previousPosition: FocalPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mediaId: string;
|
mediaId: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
@ -275,15 +270,14 @@ interface Props {
|
||||||
interface ConfirmationMessage {
|
interface ConfirmationMessage {
|
||||||
message: string;
|
message: string;
|
||||||
confirm: string;
|
confirm: string;
|
||||||
props?: RestoreProps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModalRef {
|
export interface ModalRef {
|
||||||
getCloseConfirmationMessage: () => null | ConfirmationMessage;
|
getCloseConfirmationMessage: () => null | ConfirmationMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
export const AltTextModal = forwardRef<ModalRef, Props>(
|
||||||
({ mediaId, previousDescription, previousPosition, onClose }, ref) => {
|
({ mediaId, onClose }, ref) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const media = useAppSelector((state) =>
|
const media = useAppSelector((state) =>
|
||||||
|
@ -302,18 +296,15 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||||
const focusY =
|
const focusY =
|
||||||
(media?.getIn(['meta', 'focus', 'y'], 0) as number | undefined) ?? 0;
|
(media?.getIn(['meta', 'focus', 'y'], 0) as number | undefined) ?? 0;
|
||||||
const [description, setDescription] = useState(
|
const [description, setDescription] = useState(
|
||||||
previousDescription ??
|
|
||||||
(media?.get('description') as string | undefined) ??
|
(media?.get('description') as string | undefined) ??
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
const [position, setPosition] = useState<FocalPoint>(
|
const [position, setPosition] = useState<FocalPoint>(
|
||||||
previousPosition ?? [focusX / 2 + 0.5, focusY / -2 + 0.5],
|
[focusX / 2 + 0.5, focusY / -2 + 0.5],
|
||||||
);
|
);
|
||||||
const [isDetecting, setIsDetecting] = useState(false);
|
const [isDetecting, setIsDetecting] = useState(false);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const dirtyRef = useRef(
|
const dirtyRef = useRef(false);
|
||||||
previousDescription || previousPosition ? true : false,
|
|
||||||
);
|
|
||||||
const type = media?.get('type') as string;
|
const type = media?.get('type') as string;
|
||||||
const valid = length(description) <= MAX_LENGTH;
|
const valid = length(description) <= MAX_LENGTH;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { PureComponent } from 'react';
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import Base from 'mastodon/components/modal_root';
|
import Base from 'mastodon/components/modal_root';
|
||||||
import { AltTextModal } from 'mastodon/features/alt_text_modal';
|
import { AltTextModal } from 'mastodon/features/alt_text_modal';
|
||||||
import {
|
import {
|
||||||
|
@ -78,8 +80,7 @@ export const MODAL_COMPONENTS = {
|
||||||
export default class ModalRoot extends PureComponent {
|
export default class ModalRoot extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: PropTypes.string,
|
modals: ImmutablePropTypes.list.isRequired,
|
||||||
props: PropTypes.object,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
ignoreFocus: PropTypes.bool,
|
ignoreFocus: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -89,7 +90,7 @@ export default class ModalRoot extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
getSnapshotBeforeUpdate () {
|
getSnapshotBeforeUpdate () {
|
||||||
return { visible: !!this.props.type };
|
return { visible: !!this.props.modals.get([0, 'type']) };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState, { visible }) {
|
componentDidUpdate (prevProps, prevState, { visible }) {
|
||||||
|
@ -129,25 +130,19 @@ export default class ModalRoot extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { type, props, ignoreFocus } = this.props;
|
const { modals, ignoreFocus } = this.props;
|
||||||
const { backgroundColor } = this.state;
|
const { backgroundColor } = this.state;
|
||||||
const visible = !!type;
|
const visible = !modals.isEmpty();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}>
|
<Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}>
|
||||||
{visible && (
|
{visible && modals.toArray().map((modal, index) => (
|
||||||
<>
|
<BundleContainer key={modal.modalKey} fetchComponent={MODAL_COMPONENTS[modal.modalType]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
|
||||||
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
|
{(SpecificComponent) => {
|
||||||
{(SpecificComponent) => {
|
return <SpecificComponent {...modal.modalProps} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={index === 0 ? this.setModalRef : null} />;
|
||||||
return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />;
|
}}
|
||||||
}}
|
</BundleContainer>
|
||||||
</BundleContainer>
|
))}
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<meta name='robots' content='noindex' />
|
|
||||||
</Helmet>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Base>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { openModal, closeModal } from '../../../actions/modal';
|
import { openModal, closeModal } from '../../../actions/modal';
|
||||||
import ModalRoot from '../components/modal_root';
|
import ModalRoot from '../components/modal_root';
|
||||||
|
|
||||||
const defaultProps = {};
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
ignoreFocus: state.getIn(['modal', 'ignoreFocus']),
|
ignoreFocus: state.getIn(['modal', 'ignoreFocus']),
|
||||||
type: state.getIn(['modal', 'stack', 0, 'modalType'], null),
|
modals: state.getIn(['modal', 'stack'], ImmutableList()),
|
||||||
props: state.getIn(['modal', 'stack', 0, 'modalProps'], defaultProps),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
@ -16,7 +14,6 @@ const mapDispatchToProps = dispatch => ({
|
||||||
if (confirmationMessage) {
|
if (confirmationMessage) {
|
||||||
dispatch(
|
dispatch(
|
||||||
openModal({
|
openModal({
|
||||||
previousModalProps: confirmationMessage.props,
|
|
||||||
modalType: 'CONFIRM',
|
modalType: 'CONFIRM',
|
||||||
modalProps: {
|
modalProps: {
|
||||||
message: confirmationMessage.message,
|
message: confirmationMessage.message,
|
||||||
|
|
|
@ -5,16 +5,19 @@ import { timelineDelete } from 'mastodon/actions/timelines_typed';
|
||||||
|
|
||||||
import type { ModalType } from '../actions/modal';
|
import type { ModalType } from '../actions/modal';
|
||||||
import { openModal, closeModal } from '../actions/modal';
|
import { openModal, closeModal } from '../actions/modal';
|
||||||
|
import { uuid } from '../uuid';
|
||||||
|
|
||||||
export type ModalProps = Record<string, unknown>;
|
export type ModalProps = Record<string, unknown>;
|
||||||
interface Modal {
|
interface Modal {
|
||||||
modalType: ModalType;
|
modalType: ModalType;
|
||||||
modalProps: ModalProps;
|
modalProps: ModalProps;
|
||||||
|
modalKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal = ImmutableRecord<Modal>({
|
const Modal = ImmutableRecord<Modal>({
|
||||||
modalType: 'ACTIONS',
|
modalType: 'ACTIONS',
|
||||||
modalProps: ImmutableRecord({})(),
|
modalProps: ImmutableRecord({})(),
|
||||||
|
modalKey: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ModalState {
|
interface ModalState {
|
||||||
|
@ -52,35 +55,11 @@ const pushModal = (
|
||||||
state: State,
|
state: State,
|
||||||
modalType: ModalType,
|
modalType: ModalType,
|
||||||
modalProps: ModalProps,
|
modalProps: ModalProps,
|
||||||
previousModalProps?: ModalProps,
|
|
||||||
): State => {
|
): State => {
|
||||||
return state.withMutations((record) => {
|
return state.withMutations((record) => {
|
||||||
record.set('ignoreFocus', false);
|
record.set('ignoreFocus', false);
|
||||||
record.update('stack', (stack) => {
|
record.update('stack', (stack) => {
|
||||||
let tmp = stack;
|
return stack.unshift(Modal({ modalType, modalProps, modalKey: uuid() }));
|
||||||
|
|
||||||
// With this option, we update the previously opened modal, so that when the
|
|
||||||
// current (new) modal is closed, the previous modal is re-opened with different
|
|
||||||
// props. Specifically, this is useful for the confirmation modal.
|
|
||||||
if (previousModalProps) {
|
|
||||||
const previousModal = tmp.first() as Modal | undefined;
|
|
||||||
|
|
||||||
if (previousModal) {
|
|
||||||
tmp = tmp.shift().unshift(
|
|
||||||
Modal({
|
|
||||||
modalType: previousModal.modalType,
|
|
||||||
modalProps: {
|
|
||||||
...previousModal.modalProps,
|
|
||||||
...previousModalProps,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = tmp.unshift(Modal({ modalType, modalProps }));
|
|
||||||
|
|
||||||
return tmp;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -91,7 +70,6 @@ export const modalReducer: Reducer<State> = (state = initialState, action) => {
|
||||||
state,
|
state,
|
||||||
action.payload.modalType,
|
action.payload.modalType,
|
||||||
action.payload.modalProps,
|
action.payload.modalProps,
|
||||||
action.payload.previousModalProps,
|
|
||||||
);
|
);
|
||||||
else if (closeModal.match(action)) return popModal(state, action.payload);
|
else if (closeModal.match(action)) return popModal(state, action.payload);
|
||||||
// TODO: type those actions
|
// TODO: type those actions
|
||||||
|
|
Loading…
Reference in New Issue