Web Page: Display stickerset preview (#4529)
This commit is contained in:
parent
1f834d42ed
commit
85bc6cb297
@ -19,6 +19,7 @@ import type {
|
|||||||
ApiVoice,
|
ApiVoice,
|
||||||
ApiWebDocument,
|
ApiWebDocument,
|
||||||
ApiWebPage,
|
ApiWebPage,
|
||||||
|
ApiWebPageStickerData,
|
||||||
ApiWebPageStoryData,
|
ApiWebPageStoryData,
|
||||||
MediaContent,
|
MediaContent,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
@ -35,7 +36,7 @@ import {
|
|||||||
buildApiThumbnailFromStripped,
|
buildApiThumbnailFromStripped,
|
||||||
} from './common';
|
} from './common';
|
||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
import { buildStickerFromDocument } from './symbols';
|
import { buildStickerFromDocument, processStickerResult } from './symbols';
|
||||||
|
|
||||||
export function buildMessageContent(
|
export function buildMessageContent(
|
||||||
mtpMessage: UniversalMessage | GramJs.UpdateServiceNotification,
|
mtpMessage: UniversalMessage | GramJs.UpdateServiceNotification,
|
||||||
@ -694,8 +695,9 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef
|
|||||||
audio = buildAudioFromDocument(document);
|
audio = buildAudioFromDocument(document);
|
||||||
}
|
}
|
||||||
let story: ApiWebPageStoryData | undefined;
|
let story: ApiWebPageStoryData | undefined;
|
||||||
|
let stickers: ApiWebPageStickerData | undefined;
|
||||||
const attributeStory = attributes
|
const attributeStory = attributes
|
||||||
?.find((a: any): a is GramJs.WebPageAttributeStory => a instanceof GramJs.WebPageAttributeStory);
|
?.find((a): a is GramJs.WebPageAttributeStory => a instanceof GramJs.WebPageAttributeStory);
|
||||||
if (attributeStory) {
|
if (attributeStory) {
|
||||||
const peerId = getApiChatIdFromMtpPeer(attributeStory.peer);
|
const peerId = getApiChatIdFromMtpPeer(attributeStory.peer);
|
||||||
story = {
|
story = {
|
||||||
@ -707,6 +709,16 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef
|
|||||||
addStoryToLocalDb(attributeStory.story, peerId);
|
addStoryToLocalDb(attributeStory.story, peerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const attributeStickers = attributes?.find((a): a is GramJs.WebPageAttributeStickerSet => (
|
||||||
|
a instanceof GramJs.WebPageAttributeStickerSet
|
||||||
|
));
|
||||||
|
if (attributeStickers) {
|
||||||
|
stickers = {
|
||||||
|
documents: processStickerResult(attributeStickers.stickers),
|
||||||
|
isEmoji: attributeStickers.emojis,
|
||||||
|
isWithTextColor: attributeStickers.textColor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: Number(id),
|
id: Number(id),
|
||||||
@ -724,6 +736,7 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef
|
|||||||
video,
|
video,
|
||||||
audio,
|
audio,
|
||||||
story,
|
story,
|
||||||
|
stickers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { ApiWebDocument } from './bots';
|
|||||||
import type { ApiGroupCall, PhoneCallAction } from './calls';
|
import type { ApiGroupCall, PhoneCallAction } from './calls';
|
||||||
import type { ApiChat, ApiPeerColor } from './chats';
|
import type { ApiChat, ApiPeerColor } from './chats';
|
||||||
import type { ApiInputStorePaymentPurpose, ApiPremiumGiftCodeOption } from './payments';
|
import type { ApiInputStorePaymentPurpose, ApiPremiumGiftCodeOption } from './payments';
|
||||||
import type { ApiMessageStoryData, ApiWebPageStoryData } from './stories';
|
import type { ApiMessageStoryData, ApiWebPageStickerData, ApiWebPageStoryData } from './stories';
|
||||||
|
|
||||||
export interface ApiDimensions {
|
export interface ApiDimensions {
|
||||||
width: number;
|
width: number;
|
||||||
@ -380,6 +380,7 @@ export interface ApiWebPage {
|
|||||||
document?: ApiDocument;
|
document?: ApiDocument;
|
||||||
video?: ApiVideo;
|
video?: ApiVideo;
|
||||||
story?: ApiWebPageStoryData;
|
story?: ApiWebPageStoryData;
|
||||||
|
stickers?: ApiWebPageStickerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiReplyInfo = ApiMessageReplyInfo | ApiStoryReplyInfo;
|
export type ApiReplyInfo = ApiMessageReplyInfo | ApiStoryReplyInfo;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ApiPrivacySettings } from '../../types';
|
import type { ApiPrivacySettings } from '../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiGeoPoint, ApiMessage, ApiReaction, ApiReactionCount, ApiStoryForwardInfo, MediaContent,
|
ApiGeoPoint, ApiMessage, ApiReaction, ApiReactionCount, ApiSticker, ApiStoryForwardInfo, MediaContent,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
|
|
||||||
export interface ApiStory {
|
export interface ApiStory {
|
||||||
@ -73,6 +73,12 @@ export type ApiWebPageStoryData = {
|
|||||||
peerId: string;
|
peerId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiWebPageStickerData = {
|
||||||
|
documents: ApiSticker[];
|
||||||
|
isEmoji?: boolean;
|
||||||
|
isWithTextColor?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiStoryViewPublicForward = {
|
export type ApiStoryViewPublicForward = {
|
||||||
type: 'forward';
|
type: 'forward';
|
||||||
peerId: string;
|
peerId: string;
|
||||||
|
|||||||
@ -1230,7 +1230,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{webPage && (
|
{webPage && (
|
||||||
<WebPage
|
<WebPage
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForLoading}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
canAutoPlay={canAutoPlayMedia}
|
canAutoPlay={canAutoPlayMedia}
|
||||||
|
|||||||
@ -22,6 +22,23 @@
|
|||||||
margin: -0.375rem;
|
margin: -0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--emoji-grid {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--sticker {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--stickers {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
&.in-preview {
|
&.in-preview {
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
background-color: var(--color-primary-tint);
|
background-color: var(--color-primary-tint);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo } from '../../../lib/teact/teact';
|
import React, { memo, useRef } from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage, ApiTypeStory } from '../../../api/types';
|
import type { ApiMessage, ApiTypeStory } from '../../../api/types';
|
||||||
@ -13,6 +13,7 @@ import renderText from '../../common/helpers/renderText';
|
|||||||
import { calculateMediaDimensions } from './helpers/mediaDimensions';
|
import { calculateMediaDimensions } from './helpers/mediaDimensions';
|
||||||
import { getWebpageButtonText } from './helpers/webpageType';
|
import { getWebpageButtonText } from './helpers/webpageType';
|
||||||
|
|
||||||
|
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
import useEnsureStory from '../../../hooks/useEnsureStory';
|
import useEnsureStory from '../../../hooks/useEnsureStory';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -22,6 +23,7 @@ import Audio from '../../common/Audio';
|
|||||||
import Document from '../../common/Document';
|
import Document from '../../common/Document';
|
||||||
import EmojiIconBackground from '../../common/embedded/EmojiIconBackground';
|
import EmojiIconBackground from '../../common/embedded/EmojiIconBackground';
|
||||||
import SafeLink from '../../common/SafeLink';
|
import SafeLink from '../../common/SafeLink';
|
||||||
|
import StickerView from '../../common/StickerView';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import BaseStory from './BaseStory';
|
import BaseStory from './BaseStory';
|
||||||
import Photo from './Photo';
|
import Photo from './Photo';
|
||||||
@ -31,10 +33,13 @@ import './WebPage.scss';
|
|||||||
|
|
||||||
const MAX_TEXT_LENGTH = 170; // symbols
|
const MAX_TEXT_LENGTH = 170; // symbols
|
||||||
const WEBPAGE_STORY_TYPE = 'telegram_story';
|
const WEBPAGE_STORY_TYPE = 'telegram_story';
|
||||||
|
const STICKER_SIZE = 80;
|
||||||
|
const EMOJI_SIZE = 40;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
noAvatars?: boolean;
|
noAvatars?: boolean;
|
||||||
canAutoLoad?: boolean;
|
canAutoLoad?: boolean;
|
||||||
canAutoPlay?: boolean;
|
canAutoPlay?: boolean;
|
||||||
@ -55,7 +60,8 @@ type OwnProps = {
|
|||||||
|
|
||||||
const WebPage: FC<OwnProps> = ({
|
const WebPage: FC<OwnProps> = ({
|
||||||
message,
|
message,
|
||||||
observeIntersection,
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
noAvatars,
|
noAvatars,
|
||||||
canAutoLoad,
|
canAutoLoad,
|
||||||
canAutoPlay,
|
canAutoPlay,
|
||||||
@ -76,6 +82,10 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
const { openTelegramLink } = getActions();
|
const { openTelegramLink } = getActions();
|
||||||
const webPage = getMessageWebPage(message);
|
const webPage = getMessageWebPage(message);
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const stickersRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -90,10 +100,13 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const { story: storyData } = webPage || {};
|
const { story: storyData, stickers } = webPage || {};
|
||||||
|
|
||||||
useEnsureStory(storyData?.peerId, storyData?.id, story);
|
useEnsureStory(storyData?.peerId, storyData?.id, story);
|
||||||
|
|
||||||
|
const hasCustomColor = stickers?.isWithTextColor || stickers?.documents?.[0]?.shouldUseTextColor;
|
||||||
|
const customColor = useDynamicColorListener(stickersRef, !hasCustomColor);
|
||||||
|
|
||||||
if (!webPage) {
|
if (!webPage) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -115,7 +128,7 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
const quickButtonLangKey = !inPreview && !isExpiredStory ? getWebpageButtonText(type) : undefined;
|
const quickButtonLangKey = !inPreview && !isExpiredStory ? getWebpageButtonText(type) : undefined;
|
||||||
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
|
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
|
||||||
const isArticle = Boolean(truncatedDescription || title || siteName);
|
const isArticle = Boolean(truncatedDescription || title || siteName);
|
||||||
let isSquarePhoto = false;
|
let isSquarePhoto = Boolean(stickers);
|
||||||
if (isArticle && webPage?.photo && !webPage.video) {
|
if (isArticle && webPage?.photo && !webPage.video) {
|
||||||
const { width, height } = calculateMediaDimensions(message, undefined, undefined, isMobile);
|
const { width, height } = calculateMediaDimensions(message, undefined, undefined, isMobile);
|
||||||
isSquarePhoto = width === height;
|
isSquarePhoto = width === height;
|
||||||
@ -149,18 +162,25 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={ref}
|
||||||
className={className}
|
className={className}
|
||||||
data-initial={(siteName || displayUrl)[0]}
|
data-initial={(siteName || displayUrl)[0]}
|
||||||
dir={lang.isRtl ? 'rtl' : 'auto'}
|
dir={lang.isRtl ? 'rtl' : 'auto'}
|
||||||
>
|
>
|
||||||
<div className={buildClassName('WebPage--content', isStory && 'is-story')}>
|
<div className={buildClassName('WebPage--content', isStory && 'is-story')}>
|
||||||
|
{backgroundEmojiId && (
|
||||||
|
<EmojiIconBackground
|
||||||
|
emojiDocumentId={backgroundEmojiId}
|
||||||
|
className="WebPage--background-icons"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isStory && (
|
{isStory && (
|
||||||
<BaseStory story={story} isProtected={isProtected} isConnected={isConnected} isPreview />
|
<BaseStory story={story} isProtected={isProtected} isConnected={isConnected} isPreview />
|
||||||
)}
|
)}
|
||||||
{photo && !video && (
|
{photo && !video && (
|
||||||
<Photo
|
<Photo
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
canAutoLoad={canAutoLoad}
|
canAutoLoad={canAutoLoad}
|
||||||
size={isSquarePhoto ? 'pictogram' : 'inline'}
|
size={isSquarePhoto ? 'pictogram' : 'inline'}
|
||||||
@ -175,12 +195,6 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
{isArticle && (
|
{isArticle && (
|
||||||
<div className="WebPage-text">
|
<div className="WebPage-text">
|
||||||
{backgroundEmojiId && (
|
|
||||||
<EmojiIconBackground
|
|
||||||
emojiDocumentId={backgroundEmojiId}
|
|
||||||
className="WebPage--background-icons"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SafeLink className="site-name" url={url} text={siteName || displayUrl} />
|
<SafeLink className="site-name" url={url} text={siteName || displayUrl} />
|
||||||
{!inPreview && title && (
|
{!inPreview && title && (
|
||||||
<p className="site-title">{renderText(title)}</p>
|
<p className="site-title">{renderText(title)}</p>
|
||||||
@ -193,7 +207,7 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
{!inPreview && video && (
|
{!inPreview && video && (
|
||||||
<Video
|
<Video
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersectionForLoading={observeIntersection!}
|
observeIntersectionForLoading={observeIntersectionForLoading!}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
canAutoLoad={canAutoLoad}
|
canAutoLoad={canAutoLoad}
|
||||||
canAutoPlay={canAutoPlay}
|
canAutoPlay={canAutoPlay}
|
||||||
@ -218,7 +232,7 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
{!inPreview && document && (
|
{!inPreview && document && (
|
||||||
<Document
|
<Document
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb}
|
autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb}
|
||||||
onMediaClick={handleMediaClick}
|
onMediaClick={handleMediaClick}
|
||||||
onCancelUpload={onCancelMediaTransfer}
|
onCancelUpload={onCancelMediaTransfer}
|
||||||
@ -226,14 +240,30 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
shouldWarnAboutSvg={shouldWarnAboutSvg}
|
shouldWarnAboutSvg={shouldWarnAboutSvg}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!inPreview && stickers && (
|
||||||
|
<div
|
||||||
|
ref={stickersRef}
|
||||||
|
className={buildClassName(
|
||||||
|
'media-inner', 'square-image', stickers.isEmoji && 'WebPage--emoji-grid', 'WebPage--stickers',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{stickers.documents.map((sticker) => (
|
||||||
|
<div key={sticker.id} className="WebPage--sticker">
|
||||||
|
<StickerView
|
||||||
|
containerRef={stickersRef}
|
||||||
|
sticker={sticker}
|
||||||
|
shouldLoop
|
||||||
|
size={stickers.isEmoji ? EMOJI_SIZE : STICKER_SIZE}
|
||||||
|
customColor={customColor}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{inPreview && displayUrl && !isArticle && (
|
{inPreview && displayUrl && !isArticle && (
|
||||||
<div className="WebPage-text">
|
<div className="WebPage-text">
|
||||||
{backgroundEmojiId && (
|
|
||||||
<EmojiIconBackground
|
|
||||||
emojiDocumentId={backgroundEmojiId}
|
|
||||||
className="WebPage--background-icons"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<p className="site-name">{displayUrl}</p>
|
<p className="site-name">{displayUrl}</p>
|
||||||
<p className="site-description">{lang('Chat.Empty.LinkPreview')}</p>
|
<p className="site-description">{lang('Chat.Empty.LinkPreview')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -29,6 +29,8 @@ export function getWebpageButtonText(type?: string) {
|
|||||||
case 'telegram_channel_boost':
|
case 'telegram_channel_boost':
|
||||||
case 'telegram_group_boost':
|
case 'telegram_group_boost':
|
||||||
return 'lng_view_button_boost';
|
return 'lng_view_button_boost';
|
||||||
|
case 'telegram_stickerset':
|
||||||
|
return 'lng_view_button_stickerset';
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from '../lib/teact/teact';
|
|||||||
|
|
||||||
import type { Scheduler } from '../util/schedulers';
|
import type { Scheduler } from '../util/schedulers';
|
||||||
|
|
||||||
|
import { type CallbackManager, createCallbackManager } from '../util/callbacks';
|
||||||
import {
|
import {
|
||||||
debounce, throttle, throttleWith,
|
debounce, throttle, throttleWith,
|
||||||
} from '../util/schedulers';
|
} from '../util/schedulers';
|
||||||
@ -16,7 +17,9 @@ export type ObserveFn = (target: HTMLElement, targetCallback?: TargetCallback) =
|
|||||||
|
|
||||||
interface IntersectionController {
|
interface IntersectionController {
|
||||||
observer: IntersectionObserver;
|
observer: IntersectionObserver;
|
||||||
callbacks: Map<HTMLElement, TargetCallback>;
|
addCallback: (element: HTMLElement, callback: TargetCallback) => void;
|
||||||
|
removeCallback: (element: HTMLElement, callback: TargetCallback) => void;
|
||||||
|
destroy: NoneToVoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
@ -78,14 +81,14 @@ export function useIntersectionObserver({
|
|||||||
return () => {
|
return () => {
|
||||||
if (controllerRef.current) {
|
if (controllerRef.current) {
|
||||||
controllerRef.current.observer.disconnect();
|
controllerRef.current.observer.disconnect();
|
||||||
controllerRef.current.callbacks.clear();
|
controllerRef.current.destroy();
|
||||||
controllerRef.current = undefined;
|
controllerRef.current = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [isDisabled]);
|
}, [isDisabled]);
|
||||||
|
|
||||||
function initController() {
|
function initController() {
|
||||||
const callbacks = new Map();
|
const callbacks = new Map<HTMLElement, CallbackManager<TargetCallback>>();
|
||||||
const entriesAccumulator = new Map<Element, IntersectionObserverEntry>();
|
const entriesAccumulator = new Map<Element, IntersectionObserverEntry>();
|
||||||
|
|
||||||
let observerCallback: typeof observerCallbackSync;
|
let observerCallback: typeof observerCallbackSync;
|
||||||
@ -108,10 +111,8 @@ export function useIntersectionObserver({
|
|||||||
const entries = Array.from(entriesAccumulator.values());
|
const entries = Array.from(entriesAccumulator.values());
|
||||||
|
|
||||||
entries.forEach((entry: IntersectionObserverEntry) => {
|
entries.forEach((entry: IntersectionObserverEntry) => {
|
||||||
const callback = callbacks.get(entry.target);
|
const callbackManager = callbacks.get(entry.target as HTMLElement);
|
||||||
if (callback) {
|
callbackManager?.runCallbacks(entry);
|
||||||
callback!(entry, entries);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (rootCallbackRef.current) {
|
if (rootCallbackRef.current) {
|
||||||
@ -121,6 +122,25 @@ export function useIntersectionObserver({
|
|||||||
entriesAccumulator.clear();
|
entriesAccumulator.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCallback(element: HTMLElement, callback: TargetCallback) {
|
||||||
|
if (!callbacks.get(element)) {
|
||||||
|
callbacks.set(element, createCallbackManager<TargetCallback>());
|
||||||
|
}
|
||||||
|
|
||||||
|
const callbackManager = callbacks.get(element)!;
|
||||||
|
callbackManager.addCallback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCallback(element: HTMLElement, callback: TargetCallback) {
|
||||||
|
const callbackManager = callbacks.get(element);
|
||||||
|
if (!callbackManager) return;
|
||||||
|
callbackManager.removeCallback(callback);
|
||||||
|
|
||||||
|
if (!callbackManager.hasCallbacks()) {
|
||||||
|
callbacks.delete(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
@ -140,7 +160,17 @@ export function useIntersectionObserver({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
controllerRef.current = { observer, callbacks };
|
function destroy() {
|
||||||
|
callbacks.clear();
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerRef.current = {
|
||||||
|
observer,
|
||||||
|
addCallback,
|
||||||
|
removeCallback,
|
||||||
|
destroy,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const observe = useLastCallback((target, targetCallback) => {
|
const observe = useLastCallback((target, targetCallback) => {
|
||||||
@ -152,12 +182,12 @@ export function useIntersectionObserver({
|
|||||||
controller.observer.observe(target);
|
controller.observer.observe(target);
|
||||||
|
|
||||||
if (targetCallback) {
|
if (targetCallback) {
|
||||||
controller.callbacks.set(target, targetCallback);
|
controller.addCallback(target, targetCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (targetCallback) {
|
if (targetCallback) {
|
||||||
controller.callbacks.delete(target);
|
controller.removeCallback(target, targetCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.observer.unobserve(target);
|
controller.observer.unobserve(target);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user