Use react-query and zod for announcements
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
@ -3,7 +3,7 @@ import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { getTextDirection } from 'soapbox/utils/rtl';
|
||||
|
||||
import type { Announcement as AnnouncementEntity, Mention as MentionEntity } from 'soapbox/types/entities';
|
||||
import type { Announcement as AnnouncementEntity, Mention as MentionEntity } from 'soapbox/schemas';
|
||||
|
||||
interface IAnnouncementContent {
|
||||
announcement: AnnouncementEntity;
|
||||
@ -67,7 +67,7 @@ const AnnouncementContent: React.FC<IAnnouncementContent> = ({ announcement }) =
|
||||
} else if (link.textContent?.charAt(0) === '#' || (link.previousSibling?.textContent?.charAt(link.previousSibling.textContent.length - 1) === '#')) {
|
||||
link.addEventListener('click', onHashtagClick.bind(link, link.text), false);
|
||||
} else {
|
||||
const status = announcement.statuses.get(link.href);
|
||||
const status = announcement.statuses[link.href];
|
||||
if (status) {
|
||||
link.addEventListener('click', onStatusClick.bind(this, status), false);
|
||||
}
|
||||
|
||||
@ -9,16 +9,14 @@ import AnnouncementContent from './announcement-content';
|
||||
import ReactionsBar from './reactions-bar';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import type { Announcement as AnnouncementEntity } from 'soapbox/types/entities';
|
||||
import type { Announcement as AnnouncementEntity } from 'soapbox/schemas';
|
||||
|
||||
interface IAnnouncement {
|
||||
announcement: AnnouncementEntity;
|
||||
addReaction: (id: string, name: string) => void;
|
||||
removeReaction: (id: string, name: string) => void;
|
||||
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
|
||||
}
|
||||
|
||||
const Announcement: React.FC<IAnnouncement> = ({ announcement, addReaction, removeReaction, emojiMap }) => {
|
||||
const Announcement: React.FC<IAnnouncement> = ({ announcement, emojiMap }) => {
|
||||
const features = useFeatures();
|
||||
|
||||
const startsAt = announcement.starts_at && new Date(announcement.starts_at);
|
||||
@ -64,8 +62,6 @@ const Announcement: React.FC<IAnnouncement> = ({ announcement, addReaction, remo
|
||||
<ReactionsBar
|
||||
reactions={announcement.reactions}
|
||||
announcementId={announcement.id}
|
||||
addReaction={addReaction}
|
||||
removeReaction={removeReaction}
|
||||
emojiMap={emojiMap}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -5,9 +5,9 @@ import { FormattedMessage } from 'react-intl';
|
||||
import ReactSwipeableViews from 'react-swipeable-views';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { addReaction as addReactionAction, removeReaction as removeReactionAction } from 'soapbox/actions/announcements';
|
||||
import { useAnnouncements } from 'soapbox/api/hooks/announcements';
|
||||
import { Card, HStack, Widget } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import Announcement from './announcement';
|
||||
|
||||
@ -16,36 +16,30 @@ import type { RootState } from 'soapbox/store';
|
||||
const customEmojiMap = createSelector([(state: RootState) => state.custom_emojis], items => (items as ImmutableList<ImmutableMap<string, string>>).reduce((map, emoji) => map.set(emoji.get('shortcode')!, emoji), ImmutableMap<string, ImmutableMap<string, string>>()));
|
||||
|
||||
const AnnouncementsPanel = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const emojiMap = useAppSelector(state => customEmojiMap(state));
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
const announcements = useAppSelector((state) => state.announcements.items);
|
||||
const { data: announcements } = useAnnouncements();
|
||||
|
||||
const addReaction = (id: string, name: string) => dispatch(addReactionAction(id, name));
|
||||
const removeReaction = (id: string, name: string) => dispatch(removeReactionAction(id, name));
|
||||
|
||||
if (announcements.size === 0) return null;
|
||||
if (!announcements || announcements.length === 0) return null;
|
||||
|
||||
const handleChangeIndex = (index: number) => {
|
||||
setIndex(index % announcements.size);
|
||||
setIndex(index % announcements.length);
|
||||
};
|
||||
|
||||
return (
|
||||
<Widget title={<FormattedMessage id='announcements.title' defaultMessage='Announcements' />}>
|
||||
<Card className='relative black:rounded-xl black:border black:border-gray-800' size='md' variant='rounded'>
|
||||
<ReactSwipeableViews animateHeight index={index} onChangeIndex={handleChangeIndex}>
|
||||
{announcements.map((announcement) => (
|
||||
{announcements!.map((announcement) => (
|
||||
<Announcement
|
||||
key={announcement.id}
|
||||
announcement={announcement}
|
||||
emojiMap={emojiMap}
|
||||
addReaction={addReaction}
|
||||
removeReaction={removeReaction}
|
||||
/>
|
||||
)).reverse()}
|
||||
</ReactSwipeableViews>
|
||||
{announcements.size > 1 && (
|
||||
{announcements.length > 1 && (
|
||||
<HStack space={2} alignItems='center' justifyContent='center' className='relative'>
|
||||
{announcements.map((_, i) => (
|
||||
<button
|
||||
|
||||
@ -1,31 +1,32 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useAnnouncements } from 'soapbox/api/hooks/announcements';
|
||||
import AnimatedNumber from 'soapbox/components/animated-number';
|
||||
import unicodeMapping from 'soapbox/features/emoji/mapping';
|
||||
|
||||
import Emoji from './emoji';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import type { AnnouncementReaction } from 'soapbox/types/entities';
|
||||
import type { AnnouncementReaction } from 'soapbox/schemas';
|
||||
|
||||
interface IReaction {
|
||||
announcementId: string;
|
||||
reaction: AnnouncementReaction;
|
||||
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
|
||||
addReaction: (id: string, name: string) => void;
|
||||
removeReaction: (id: string, name: string) => void;
|
||||
style: React.CSSProperties;
|
||||
}
|
||||
|
||||
const Reaction: React.FC<IReaction> = ({ announcementId, reaction, addReaction, removeReaction, emojiMap, style }) => {
|
||||
const Reaction: React.FC<IReaction> = ({ announcementId, reaction, emojiMap, style }) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const { addReaction, removeReaction } = useAnnouncements();
|
||||
|
||||
const handleClick = () => {
|
||||
if (reaction.me) {
|
||||
removeReaction(announcementId, reaction.name);
|
||||
removeReaction({ announcementId, name: reaction.name });
|
||||
} else {
|
||||
addReaction(announcementId, reaction.name);
|
||||
addReaction({ announcementId, name: reaction.name });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -2,28 +2,28 @@ import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { TransitionMotion, spring } from 'react-motion';
|
||||
|
||||
import { useAnnouncements } from 'soapbox/api/hooks/announcements';
|
||||
import EmojiPickerDropdown from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container';
|
||||
import { useSettings } from 'soapbox/hooks';
|
||||
|
||||
import Reaction from './reaction';
|
||||
|
||||
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import type { Emoji, NativeEmoji } from 'soapbox/features/emoji';
|
||||
import type { AnnouncementReaction } from 'soapbox/types/entities';
|
||||
import type { AnnouncementReaction } from 'soapbox/schemas';
|
||||
|
||||
interface IReactionsBar {
|
||||
announcementId: string;
|
||||
reactions: ImmutableList<AnnouncementReaction>;
|
||||
reactions: AnnouncementReaction[];
|
||||
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
|
||||
addReaction: (id: string, name: string) => void;
|
||||
removeReaction: (id: string, name: string) => void;
|
||||
}
|
||||
|
||||
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addReaction, removeReaction, emojiMap }) => {
|
||||
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, emojiMap }) => {
|
||||
const { reduceMotion } = useSettings();
|
||||
const { addReaction } = useAnnouncements();
|
||||
|
||||
const handleEmojiPick = (data: Emoji) => {
|
||||
addReaction(announcementId, (data as NativeEmoji).native.replace(/:/g, ''));
|
||||
addReaction({ announcementId, name: (data as NativeEmoji).native.replace(/:/g, '') });
|
||||
};
|
||||
|
||||
const willEnter = () => ({ scale: reduceMotion ? 1 : 0 });
|
||||
@ -36,25 +36,23 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addR
|
||||
key: reaction.name,
|
||||
data: reaction,
|
||||
style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) },
|
||||
})).toArray();
|
||||
}));
|
||||
|
||||
return (
|
||||
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
|
||||
{items => (
|
||||
<div className={clsx('flex flex-wrap items-center gap-1', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
|
||||
<div className={clsx('flex flex-wrap items-center gap-1', { 'reactions-bar--empty': visibleReactions.length === 0 })}>
|
||||
{items.map(({ key, data, style }) => (
|
||||
<Reaction
|
||||
key={key}
|
||||
reaction={data}
|
||||
style={{ transform: `scale(${style.scale})`, position: style.scale < 0.5 ? 'absolute' : 'static' }}
|
||||
announcementId={announcementId}
|
||||
addReaction={addReaction}
|
||||
removeReaction={removeReaction}
|
||||
emojiMap={emojiMap}
|
||||
/>
|
||||
))}
|
||||
|
||||
{visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={handleEmojiPick} />}
|
||||
{visibleReactions.length < 8 && <EmojiPickerDropdown onPickEmoji={handleEmojiPick} />}
|
||||
</div>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
|
||||
Reference in New Issue
Block a user