chore: bug fix cleanup (#4)

This commit is contained in:
hampus-fluxer
2026-01-03 06:44:40 +01:00
committed by GitHub
parent 275126d61b
commit c9c5dceb47
80 changed files with 4639 additions and 3709 deletions

View File

@@ -103,6 +103,7 @@
display: flex;
flex-direction: column;
min-height: 0;
background-color: var(--background-tertiary);
}
.listWrapper {
@@ -118,6 +119,33 @@
padding: var(--spacing-3) var(--spacing-2) 0;
}
.emptyState {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.emptyStateInner {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: var(--text-primary-muted);
opacity: 0.7;
}
.emptyIcon {
font-size: 42px;
line-height: 1;
}
.emptyLabel {
font-size: 0.875rem;
}
.header {
display: flex;
align-items: center;

View File

@@ -18,6 +18,7 @@
*/
import {useLingui} from '@lingui/react/macro';
import {SmileySadIcon} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite';
import React from 'react';
import * as EmojiPickerActionCreators from '~/actions/EmojiPickerActionCreators';
@@ -37,7 +38,7 @@ import {useSearchInputAutofocus} from '~/hooks/useSearchInputAutofocus';
import {ComponentDispatch} from '~/lib/ComponentDispatch';
import UnicodeEmojis, {EMOJI_SPRITES} from '~/lib/UnicodeEmojis';
import ChannelStore from '~/stores/ChannelStore';
import EmojiStore, {type Emoji} from '~/stores/EmojiStore';
import EmojiStore, {type Emoji, normalizeEmojiSearchQuery} from '~/stores/EmojiStore';
import {checkEmojiAvailability, shouldShowEmojiPremiumUpsell} from '~/utils/ExpressionPermissionUtils';
import {shouldShowPremiumFeatures} from '~/utils/PremiumUtils';
@@ -53,14 +54,16 @@ export const EmojiPicker = observer(
const [searchTerm, setSearchTerm] = React.useState('');
const [hoveredEmoji, setHoveredEmoji] = React.useState<Emoji | null>(null);
const [renderedEmojis, setRenderedEmojis] = React.useState<Array<Emoji>>([]);
const [allEmojis, setAllEmojis] = React.useState<Array<Emoji>>([]);
const [selectedRow, setSelectedRow] = React.useState(-1);
const [selectedColumn, setSelectedColumn] = React.useState(-1);
const [shouldScrollOnSelection, setShouldScrollOnSelection] = React.useState(false);
const scrollerRef = React.useRef<ScrollerHandle>(null);
const searchInputRef = React.useRef<HTMLInputElement>(null);
const emojiRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
const normalizedSearchTerm = React.useMemo(() => normalizeEmojiSearchQuery(searchTerm), [searchTerm]);
const {i18n} = useLingui();
const {i18n, t} = useLingui();
const channel = channelId ? (ChannelStore.getChannel(channelId) ?? null) : null;
const categoryRefs = React.useRef<Map<string, HTMLDivElement>>(new Map());
const forceUpdate = useForceUpdate();
@@ -81,7 +84,7 @@ export const EmojiPicker = observer(
}, []);
React.useEffect(() => {
const emojis = EmojiStore.search(channel, searchTerm).slice();
const emojis = EmojiStore.search(channel, normalizedSearchTerm).slice();
setRenderedEmojis(emojis);
if (emojis.length > 0) {
setSelectedRow(0);
@@ -91,7 +94,12 @@ export const EmojiPicker = observer(
setSelectedColumn(-1);
setHoveredEmoji(null);
}
}, [channel, searchTerm]);
}, [channel, normalizedSearchTerm]);
React.useEffect(() => {
const emojis = EmojiStore.search(channel, '').slice();
setAllEmojis(emojis);
}, [channel]);
React.useEffect(() => {
return ComponentDispatch.subscribe('EMOJI_PICKER_RERENDER', forceUpdate);
@@ -99,10 +107,13 @@ export const EmojiPicker = observer(
useSearchInputAutofocus(searchInputRef);
const {favoriteEmojis, frequentlyUsedEmojis, customEmojisByGuildId, unicodeEmojisByCategory} =
useEmojiCategories(renderedEmojis);
const {favoriteEmojis, frequentlyUsedEmojis, customEmojisByGuildId, unicodeEmojisByCategory} = useEmojiCategories(
allEmojis,
renderedEmojis,
);
const showFrequentlyUsedButton = frequentlyUsedEmojis.length > 0 && !normalizedSearchTerm;
const virtualRows = useVirtualRows(
searchTerm,
normalizedSearchTerm,
renderedEmojis,
favoriteEmojis,
frequentlyUsedEmojis,
@@ -110,7 +121,8 @@ export const EmojiPicker = observer(
unicodeEmojisByCategory,
);
const showPremiumUpsell = shouldShowPremiumFeatures() && shouldShowEmojiPremiumUpsell(channel);
const showPremiumUpsell =
shouldShowPremiumFeatures() && shouldShowEmojiPremiumUpsell(channel) && !normalizedSearchTerm;
const sections = React.useMemo(() => {
const result: Array<number> = [];
@@ -223,11 +235,12 @@ export const EmojiPicker = observer(
className={`${styles.list} ${styles.listWrapper}`}
fade={false}
key="emoji-picker-scroller"
reserveScrollbarTrack={false}
reserveScrollbarTrack={true}
>
{showPremiumUpsell && <PremiumUpsellBanner />}
{virtualRows.map((row, index) => {
const emojiRowIndex = virtualRows.slice(0, index).filter((r) => r.type === 'emoji-row').length;
const needsSpacingAfter = row.type === 'emoji-row' && virtualRows[index + 1]?.type === 'header';
return (
<div
@@ -241,6 +254,7 @@ export const EmojiPicker = observer(
}
: undefined
}
style={row.type === 'emoji-row' && needsSpacingAfter ? {marginBottom: '12px'} : undefined}
>
<VirtualizedRow
row={row}
@@ -260,6 +274,16 @@ export const EmojiPicker = observer(
);
})}
</Scroller>
{renderedEmojis.length === 0 && (
<div className={styles.emptyState}>
<div className={styles.emptyStateInner}>
<div className={styles.emptyIcon}>
<SmileySadIcon weight="duotone" />
</div>
<div className={styles.emptyLabel}>{t`No emojis match your search`}</div>
</div>
</div>
)}
</div>
</div>
<EmojiPickerInspector hoveredEmoji={hoveredEmoji} />
@@ -268,6 +292,7 @@ export const EmojiPicker = observer(
customEmojisByGuildId={customEmojisByGuildId}
unicodeEmojisByCategory={unicodeEmojisByCategory}
handleCategoryClick={handleCategoryClick}
showFrequentlyUsedButton={showFrequentlyUsedButton}
/>
</div>
);

View File

@@ -280,7 +280,12 @@ export const Messages = observer(function Messages({channel}: {channel: ChannelR
const data = payload as {channelId?: string; heightDelta?: number} | undefined;
if (data?.channelId && data.channelId !== channel.id) return;
scrollManager.handleScroll();
const heightDelta = data?.heightDelta;
const handledLayoutShift = typeof heightDelta === 'number' ? scrollManager.applyLayoutShift(heightDelta) : false;
if (!handledLayoutShift) {
scrollManager.handleScroll();
}
};
const onFocusBottommostMessage = (payload?: unknown) => {

View File

@@ -35,7 +35,7 @@ import {useForceUpdate} from '~/hooks/useForceUpdate';
import {ComponentDispatch} from '~/lib/ComponentDispatch';
import UnicodeEmojis, {EMOJI_SPRITES} from '~/lib/UnicodeEmojis';
import ChannelStore from '~/stores/ChannelStore';
import EmojiStore, {type Emoji} from '~/stores/EmojiStore';
import EmojiStore, {type Emoji, normalizeEmojiSearchQuery} from '~/stores/EmojiStore';
export const MobileEmojiPicker = observer(
({
@@ -57,6 +57,7 @@ export const MobileEmojiPicker = observer(
const [internalSearchTerm, setInternalSearchTerm] = React.useState('');
const [hoveredEmoji, setHoveredEmoji] = React.useState<Emoji | null>(null);
const [renderedEmojis, setRenderedEmojis] = React.useState<Array<Emoji>>([]);
const [allEmojis, setAllEmojis] = React.useState<Array<Emoji>>([]);
const scrollerRef = React.useRef<ScrollerHandle>(null);
const emojiRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
@@ -67,6 +68,7 @@ export const MobileEmojiPicker = observer(
const searchTerm = externalSearchTerm ?? internalSearchTerm;
const setSearchTerm = externalSetSearchTerm ?? setInternalSearchTerm;
const normalizedSearchTerm = React.useMemo(() => normalizeEmojiSearchQuery(searchTerm), [searchTerm]);
const spriteSheetSizes = React.useMemo(() => {
const nonDiversitySize = [
@@ -83,18 +85,26 @@ export const MobileEmojiPicker = observer(
}, []);
React.useEffect(() => {
const emojis = EmojiStore.search(null, searchTerm).slice();
const emojis = EmojiStore.search(channel, normalizedSearchTerm).slice();
setRenderedEmojis(emojis);
}, [searchTerm]);
}, [channel, normalizedSearchTerm]);
React.useEffect(() => {
const emojis = EmojiStore.search(channel, '').slice();
setAllEmojis(emojis);
}, [channel]);
React.useEffect(() => {
return ComponentDispatch.subscribe('EMOJI_PICKER_RERENDER', forceUpdate);
});
const {customEmojisByGuildId, unicodeEmojisByCategory, favoriteEmojis, frequentlyUsedEmojis} =
useEmojiCategories(renderedEmojis);
const {customEmojisByGuildId, unicodeEmojisByCategory, favoriteEmojis, frequentlyUsedEmojis} = useEmojiCategories(
allEmojis,
renderedEmojis,
);
const showFrequentlyUsedButton = frequentlyUsedEmojis.length > 0 && !normalizedSearchTerm;
const virtualRows = useVirtualRows(
searchTerm,
normalizedSearchTerm,
renderedEmojis,
favoriteEmojis,
frequentlyUsedEmojis,
@@ -168,6 +178,7 @@ export const MobileEmojiPicker = observer(
unicodeEmojisByCategory={unicodeEmojisByCategory}
handleCategoryClick={handleCategoryClick}
horizontal={true}
showFrequentlyUsedButton={showFrequentlyUsedButton}
/>
</div>
</div>

View File

@@ -61,6 +61,9 @@ export const MobileStickersPicker = observer(
const [searchTerm, setSearchTerm] = React.useState('');
const [hoveredSticker, setHoveredSticker] = React.useState<GuildStickerRecord | null>(null);
const [renderedStickers, setRenderedStickers] = React.useState<ReadonlyArray<GuildStickerRecord>>([]);
const [allStickersForCategories, setAllStickersForCategories] = React.useState<ReadonlyArray<GuildStickerRecord>>(
[],
);
const scrollerRef = React.useRef<ScrollerHandle>(null);
const searchInputRef = React.useRef<HTMLInputElement>(null);
const stickerRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
@@ -74,13 +77,20 @@ export const MobileStickersPicker = observer(
setRenderedStickers(stickers);
}, [channel, searchTerm]);
React.useEffect(() => {
setAllStickersForCategories(StickerStore.getAllStickers());
}, []);
React.useEffect(() => {
return ComponentDispatch.subscribe('STICKER_PICKER_RERENDER', forceUpdate);
});
useSearchInputAutofocus(searchInputRef);
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(renderedStickers);
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(
allStickersForCategories,
renderedStickers,
);
const virtualRows = useVirtualRows(
searchTerm,
renderedStickers,

View File

@@ -55,6 +55,9 @@ export const StickersPicker = observer(
const [searchTerm, setSearchTerm] = React.useState('');
const [hoveredSticker, setHoveredSticker] = React.useState<GuildStickerRecord | null>(null);
const [renderedStickers, setRenderedStickers] = React.useState<ReadonlyArray<GuildStickerRecord>>([]);
const [allStickersForCategories, setAllStickersForCategories] = React.useState<ReadonlyArray<GuildStickerRecord>>(
[],
);
const [selectedRow, setSelectedRow] = React.useState(-1);
const [selectedColumn, setSelectedColumn] = React.useState(-1);
const [shouldScrollOnSelection, setShouldScrollOnSelection] = React.useState(false);
@@ -79,13 +82,20 @@ export const StickersPicker = observer(
}
}, [channel, searchTerm]);
React.useEffect(() => {
setAllStickersForCategories(StickerStore.getAllStickers());
}, []);
React.useEffect(() => {
return ComponentDispatch.subscribe('STICKER_PICKER_RERENDER', forceUpdate);
});
useSearchInputAutofocus(searchInputRef);
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(renderedStickers);
const {favoriteStickers, frequentlyUsedStickers, stickersByGuildId} = useStickerCategories(
allStickersForCategories,
renderedStickers,
);
const virtualRows = useVirtualRows(
searchTerm,
renderedStickers,
@@ -98,7 +108,8 @@ export const StickersPicker = observer(
const allStickers = React.useMemo(() => StickerStore.getAllStickers(), []);
const hasNoStickersAtAll = allStickers.length === 0;
const showPremiumUpsell = shouldShowPremiumFeatures() && shouldShowStickerPremiumUpsell(channel);
const isSearching = searchTerm.trim().length > 0;
const showPremiumUpsell = shouldShowPremiumFeatures() && shouldShowStickerPremiumUpsell(channel) && !isSearching;
const sections = React.useMemo(() => {
const result: Array<number> = [];
@@ -216,21 +227,6 @@ export const StickersPicker = observer(
/>
);
if (renderedStickers.length === 0 && searchTerm) {
return (
<div className={gifStyles.gifPickerContainer}>
<ExpressionPickerHeaderPortal>{renderSearchBar()}</ExpressionPickerHeaderPortal>
<div className={gifStyles.gifPickerMain}>
<PickerEmptyState
icon={SmileySadIcon}
title={t`No Stickers Found`}
description={t`Try a different search term`}
/>
</div>
</div>
);
}
return (
<div className={styles.container}>
<ExpressionPickerHeaderPortal>{renderSearchBar()}</ExpressionPickerHeaderPortal>
@@ -242,11 +238,12 @@ export const StickersPicker = observer(
className={`${styles.list} ${styles.listWrapper}`}
fade={false}
key="stickers-picker-scroller"
reserveScrollbarTrack={false}
reserveScrollbarTrack={true}
>
{showPremiumUpsell && <PremiumUpsellBanner />}
{virtualRows.map((row, index) => {
const stickerRowIndex = virtualRows.slice(0, index).filter((r) => r.type === 'sticker-row').length;
const needsSpacingAfter = row.type === 'sticker-row' && virtualRows[index + 1]?.type === 'header';
return (
<div
@@ -260,6 +257,7 @@ export const StickersPicker = observer(
}
: undefined
}
style={row.type === 'sticker-row' && needsSpacingAfter ? {marginBottom: '12px'} : undefined}
>
<VirtualRowWrapper
row={row}
@@ -278,6 +276,16 @@ export const StickersPicker = observer(
);
})}
</Scroller>
{renderedStickers.length === 0 && (
<div className={styles.emptyState}>
<div className={styles.emptyStateInner}>
<div className={styles.emptyIcon}>
<SmileySadIcon weight="duotone" />
</div>
<div className={styles.emptyLabel}>{t`No stickers match your search`}</div>
</div>
</div>
)}
</div>
</div>
<StickerPickerInspector hoveredSticker={hoveredSticker} />

View File

@@ -133,6 +133,27 @@ const getOptimizedMediaURL = (
});
};
const SuppressEmbedsConfirmModal: FC<{message: MessageRecord}> = ({message}) => {
const {t} = useLingui();
return (
<ConfirmModal
title={t`Suppress Embeds`}
description={
<Trans>
Are you sure you want to suppress all link embeds on this message? This action will hide all embeds from this
message.
</Trans>
}
primaryText={t`Suppress Embeds`}
primaryVariant="danger-primary"
onPrimary={async () => {
await MessageActionCreators.toggleSuppressEmbeds(message.channelId, message.id, message.flags);
}}
/>
);
};
const shouldRenderAsInlineThumbnail = (media?: EmbedMedia): boolean => {
if (!isValidMedia(media)) return false;
@@ -697,27 +718,7 @@ export const Embed: FC<EmbedProps> = observer(({embed, message, embedIndex, onDe
}, [message, channel]);
const handleSuppressEmbeds = useCallback(() => {
ModalActionCreators.push(
modal(() => {
const {t} = useLingui();
return (
<ConfirmModal
title={t`Suppress Embeds`}
description={
<Trans>
Are you sure you want to suppress all link embeds on this message? This action will hide all embeds from
this message.
</Trans>
}
primaryText={t`Suppress Embeds`}
primaryVariant="danger-primary"
onPrimary={async () => {
await MessageActionCreators.toggleSuppressEmbeds(message.channelId, message.id, message.flags);
}}
/>
);
}),
);
ModalActionCreators.push(modal(() => <SuppressEmbedsConfirmModal message={message} />));
}, [message]);
const showSuppressButton = !isMobile && canSuppressEmbeds() && AccessibilityStore.showSuppressEmbedsButton;

View File

@@ -18,6 +18,7 @@
*/
import {useLingui} from '@lingui/react/macro';
import {ClockIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import styles from '~/components/channel/EmojiPicker.module.css';
@@ -31,6 +32,7 @@ interface EmojiPickerCategoryListProps {
customEmojisByGuildId: Map<string, Array<Emoji>>;
unicodeEmojisByCategory: Map<string, Array<Emoji>>;
handleCategoryClick: (category: string) => void;
showFrequentlyUsedButton: boolean;
horizontal?: boolean;
}
@@ -39,12 +41,23 @@ export const EmojiPickerCategoryList = observer(
customEmojisByGuildId,
unicodeEmojisByCategory,
handleCategoryClick,
showFrequentlyUsedButton = false,
horizontal = false,
}: EmojiPickerCategoryListProps) => {
const {i18n} = useLingui();
if (horizontal) {
return (
<div className={styles.horizontalCategories}>
{showFrequentlyUsedButton && (
<button
type="button"
onClick={() => handleCategoryClick('frequently-used')}
className={clsx(styles.categoryListIcon, styles.textPrimaryMuted)}
aria-label={i18n._('Frequently Used')}
>
<ClockIcon className={styles.iconSize} />
</button>
)}
{Array.from(customEmojisByGuildId.keys()).map((guildId) => {
const guild = GuildStore.getGuild(guildId)!;
return (
@@ -81,6 +94,17 @@ export const EmojiPickerCategoryList = observer(
<div className={styles.categoryList}>
<div className={styles.categoryListScroll}>
<div className={styles.listItems}>
{showFrequentlyUsedButton && (
<Tooltip text={i18n._('Frequently Used')} position="left">
<button
type="button"
onClick={() => handleCategoryClick('frequently-used')}
className={clsx(styles.categoryListIcon, styles.textPrimaryMuted)}
>
<ClockIcon className={styles.iconSize} />
</button>
</Tooltip>
)}
{Array.from(customEmojisByGuildId.keys()).map((guildId) => {
const guild = GuildStore.getGuild(guildId)!;
return (

View File

@@ -23,15 +23,15 @@ import EmojiPickerStore from '~/stores/EmojiPickerStore';
import type {Emoji} from '~/stores/EmojiStore';
import GuildListStore from '~/stores/GuildListStore';
export const useEmojiCategories = (renderedEmojis: Array<Emoji>) => {
export const useEmojiCategories = (allEmojis: Array<Emoji>, _renderedEmojis: Array<Emoji>) => {
const guilds = GuildListStore.guilds;
const favoriteEmojis = EmojiPickerStore.getFavoriteEmojis(renderedEmojis);
const favoriteEmojis = EmojiPickerStore.getFavoriteEmojis(allEmojis);
const frequentlyUsedEmojis = EmojiPickerStore.getFrecentEmojis(renderedEmojis, 42);
const frequentlyUsedEmojis = EmojiPickerStore.getFrecentEmojis(allEmojis, 42);
const customEmojisByGuildId = React.useMemo(() => {
const guildEmojis = renderedEmojis.filter((emoji) => emoji.guildId != null);
const guildEmojis = allEmojis.filter((emoji) => emoji.guildId != null);
const guildEmojisByGuildId = new Map<string, Array<Emoji>>();
for (const guildEmoji of guildEmojis) {
@@ -50,10 +50,10 @@ export const useEmojiCategories = (renderedEmojis: Array<Emoji>) => {
}
return sortedGuildEmojisByGuildId;
}, [renderedEmojis, guilds]);
}, [allEmojis, guilds]);
const unicodeEmojisByCategory = React.useMemo(() => {
const unicodeEmojis = renderedEmojis.filter((emoji) => emoji.guildId == null);
const unicodeEmojis = allEmojis.filter((emoji) => emoji.guildId == null);
const unicodeEmojisByCategory = new Map<string, Array<Emoji>>();
for (const emoji of unicodeEmojis) {
@@ -76,7 +76,7 @@ export const useEmojiCategories = (renderedEmojis: Array<Emoji>) => {
}
return sortedUnicodeEmojisByCategory;
}, [renderedEmojis]);
}, [allEmojis]);
return {
favoriteEmojis,

View File

@@ -42,6 +42,7 @@ import {
} from '~/components/uikit/ContextMenu/ContextMenuIcons';
import type {MenuGroupType, MenuItemType} from '~/components/uikit/MenuBottomSheet/MenuBottomSheet';
import type {MessageRecord} from '~/records/MessageRecord';
import ChannelStore from '~/stores/ChannelStore';
import EmojiPickerStore from '~/stores/EmojiPickerStore';
import type {Emoji} from '~/stores/EmojiStore';
import EmojiStore from '~/stores/EmojiStore';
@@ -73,7 +74,8 @@ export const useMessageActionMenuData = (
const permissions = useMessagePermissions(message);
const handlers = React.useMemo(() => createMessageActionHandlers(message, {onClose}), [message, onClose]);
const isSaved = React.useMemo(() => SavedMessagesStore.isSaved(message.id), [message.id]);
const allEmojis = React.useMemo(() => EmojiStore.search(null, ''), []);
const channel = React.useMemo(() => ChannelStore.getChannel(message.channelId) ?? null, [message.channelId]);
const allEmojis = React.useMemo(() => EmojiStore.search(channel, ''), [channel]);
const quickReactionEmojis = React.useMemo(
() => EmojiPickerStore.getQuickReactionEmojis(allEmojis, quickReactionCount),
[allEmojis, quickReactionCount],

View File

@@ -33,8 +33,8 @@
.stickerGrid {
display: grid;
gap: 0.5rem;
padding: 0.5rem;
gap: var(--spacing-2);
padding: 0 0 0.5rem;
}
.stickerButton {

View File

@@ -22,22 +22,25 @@ import type {GuildStickerRecord} from '~/records/GuildStickerRecord';
import GuildListStore from '~/stores/GuildListStore';
import StickerPickerStore from '~/stores/StickerPickerStore';
export const useStickerCategories = (renderedStickers: ReadonlyArray<GuildStickerRecord>) => {
export const useStickerCategories = (
allStickers: ReadonlyArray<GuildStickerRecord>,
_renderedStickers: ReadonlyArray<GuildStickerRecord>,
) => {
const guilds = GuildListStore.guilds;
const stickerPickerState = StickerPickerStore;
const favoriteStickers = React.useMemo(() => {
return StickerPickerStore.getFavoriteStickers(renderedStickers);
}, [renderedStickers, stickerPickerState.favoriteStickers]);
return StickerPickerStore.getFavoriteStickers(allStickers);
}, [allStickers, stickerPickerState.favoriteStickers]);
const frequentlyUsedStickers = React.useMemo(() => {
return StickerPickerStore.getFrecentStickers(renderedStickers, 42);
}, [renderedStickers, stickerPickerState.stickerUsage]);
return StickerPickerStore.getFrecentStickers(allStickers, 42);
}, [allStickers, stickerPickerState.stickerUsage]);
const stickersByGuildId = React.useMemo(() => {
const guildStickersMap = new Map<string, Array<GuildStickerRecord>>();
for (const sticker of renderedStickers) {
for (const sticker of allStickers) {
if (!guildStickersMap.has(sticker.guildId)) {
guildStickersMap.set(sticker.guildId, []);
}
@@ -53,7 +56,7 @@ export const useStickerCategories = (renderedStickers: ReadonlyArray<GuildSticke
}
return sortedGuildStickersMap;
}, [renderedStickers, guilds]);
}, [allStickers, guilds]);
return {
favoriteStickers,