174 lines
5.7 KiB
JavaScript
174 lines
5.7 KiB
JavaScript
import PropTypes from 'prop-types';
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
|
|
import { Helmet } from 'react-helmet';
|
|
|
|
import { createSelector } from '@reduxjs/toolkit';
|
|
import { Map as ImmutableMap } from 'immutable';
|
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
import { connect } from 'react-redux';
|
|
|
|
|
|
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
|
|
|
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
|
|
import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react';
|
|
import { updateReactionDeck } from 'mastodon/actions/reaction_deck';
|
|
import { Button } from 'mastodon/components/button';
|
|
import ColumnHeader from 'mastodon/components/column_header';
|
|
import { Icon } from 'mastodon/components/icon';
|
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
|
import Column from 'mastodon/features/ui/components/column';
|
|
|
|
|
|
import ReactionEmoji from './components/reaction_emoji';
|
|
|
|
|
|
// https://medium.com/@wbern/getting-react-18s-strict-mode-to-work-with-react-beautiful-dnd-47bc909348e4
|
|
/* eslint react/prop-types: 0 */
|
|
const StrictModeDroppable = ({ children, ...props }) => {
|
|
const [enabled, setEnabled] = useState(false);
|
|
useEffect(() => {
|
|
const animation = requestAnimationFrame(() => setEnabled(true));
|
|
return () => {
|
|
cancelAnimationFrame(animation);
|
|
setEnabled(false);
|
|
};
|
|
}, []);
|
|
if (!enabled) {
|
|
return null;
|
|
}
|
|
return <Droppable {...props}>{children}</Droppable>;
|
|
};
|
|
/* eslint react/prop-types: 0 */
|
|
|
|
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
|
|
|
|
const messages = defineMessages({
|
|
reaction_deck_add: { id: 'reaction_deck.add', defaultMessage: 'Add' },
|
|
heading: { id: 'column.reaction_deck', defaultMessage: 'Reaction deck' },
|
|
});
|
|
|
|
const mapStateToProps = (state, props) => ({
|
|
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
|
|
deck: state.get('reaction_deck'),
|
|
emojiMap: customEmojiMap(state),
|
|
});
|
|
|
|
const mapDispatchToProps = (dispatch) => ({
|
|
onChange: (emojis) => dispatch(updateReactionDeck(emojis)),
|
|
});
|
|
|
|
class ReactionDeck extends ImmutablePureComponent {
|
|
|
|
static propTypes = {
|
|
params: PropTypes.object.isRequired,
|
|
deck: ImmutablePropTypes.list,
|
|
emojiMap: ImmutablePropTypes.map,
|
|
multiColumn: PropTypes.bool,
|
|
intl: PropTypes.object.isRequired,
|
|
onChange: PropTypes.func.isRequired,
|
|
};
|
|
|
|
deckToArray = () => {
|
|
const { deck } = this.props;
|
|
|
|
return deck.map((item) => item.get('name')).toArray();
|
|
};
|
|
|
|
handleReorder = (result) => {
|
|
const newDeck = this.deckToArray();
|
|
const deleted = newDeck.splice(result.source.index, 1);
|
|
newDeck.splice(result.destination.index, 0, deleted[0]);
|
|
this.props.onChange(newDeck);
|
|
};
|
|
|
|
handleChange = (index, emoji) => {
|
|
const newDeck = this.deckToArray();
|
|
newDeck[index] = emoji.native || emoji.id.replace(':', '');
|
|
this.props.onChange(newDeck);
|
|
};
|
|
|
|
handleRemove = (index) => {
|
|
const newDeck = this.deckToArray();
|
|
newDeck.splice(index, 1);
|
|
this.props.onChange(newDeck);
|
|
};
|
|
|
|
handleAdd = () => {
|
|
const newDeck = this.deckToArray();
|
|
newDeck.push('👍');
|
|
this.props.onChange(newDeck);
|
|
};
|
|
|
|
render () {
|
|
const { intl, deck, emojiMap, multiColumn } = this.props;
|
|
|
|
if (!deck) {
|
|
return (
|
|
<Column>
|
|
<LoadingIndicator />
|
|
</Column>
|
|
);
|
|
}
|
|
|
|
|
|
return (
|
|
<Column bindToDocument={!multiColumn}>
|
|
<ColumnHeader
|
|
icon='smile-o'
|
|
iconComponent={EmojiReactionIcon}
|
|
title={intl.formatMessage(messages.heading)}
|
|
multiColumn={multiColumn}
|
|
showBackButton
|
|
/>
|
|
|
|
<ScrollableList
|
|
scrollKey='reaction_deck'
|
|
bindToDocument={!multiColumn}
|
|
>
|
|
<DragDropContext onDragEnd={this.handleReorder}>
|
|
<StrictModeDroppable droppableId='deckitems'>
|
|
{(provided) => (
|
|
<div className='deckitems reaction_deck_container' {...provided.droppableProps} ref={provided.innerRef}>
|
|
{deck.map((emoji, index) => (
|
|
<Draggable key={index} draggableId={'' + index} index={index}>
|
|
{(provided2) => (
|
|
<div className='reaction_deck_container__row' ref={provided2.innerRef} {...provided2.draggableProps}>
|
|
<span {...provided2.dragHandleProps}>
|
|
<Icon id='bars' icon={MenuIcon} className='handle' />
|
|
</span>
|
|
<ReactionEmoji emojiMap={emojiMap}
|
|
emoji={emoji.get('name')}
|
|
index={index}
|
|
onChange={this.handleChange}
|
|
onRemove={this.handleRemove}
|
|
className='reaction_emoji'
|
|
/>
|
|
</div>
|
|
)}
|
|
</Draggable>
|
|
))}
|
|
{provided.placeholder}
|
|
|
|
<Button text={intl.formatMessage(messages.reaction_deck_add)} onClick={this.handleAdd} />
|
|
</div>
|
|
)}
|
|
</StrictModeDroppable>
|
|
</DragDropContext>
|
|
</ScrollableList>
|
|
|
|
<Helmet>
|
|
<meta name='robots' content='noindex' />
|
|
</Helmet>
|
|
</Column>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ReactionDeck));
|