mirror of
https://github.com/mastodon/mastodon.git
synced 2025-05-07 04:06:13 +00:00
Refactor <ActionsModal>
to TypeScript (#34559)
This commit is contained in:
parent
17e4345eb2
commit
926c67c648
|
@ -26,11 +26,12 @@ import {
|
|||
import { openModal, closeModal } from 'mastodon/actions/modal';
|
||||
import { CircularProgress } from 'mastodon/components/circular_progress';
|
||||
import { isUserTouching } from 'mastodon/is_mobile';
|
||||
import type {
|
||||
MenuItem,
|
||||
ActionMenuItem,
|
||||
ExternalLinkMenuItem,
|
||||
import {
|
||||
isMenuItem,
|
||||
isActionItem,
|
||||
isExternalLinkItem,
|
||||
} from 'mastodon/models/dropdown_menu';
|
||||
import type { MenuItem } from 'mastodon/models/dropdown_menu';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import type { IconProp } from './icon';
|
||||
|
@ -38,30 +39,6 @@ import { IconButton } from './icon_button';
|
|||
|
||||
let id = 0;
|
||||
|
||||
const isMenuItem = (item: unknown): item is MenuItem => {
|
||||
if (item === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return typeof item === 'object' && 'text' in item;
|
||||
};
|
||||
|
||||
const isActionItem = (item: unknown): item is ActionMenuItem => {
|
||||
if (!item || !isMenuItem(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'action' in item;
|
||||
};
|
||||
|
||||
const isExternalLinkItem = (item: unknown): item is ExternalLinkMenuItem => {
|
||||
if (!item || !isMenuItem(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'href' in item;
|
||||
};
|
||||
|
||||
type RenderItemFn<Item = MenuItem> = (
|
||||
item: Item,
|
||||
index: number,
|
||||
|
@ -354,6 +331,9 @@ export const Dropdown = <Item = MenuItem,>({
|
|||
const open = currentId === openDropdownId;
|
||||
const activeElement = useRef<HTMLElement | null>(null);
|
||||
const targetRef = useRef<HTMLButtonElement | null>(null);
|
||||
const prefetchAccountId = status
|
||||
? status.getIn(['account', 'id'])
|
||||
: undefined;
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (activeElement.current) {
|
||||
|
@ -402,8 +382,8 @@ export const Dropdown = <Item = MenuItem,>({
|
|||
} else {
|
||||
onOpen?.();
|
||||
|
||||
if (status) {
|
||||
dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
|
||||
if (prefetchAccountId) {
|
||||
dispatch(fetchRelationships([prefetchAccountId]));
|
||||
}
|
||||
|
||||
if (isUserTouching()) {
|
||||
|
@ -411,7 +391,6 @@ export const Dropdown = <Item = MenuItem,>({
|
|||
openModal({
|
||||
modalType: 'ACTIONS',
|
||||
modalProps: {
|
||||
status,
|
||||
actions: items,
|
||||
onClick: handleItemClick,
|
||||
},
|
||||
|
@ -431,11 +410,11 @@ export const Dropdown = <Item = MenuItem,>({
|
|||
[
|
||||
dispatch,
|
||||
currentId,
|
||||
prefetchAccountId,
|
||||
scrollKey,
|
||||
onOpen,
|
||||
handleItemClick,
|
||||
open,
|
||||
status,
|
||||
items,
|
||||
handleClose,
|
||||
],
|
||||
|
|
|
@ -30,9 +30,6 @@ const messages = defineMessages({
|
|||
class PrivacyDropdown extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isUserTouching: PropTypes.func,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
noDirect: PropTypes.bool,
|
||||
|
|
|
@ -15,16 +15,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
dispatch(changeComposeVisibility(value));
|
||||
},
|
||||
|
||||
isUserTouching,
|
||||
onModalOpen: props => dispatch(openModal({
|
||||
modalType: 'ACTIONS',
|
||||
modalProps: props,
|
||||
})),
|
||||
onModalClose: () => dispatch(closeModal({
|
||||
modalType: undefined,
|
||||
ignoreFocus: false,
|
||||
})),
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
|
||||
export default class ActionsModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
actions: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
renderAction = (action, i) => {
|
||||
if (action === null) {
|
||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
||||
const { icon = null, iconComponent = null, text, meta = null, active = false, href = '#' } = action;
|
||||
|
||||
return (
|
||||
<li key={`${text}-${i}`}>
|
||||
<a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
|
||||
{icon && <IconButton title={text} icon={icon} iconComponent={iconComponent} role='presentation' tabIndex={-1} inverted />}
|
||||
<div>
|
||||
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
|
||||
<div>{meta}</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='modal-root__modal actions-modal'>
|
||||
<ul className={classNames({ 'with-status': !!status })}>
|
||||
{this.props.actions.map(this.renderAction)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { MenuItem } from 'mastodon/models/dropdown_menu';
|
||||
import {
|
||||
isActionItem,
|
||||
isExternalLinkItem,
|
||||
} from 'mastodon/models/dropdown_menu';
|
||||
|
||||
export const ActionsModal: React.FC<{
|
||||
actions: MenuItem[];
|
||||
onClick: React.MouseEventHandler;
|
||||
}> = ({ actions, onClick }) => (
|
||||
<div className='modal-root__modal actions-modal'>
|
||||
<ul>
|
||||
{actions.map((option, i: number) => {
|
||||
if (option === null) {
|
||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
||||
const { text, dangerous } = option;
|
||||
|
||||
let element: React.ReactElement;
|
||||
|
||||
if (isActionItem(option)) {
|
||||
element = (
|
||||
<button onClick={onClick} data-index={i}>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
} else if (isExternalLinkItem(option)) {
|
||||
element = (
|
||||
<a
|
||||
href={option.href}
|
||||
target={option.target ?? '_target'}
|
||||
data-method={option.method}
|
||||
rel='noopener'
|
||||
onClick={onClick}
|
||||
data-index={i}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
element = (
|
||||
<Link to={option.to} onClick={onClick} data-index={i}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
className={classNames({
|
||||
'dropdown-menu__item--dangerous': dangerous,
|
||||
})}
|
||||
key={`${text}-${i}`}
|
||||
>
|
||||
{element}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
|
@ -24,7 +24,7 @@ import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
|||
|
||||
import BundleContainer from '../containers/bundle_container';
|
||||
|
||||
import ActionsModal from './actions_modal';
|
||||
import { ActionsModal } from './actions_modal';
|
||||
import AudioModal from './audio_modal';
|
||||
import { BoostModal } from './boost_modal';
|
||||
import {
|
||||
|
|
|
@ -22,3 +22,29 @@ export type MenuItem =
|
|||
| LinkMenuItem
|
||||
| ExternalLinkMenuItem
|
||||
| null;
|
||||
|
||||
export const isMenuItem = (item: unknown): item is MenuItem => {
|
||||
if (item === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return typeof item === 'object' && 'text' in item;
|
||||
};
|
||||
|
||||
export const isActionItem = (item: unknown): item is ActionMenuItem => {
|
||||
if (!item || !isMenuItem(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'action' in item;
|
||||
};
|
||||
|
||||
export const isExternalLinkItem = (
|
||||
item: unknown,
|
||||
): item is ExternalLinkMenuItem => {
|
||||
if (!item || !isMenuItem(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'href' in item;
|
||||
};
|
||||
|
|
|
@ -6484,55 +6484,38 @@ a.status-card {
|
|||
}
|
||||
|
||||
.actions-modal {
|
||||
border-radius: 8px 8px 0 0;
|
||||
background: var(--dropdown-background-color);
|
||||
backdrop-filter: var(--background-filter);
|
||||
border-color: var(--dropdown-border-color);
|
||||
box-shadow: var(--dropdown-shadow);
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
|
||||
.actions-modal__item-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
ul {
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
max-height: 80vh;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
&.with-status {
|
||||
max-height: calc(80vh - 75px);
|
||||
}
|
||||
a,
|
||||
button {
|
||||
color: inherit;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
li:empty {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li:not(:empty) {
|
||||
a {
|
||||
color: $primary-text-color;
|
||||
display: flex;
|
||||
padding: 12px 16px;
|
||||
font-size: 15px;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
|
||||
&,
|
||||
button {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&.active,
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
&,
|
||||
button {
|
||||
background: $ui-highlight-color;
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
button:first-child {
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
}
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: var(--dropdown-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user