added badge stacking in user popover
This commit is contained in:
parent
dffe0685bb
commit
a88ce39a48
@ -19,6 +19,7 @@
|
||||
"badges.label.granted_by": "Granted by: {username}",
|
||||
"badges.label.granted_at": "Granted at: {date}",
|
||||
"badges.label.reason": "Why? {reason}",
|
||||
"badges.label.count": "Count: {count}",
|
||||
|
||||
"badges.granted.not_yet": "Not yet granted.",
|
||||
"badges.granted.multiple": "Granted {times, plural, one {# time} other {# times}} to {users, plural, one {# user} other {# users}}.",
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
"badges.label.granted_by": "Выдал: {username}",
|
||||
"badges.label.granted_at": "Выдан: {date}",
|
||||
"badges.label.reason": "Причина: {reason}",
|
||||
"badges.label.count": "Количество: {count}",
|
||||
|
||||
"badges.granted.not_yet": "Ещё не выдан.",
|
||||
"badges.granted.multiple": "Выдан {times, plural, one {# раз} few {# раза} many {# раз} other {# раз}} {users, plural, one {# пользователю} few {# пользователям} many {# пользователям} other {# пользователям}}.",
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
display: flex;
|
||||
align-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
#showMoreButton {
|
||||
@ -23,6 +24,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
.badge-stacked {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.badge-stack-count {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -4px;
|
||||
background: var(--button-bg, #166de0);
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding: 1px 3px;
|
||||
border-radius: 6px;
|
||||
min-width: 14px;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#grantBadgeButton {
|
||||
margin-top: 4px;
|
||||
padding-left: 0;
|
||||
|
||||
@ -32,6 +32,11 @@ type Props = {
|
||||
};
|
||||
}
|
||||
|
||||
type BadgeGroup = {
|
||||
badge: UserBadge;
|
||||
count: number;
|
||||
}
|
||||
|
||||
type State = {
|
||||
badges?: UserBadge[];
|
||||
loaded?: boolean;
|
||||
@ -55,12 +60,12 @@ class BadgeList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
if (this.state.badges !== prevState.badges) {
|
||||
const nBadges = this.state.badges?.length || 0;
|
||||
const toShow = nBadges < MAX_BADGES ? nBadges : MAX_BADGES;
|
||||
if (this.state.badges !== prevState.badges && this.state.badges) {
|
||||
const groups = this.groupBadges(this.state.badges);
|
||||
const toShow = Math.min(groups.length, MAX_BADGES);
|
||||
const names: string[] = [];
|
||||
for (let i = 0; i < toShow; i++) {
|
||||
const badge = this.state.badges![i];
|
||||
const {badge} = groups[i];
|
||||
if (badge.image_type === IMAGE_TYPE_EMOJI) {
|
||||
names.push(badge.image);
|
||||
}
|
||||
@ -70,6 +75,19 @@ class BadgeList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
onMoreClick = () => {
|
||||
if (!this.props.openRHS) {
|
||||
return;
|
||||
@ -78,6 +96,7 @@ class BadgeList extends React.PureComponent<Props, State> {
|
||||
if (this.props.currentUserID === this.props.user.id) {
|
||||
this.props.actions.setRHSView(RHS_STATE_MY);
|
||||
this.props.openRHS();
|
||||
this.props.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -105,58 +124,83 @@ class BadgeList extends React.PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const {intl} = this.props;
|
||||
const nBadges = this.state.badges?.length || 0;
|
||||
const toShow = nBadges < MAX_BADGES ? nBadges : MAX_BADGES;
|
||||
const groups = this.state.badges ? this.groupBadges(this.state.badges) : [];
|
||||
const nGroups = groups.length;
|
||||
const toShow = Math.min(nGroups, MAX_BADGES);
|
||||
|
||||
const nameLabel = intl.formatMessage(
|
||||
{id: 'badges.label.name', defaultMessage: 'Название:'},
|
||||
);
|
||||
const descLabel = intl.formatMessage(
|
||||
{id: 'badges.label.description', defaultMessage: 'Описание:'},
|
||||
);
|
||||
|
||||
const content: React.ReactNode[] = [];
|
||||
for (let i = 0; i < toShow; i++) {
|
||||
const badge = this.state.badges![i];
|
||||
const time = new Date(badge.time);
|
||||
const nameLabel = intl.formatMessage(
|
||||
{id: 'badges.label.name', defaultMessage: 'Название:'},
|
||||
);
|
||||
const descLabel = intl.formatMessage(
|
||||
{id: 'badges.label.description', defaultMessage: 'Описание:'},
|
||||
);
|
||||
let reason: string | null = null;
|
||||
if (badge.reason) {
|
||||
reason = intl.formatMessage(
|
||||
{id: 'badges.label.reason', defaultMessage: 'Причина: {reason}'},
|
||||
{reason: badge.reason},
|
||||
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 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'})},
|
||||
);
|
||||
const tooltipLines = [
|
||||
nameLabel + ' ' + badge.name,
|
||||
descLabel + ' ' + badge.description,
|
||||
reason,
|
||||
grantedBy,
|
||||
grantedAt,
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
const badgeComponent = (
|
||||
<TooltipWrapper tooltipContent={tooltipLines}>
|
||||
<a onClick={() => this.onBadgeClick(badge)}>
|
||||
<BadgeImage
|
||||
badge={badge}
|
||||
size={BADGE_SIZE}
|
||||
/>
|
||||
<span className='badge-stacked'>
|
||||
<BadgeImage
|
||||
badge={badge}
|
||||
size={BADGE_SIZE}
|
||||
/>
|
||||
{count > 1 && (
|
||||
<span className='badge-stack-count'>
|
||||
{'×'}{count}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</a>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
content.push(badgeComponent);
|
||||
}
|
||||
|
||||
let andMore: React.ReactNode = null;
|
||||
if (nBadges > MAX_BADGES) {
|
||||
if (nGroups > MAX_BADGES) {
|
||||
const andMoreText = intl.formatMessage(
|
||||
{id: 'badges.and_more', defaultMessage: 'и ещё {count}. Нажмите, чтобы увидеть все.'},
|
||||
{count: nBadges - MAX_BADGES},
|
||||
{count: nGroups - MAX_BADGES},
|
||||
);
|
||||
andMore = (
|
||||
<TooltipWrapper tooltipContent={andMoreText}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user