some refactoring, the BadgeList component has been remade into a functional one
This commit is contained in:
parent
b6c2a3b6f8
commit
ec89c1f115
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {Badge} from '../../types/badges';
|
import {Badge} from '../../types/badges';
|
||||||
import RenderEmoji from '../utils/emoji';
|
import RenderEmoji from '../emoji/emoji';
|
||||||
import {IMAGE_TYPE_ABSOLUTE_URL, IMAGE_TYPE_EMOJI} from '../../constants';
|
import {IMAGE_TYPE_ABSOLUTE_URL, IMAGE_TYPE_EMOJI} from '../../constants';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -6,6 +6,8 @@ import {FormattedMessage, useIntl} from 'react-intl';
|
|||||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/common';
|
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/common';
|
||||||
import {GlobalState} from 'mattermost-redux/types/store';
|
import {GlobalState} from 'mattermost-redux/types/store';
|
||||||
|
|
||||||
|
import RenderEmoji from 'components/emoji/emoji';
|
||||||
|
|
||||||
import {isCreateBadgeModalVisible, getEditBadgeModalData} from 'selectors';
|
import {isCreateBadgeModalVisible, getEditBadgeModalData} from 'selectors';
|
||||||
import {closeCreateBadgeModal, closeEditBadgeModal, setRHSView} from 'actions/actions';
|
import {closeCreateBadgeModal, closeEditBadgeModal, setRHSView} from 'actions/actions';
|
||||||
import {RHS_STATE_ALL} from '../../constants';
|
import {RHS_STATE_ALL} from '../../constants';
|
||||||
@ -14,14 +16,14 @@ import Client from 'client/api';
|
|||||||
import {getServerErrorId} from 'utils/helpers';
|
import {getServerErrorId} from 'utils/helpers';
|
||||||
import CloseIcon from 'components/icons/close_icon';
|
import CloseIcon from 'components/icons/close_icon';
|
||||||
import EmojiIcon from 'components/icons/emoji_icon';
|
import EmojiIcon from 'components/icons/emoji_icon';
|
||||||
import RenderEmoji from 'components/utils/emoji';
|
|
||||||
|
import ConfirmDialog from 'components/confirm_dialog/confirm_dialog';
|
||||||
|
|
||||||
import EmojiPickerOverlay from './emoji_picker';
|
import EmojiPickerOverlay from './emoji_picker';
|
||||||
import InlineTypeForm from './inline_type_form';
|
import InlineTypeForm from './inline_type_form';
|
||||||
import TypeSelect from './type_select';
|
import TypeSelect from './type_select';
|
||||||
|
|
||||||
import './badge_modal.scss';
|
import './badge_modal.scss';
|
||||||
import ConfirmDialog from 'components/confirm_dialog/confirm_dialog';
|
|
||||||
|
|
||||||
const NEW_TYPE_VALUE = '__new__';
|
const NEW_TYPE_VALUE = '__new__';
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {AllBadgesBadge} from 'types/badges';
|
|||||||
import Client from 'client/api';
|
import Client from 'client/api';
|
||||||
import {getServerErrorId, getUserDisplayName} from 'utils/helpers';
|
import {getServerErrorId, getUserDisplayName} from 'utils/helpers';
|
||||||
import CloseIcon from 'components/icons/close_icon';
|
import CloseIcon from 'components/icons/close_icon';
|
||||||
import RenderEmoji from 'components/utils/emoji';
|
import RenderEmoji from 'components/emoji/emoji';
|
||||||
|
|
||||||
type GrantFormData = {
|
type GrantFormData = {
|
||||||
badgeId: string;
|
badgeId: string;
|
||||||
|
|||||||
@ -72,7 +72,7 @@ const AllBadgesRow: React.FC<Props> = ({badge, onClick}: Props) => {
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{' '}
|
{' '}
|
||||||
{badge.description ? markdown(badge.description) : '—'}
|
{badge.description ? markdown(badge.description) : '-'}
|
||||||
</div>
|
</div>
|
||||||
<div className='badge-meta'>
|
<div className='badge-meta'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import Client from '../../client/api';
|
|||||||
|
|
||||||
import {RHSState} from '../../types/general';
|
import {RHSState} from '../../types/general';
|
||||||
import {RHS_STATE_MY, RHS_STATE_OTHER, RHS_STATE_TYPES} from '../../constants';
|
import {RHS_STATE_MY, RHS_STATE_OTHER, RHS_STATE_TYPES} from '../../constants';
|
||||||
import BadgeImage from '../utils/badge_image';
|
import BadgeImage from '../badge_image/badge_image';
|
||||||
|
|
||||||
import {markdown} from 'utils/markdown';
|
import {markdown} from 'utils/markdown';
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {FormattedMessage, useIntl} from 'react-intl';
|
|||||||
import Client4 from 'mattermost-redux/client/client4';
|
import Client4 from 'mattermost-redux/client/client4';
|
||||||
|
|
||||||
import {UserBadge} from '../../types/badges';
|
import {UserBadge} from '../../types/badges';
|
||||||
import BadgeImage from '../utils/badge_image';
|
import BadgeImage from '../badge_image/badge_image';
|
||||||
import {markdown} from 'utils/markdown';
|
import {markdown} from 'utils/markdown';
|
||||||
import Client from '../../client/api';
|
import Client from '../../client/api';
|
||||||
import ConfirmDialog from '../confirm_dialog/confirm_dialog';
|
import ConfirmDialog from '../confirm_dialog/confirm_dialog';
|
||||||
|
|||||||
@ -1,256 +1,169 @@
|
|||||||
import {UserProfile} from 'mattermost-redux/types/users';
|
import {UserProfile} from 'mattermost-redux/types/users';
|
||||||
import React from 'react';
|
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||||
|
|
||||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
|
|
||||||
import {GlobalState} from 'mattermost-redux/types/store';
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
|
||||||
import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common';
|
||||||
|
|
||||||
import {BadgeID, UserBadge} from 'types/badges';
|
import {systemEmojis, getCustomEmojisByName} from 'mattermost-redux/actions/emojis';
|
||||||
|
|
||||||
|
import {UserBadge} from 'types/badges';
|
||||||
import Client from 'client/api';
|
import Client from 'client/api';
|
||||||
import BadgeImage from '../utils/badge_image';
|
import BadgeImage from '../badge_image/badge_image';
|
||||||
import TooltipWrapper from '../utils/tooltip_wrapper';
|
|
||||||
import {RHSState} from 'types/general';
|
|
||||||
import {IMAGE_TYPE_EMOJI, RHS_STATE_DETAIL, RHS_STATE_MY, RHS_STATE_OTHER} from '../../constants';
|
import {IMAGE_TYPE_EMOJI, RHS_STATE_DETAIL, RHS_STATE_MY, RHS_STATE_OTHER} from '../../constants';
|
||||||
|
import {setRHSView, setRHSBadge, setRHSUser, openGrant} from '../../actions/actions';
|
||||||
|
import {getShowRHS} from 'selectors';
|
||||||
|
import {groupBadges} from 'components/utils/badge_list_utils';
|
||||||
|
|
||||||
|
import BadgeTooltip from './badge_tooltip';
|
||||||
|
import TooltipWrapper from './tooltip_wrapper';
|
||||||
import './badge_list.scss';
|
import './badge_list.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
intl: IntlShape;
|
|
||||||
debug: GlobalState;
|
|
||||||
user: UserProfile;
|
user: UserProfile;
|
||||||
currentUserID: string;
|
|
||||||
openRHS: (() => void) | null;
|
|
||||||
hide: () => void;
|
hide: () => void;
|
||||||
status?: string;
|
|
||||||
actions: {
|
|
||||||
setRHSView: (view: RHSState) => Promise<void>;
|
|
||||||
setRHSBadge: (id: BadgeID | null) => Promise<void>;
|
|
||||||
setRHSUser: (id: string | null) => Promise<void>;
|
|
||||||
openGrant: (user?: string, badge?: string) => Promise<void>;
|
|
||||||
getCustomEmojisByName: (names: string[]) => Promise<unknown>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type BadgeGroup = {
|
|
||||||
badge: UserBadge;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
badges?: UserBadge[];
|
|
||||||
loaded?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_BADGES = 6;
|
const MAX_BADGES = 6;
|
||||||
const BADGE_SIZE = 24;
|
const BADGE_SIZE = 24;
|
||||||
|
|
||||||
class BadgeList extends React.PureComponent<Props, State> {
|
const BadgeList: React.FC<Props> = ({user, hide}) => {
|
||||||
constructor(props: Props) {
|
const intl = useIntl();
|
||||||
super(props);
|
const dispatch = useDispatch();
|
||||||
|
const currentUserID = useSelector(getCurrentUserId);
|
||||||
|
const openRHS = useSelector(getShowRHS);
|
||||||
|
const [badges, setBadges] = useState<UserBadge[]>();
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
|
||||||
this.state = {};
|
useEffect(() => {
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const c = new Client();
|
const c = new Client();
|
||||||
c.getUserBadges(this.props.user.id).then((badges) => {
|
c.getUserBadges(user.id).then((result) => {
|
||||||
this.setState({badges, loaded: true});
|
setBadges(result);
|
||||||
|
setLoaded(true);
|
||||||
});
|
});
|
||||||
}
|
}, [user.id]);
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
const groups = useMemo(
|
||||||
if (this.state.badges !== prevState.badges && this.state.badges) {
|
() => (badges ? groupBadges(badges) : []),
|
||||||
const groups = this.groupBadges(this.state.badges);
|
[badges],
|
||||||
const toShow = Math.min(groups.length, MAX_BADGES);
|
);
|
||||||
const names: string[] = [];
|
|
||||||
for (let i = 0; i < toShow; i++) {
|
|
||||||
const {badge} = groups[i];
|
|
||||||
if (badge.image_type === IMAGE_TYPE_EMOJI) {
|
|
||||||
names.push(badge.image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const toLoad = names.filter((v) => !systemEmojis.has(v));
|
|
||||||
this.props.actions.getCustomEmojisByName(toLoad);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
groupBadges = (badges: UserBadge[]): BadgeGroup[] => {
|
useEffect(() => {
|
||||||
const map = new Map<BadgeID, BadgeGroup>();
|
if (!badges) {
|
||||||
for (const badge of badges) {
|
|
||||||
const existing = map.get(badge.id);
|
|
||||||
if (existing) {
|
|
||||||
existing.count++;
|
|
||||||
} else {
|
|
||||||
map.set(badge.id, {badge, count: 1});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(map.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
onMoreClick = () => {
|
|
||||||
if (!this.props.openRHS) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const toShow = groups.slice(0, MAX_BADGES);
|
||||||
|
const names = toShow.
|
||||||
|
filter(({badge}) => badge.image_type === IMAGE_TYPE_EMOJI).
|
||||||
|
map(({badge}) => badge.image).
|
||||||
|
filter((v) => !systemEmojis.has(v));
|
||||||
|
if (names.length > 0) {
|
||||||
|
dispatch(getCustomEmojisByName(names));
|
||||||
|
}
|
||||||
|
}, [badges, groups, dispatch]);
|
||||||
|
|
||||||
if (this.props.currentUserID === this.props.user.id) {
|
const handleMoreClick = useCallback(() => {
|
||||||
this.props.actions.setRHSView(RHS_STATE_MY);
|
if (!openRHS) {
|
||||||
this.props.openRHS();
|
|
||||||
this.props.hide();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (currentUserID === user.id) {
|
||||||
|
dispatch(setRHSView(RHS_STATE_MY));
|
||||||
|
} else {
|
||||||
|
dispatch(setRHSUser(user.id));
|
||||||
|
dispatch(setRHSView(RHS_STATE_OTHER));
|
||||||
|
}
|
||||||
|
openRHS();
|
||||||
|
hide();
|
||||||
|
}, [openRHS, currentUserID, user.id, dispatch, hide]);
|
||||||
|
|
||||||
this.props.actions.setRHSUser(this.props.user.id);
|
const handleBadgeClick = useCallback((badge: UserBadge) => {
|
||||||
this.props.actions.setRHSView(RHS_STATE_OTHER);
|
if (!openRHS) {
|
||||||
this.props.openRHS();
|
|
||||||
this.props.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
onBadgeClick = (badge: UserBadge) => {
|
|
||||||
if (!this.props.openRHS) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
dispatch(setRHSBadge(badge.id));
|
||||||
|
dispatch(setRHSView(RHS_STATE_DETAIL));
|
||||||
|
openRHS();
|
||||||
|
hide();
|
||||||
|
}, [openRHS, dispatch, hide]);
|
||||||
|
|
||||||
this.props.actions.setRHSBadge(badge.id);
|
const handleGrantClick = useCallback(() => {
|
||||||
this.props.actions.setRHSView(RHS_STATE_DETAIL);
|
dispatch(openGrant(user.username));
|
||||||
this.props.openRHS();
|
hide();
|
||||||
this.props.hide();
|
}, [dispatch, user.username, hide]);
|
||||||
}
|
|
||||||
|
|
||||||
onGrantClick = () => {
|
const visibleGroups = groups.slice(0, MAX_BADGES);
|
||||||
this.props.actions.openGrant(this.props.user.username);
|
const maxWidth = (MAX_BADGES * BADGE_SIZE) + 30;
|
||||||
this.props.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const {intl} = this.props;
|
<div id='badgePlugin'>
|
||||||
const groups = this.state.badges ? this.groupBadges(this.state.badges) : [];
|
<div><b>
|
||||||
const nGroups = groups.length;
|
<FormattedMessage
|
||||||
const toShow = Math.min(nGroups, MAX_BADGES);
|
id='badges.popover.title'
|
||||||
|
defaultMessage='Достижения'
|
||||||
const nameLabel = intl.formatMessage(
|
/>
|
||||||
{id: 'badges.label.name', defaultMessage: 'Название:'},
|
</b></div>
|
||||||
);
|
<div id='contentContainer'>
|
||||||
const descLabel = intl.formatMessage(
|
{visibleGroups.map(({badge, count}) => (
|
||||||
{id: 'badges.label.description', defaultMessage: 'Описание:'},
|
<TooltipWrapper
|
||||||
);
|
key={badge.id}
|
||||||
|
tooltipContent={
|
||||||
const content: React.ReactNode[] = [];
|
<BadgeTooltip
|
||||||
for (let i = 0; i < toShow; i++) {
|
|
||||||
const {badge, count} = groups[i];
|
|
||||||
|
|
||||||
let tooltipLines: string;
|
|
||||||
if (count > 1) {
|
|
||||||
const countLabel = intl.formatMessage(
|
|
||||||
{id: 'badges.label.count', defaultMessage: 'Количество: {count}'},
|
|
||||||
{count},
|
|
||||||
);
|
|
||||||
tooltipLines = [
|
|
||||||
nameLabel + ' ' + badge.name,
|
|
||||||
descLabel + ' ' + badge.description,
|
|
||||||
countLabel,
|
|
||||||
].join('\n');
|
|
||||||
} else {
|
|
||||||
const time = new Date(badge.time);
|
|
||||||
let reason: string | null = null;
|
|
||||||
if (badge.reason) {
|
|
||||||
reason = intl.formatMessage(
|
|
||||||
{id: 'badges.label.reason', defaultMessage: 'Причина: {reason}'},
|
|
||||||
{reason: badge.reason},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const grantedBy = intl.formatMessage(
|
|
||||||
{id: 'badges.label.granted_by', defaultMessage: 'Выдал: {username}'},
|
|
||||||
{username: badge.granted_by_name},
|
|
||||||
);
|
|
||||||
const grantedAt = intl.formatMessage(
|
|
||||||
{id: 'badges.label.granted_at', defaultMessage: 'Выдан: {date}'},
|
|
||||||
{date: intl.formatDate(time, {day: '2-digit', month: '2-digit', year: 'numeric'})},
|
|
||||||
);
|
|
||||||
tooltipLines = [
|
|
||||||
nameLabel + ' ' + badge.name,
|
|
||||||
descLabel + ' ' + badge.description,
|
|
||||||
reason,
|
|
||||||
grantedBy,
|
|
||||||
grantedAt,
|
|
||||||
].filter(Boolean).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
const badgeComponent = (
|
|
||||||
<TooltipWrapper tooltipContent={tooltipLines}>
|
|
||||||
<a onClick={() => this.onBadgeClick(badge)}>
|
|
||||||
<span className='badge-stacked'>
|
|
||||||
<BadgeImage
|
|
||||||
badge={badge}
|
badge={badge}
|
||||||
size={BADGE_SIZE}
|
count={count}
|
||||||
/>
|
/>
|
||||||
{count > 1 && (
|
}
|
||||||
<span className='badge-stack-count'>
|
|
||||||
{'×'}{count}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</TooltipWrapper>
|
|
||||||
);
|
|
||||||
content.push(badgeComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
let andMore: React.ReactNode = null;
|
|
||||||
if (nGroups > MAX_BADGES) {
|
|
||||||
const andMoreText = intl.formatMessage(
|
|
||||||
{id: 'badges.and_more', defaultMessage: 'и ещё {count}. Нажмите, чтобы увидеть все.'},
|
|
||||||
{count: nGroups - MAX_BADGES},
|
|
||||||
);
|
|
||||||
andMore = (
|
|
||||||
<TooltipWrapper tooltipContent={andMoreText}>
|
|
||||||
<button
|
|
||||||
id='showMoreButton'
|
|
||||||
onClick={this.onMoreClick}
|
|
||||||
>
|
>
|
||||||
<span className={'fa fa-angle-right'}/>
|
<a onClick={() => handleBadgeClick(badge)}>
|
||||||
</button>
|
<span className='badge-stacked'>
|
||||||
</TooltipWrapper>
|
<BadgeImage
|
||||||
);
|
badge={badge}
|
||||||
}
|
size={BADGE_SIZE}
|
||||||
const maxWidth = (MAX_BADGES * BADGE_SIZE) + 30;
|
/>
|
||||||
let loading: React.ReactNode = null;
|
{count > 1 && (
|
||||||
if (!this.state.loaded) {
|
<span className='badge-stack-count'>
|
||||||
loading = (
|
{'×'}{count}
|
||||||
|
</span>
|
||||||
// Reserve enough height one row of badges and the "and more" button
|
)}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</TooltipWrapper>
|
||||||
|
))}
|
||||||
|
{groups.length > MAX_BADGES && (
|
||||||
|
<TooltipWrapper
|
||||||
|
tooltipContent={intl.formatMessage(
|
||||||
|
{id: 'badges.and_more', defaultMessage: 'и ещё {count}. Нажмите, чтобы увидеть все.'},
|
||||||
|
{count: groups.length - MAX_BADGES},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
id='showMoreButton'
|
||||||
|
onClick={handleMoreClick}
|
||||||
|
>
|
||||||
|
<span className={'fa fa-angle-right'}/>
|
||||||
|
</button>
|
||||||
|
</TooltipWrapper>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!loaded && (
|
||||||
<div style={{height: BADGE_SIZE, minWidth: 66, maxWidth}}>
|
<div style={{height: BADGE_SIZE, minWidth: 66, maxWidth}}>
|
||||||
<div className='spinner'/>
|
<div className='spinner'/>
|
||||||
</div>
|
</div>
|
||||||
);
|
)}
|
||||||
}
|
<button
|
||||||
return (
|
id='grantBadgeButton'
|
||||||
<div id='badgePlugin'>
|
onClick={handleGrantClick}
|
||||||
<div><b>
|
>
|
||||||
<FormattedMessage
|
<span className={'fa fa-plus-circle'}/>
|
||||||
id='badges.popover.title'
|
<FormattedMessage
|
||||||
defaultMessage='Достижения'
|
id='badges.grant_badge'
|
||||||
/>
|
defaultMessage='Выдать достижение'
|
||||||
</b></div>
|
/>
|
||||||
<div id='contentContainer' >
|
</button>
|
||||||
{content}
|
<hr className='divider divider--expanded'/>
|
||||||
{andMore}
|
</div>
|
||||||
</div>
|
);
|
||||||
{loading}
|
};
|
||||||
<button
|
|
||||||
id='grantBadgeButton'
|
|
||||||
onClick={this.onGrantClick}
|
|
||||||
>
|
|
||||||
<span className={'fa fa-plus-circle'}/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.grant_badge'
|
|
||||||
defaultMessage='Выдать достижение'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<hr className='divider divider--expanded'/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(BadgeList);
|
export default BadgeList;
|
||||||
|
|||||||
62
webapp/src/components/user_popover/badge_tooltip.tsx
Normal file
62
webapp/src/components/user_popover/badge_tooltip.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
|
||||||
|
import {UserBadge} from 'types/badges';
|
||||||
|
|
||||||
|
import {truncateText} from 'components/utils/badge_list_utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
badge: UserBadge;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BadgeTooltip: React.FC<Props> = ({badge, count}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const desc = badge.description ? truncateText(badge.description) : '—';
|
||||||
|
|
||||||
|
const nameRow = intl.formatMessage(
|
||||||
|
{id: 'badges.label.name', defaultMessage: 'Название:'},
|
||||||
|
) + ' ' + badge.name;
|
||||||
|
|
||||||
|
const descRow = intl.formatMessage(
|
||||||
|
{id: 'badges.label.description', defaultMessage: 'Описание:'},
|
||||||
|
) + ' ' + desc;
|
||||||
|
|
||||||
|
if (count > 1) {
|
||||||
|
const countRow = intl.formatMessage(
|
||||||
|
{id: 'badges.label.count', defaultMessage: 'Количество: {count}'},
|
||||||
|
{count},
|
||||||
|
);
|
||||||
|
return <>{nameRow}{'\n'}{descRow}{'\n'}{countRow}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = new Date(badge.time);
|
||||||
|
const grantedBy = intl.formatMessage(
|
||||||
|
{id: 'badges.label.granted_by', defaultMessage: 'Выдал: {username}'},
|
||||||
|
{username: badge.granted_by_name},
|
||||||
|
);
|
||||||
|
const grantedAt = intl.formatMessage(
|
||||||
|
{id: 'badges.label.granted_at', defaultMessage: 'Выдан: {date}'},
|
||||||
|
{date: intl.formatDate(time, {day: '2-digit', month: '2-digit', year: 'numeric'})},
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{nameRow}{'\n'}
|
||||||
|
{descRow}{'\n'}
|
||||||
|
{badge.reason && (
|
||||||
|
<>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{id: 'badges.label.reason', defaultMessage: 'Причина: {reason}'},
|
||||||
|
{reason: badge.reason},
|
||||||
|
)}{'\n'}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{grantedBy}{'\n'}
|
||||||
|
{grantedAt}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BadgeTooltip;
|
||||||
@ -1,49 +1 @@
|
|||||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
export {default} from './badge_list';
|
||||||
// See License for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {ActionCreatorsMapObject, bindActionCreators, Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {GlobalState} from 'mattermost-redux/types/store';
|
|
||||||
|
|
||||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common';
|
|
||||||
|
|
||||||
import {getCustomEmojisByName} from 'mattermost-redux/actions/emojis';
|
|
||||||
|
|
||||||
import {setRHSView, setRHSBadge, setRHSUser, openGrant} from '../../actions/actions';
|
|
||||||
|
|
||||||
import {getShowRHS} from 'selectors';
|
|
||||||
import {RHSState} from 'types/general';
|
|
||||||
import {BadgeID} from 'types/badges';
|
|
||||||
|
|
||||||
import BadgeList from './badge_list';
|
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
|
||||||
return {
|
|
||||||
openRHS: getShowRHS(state),
|
|
||||||
currentUserID: getCurrentUserId(state),
|
|
||||||
debug: state,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type Actions = {
|
|
||||||
setRHSView: (view: RHSState) => Promise<void>;
|
|
||||||
setRHSBadge: (id: BadgeID | null) => Promise<void>;
|
|
||||||
setRHSUser: (id: string | null) => Promise<void>;
|
|
||||||
openGrant: (user?: string, badge?: string) => Promise<void>;
|
|
||||||
getCustomEmojisByName: (names: string[]) => Promise<unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch: Dispatch) {
|
|
||||||
return {
|
|
||||||
actions: bindActionCreators<ActionCreatorsMapObject, Actions>({
|
|
||||||
setRHSView,
|
|
||||||
setRHSBadge,
|
|
||||||
setRHSUser,
|
|
||||||
openGrant,
|
|
||||||
getCustomEmojisByName,
|
|
||||||
}, dispatch),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BadgeList);
|
|
||||||
|
|||||||
25
webapp/src/components/utils/badge_list_utils.ts
Normal file
25
webapp/src/components/utils/badge_list_utils.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {BadgeID, UserBadge} from 'types/badges';
|
||||||
|
|
||||||
|
export type BadgeGroup = {
|
||||||
|
badge: UserBadge;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_DESC_LENGTH = 40;
|
||||||
|
|
||||||
|
export function groupBadges(badges: UserBadge[]): BadgeGroup[] {
|
||||||
|
const map = new Map<BadgeID, BadgeGroup>();
|
||||||
|
for (const badge of badges) {
|
||||||
|
const existing = map.get(badge.id);
|
||||||
|
if (existing) {
|
||||||
|
existing.count++;
|
||||||
|
} else {
|
||||||
|
map.set(badge.id, {badge, count: 1});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(map.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function truncateText(text: string): string {
|
||||||
|
return text.length > MAX_DESC_LENGTH ? text.slice(0, MAX_DESC_LENGTH) + '...' : text;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user