Media Viewer: Chat media loading (#4606)
This commit is contained in:
parent
7803e4dbe2
commit
bd2f99efaa
@ -16,7 +16,9 @@ import {
|
||||
selectChatMessage,
|
||||
selectChatMessages,
|
||||
selectChatScheduledMessages,
|
||||
selectCurrentMediaSearch, selectIsChatWithSelf,
|
||||
selectCurrentChatMediaSearch,
|
||||
selectCurrentSharedMediaSearch,
|
||||
selectIsChatWithSelf,
|
||||
selectListedIds,
|
||||
selectOutlyingListByMessageId,
|
||||
selectPerformanceSettingsValue,
|
||||
@ -71,6 +73,9 @@ type StateProps = {
|
||||
isHidden?: boolean;
|
||||
withAnimation?: boolean;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
withDynamicLoading?: boolean;
|
||||
isLoadingMoreMedia?: boolean;
|
||||
isSynced?: boolean;
|
||||
};
|
||||
|
||||
const ANIMATION_DURATION = 250;
|
||||
@ -91,6 +96,9 @@ const MediaViewer: FC<StateProps> = ({
|
||||
withAnimation,
|
||||
isHidden,
|
||||
shouldSkipHistoryAnimations,
|
||||
withDynamicLoading,
|
||||
isLoadingMoreMedia,
|
||||
isSynced,
|
||||
}) => {
|
||||
const {
|
||||
openMediaViewer,
|
||||
@ -98,6 +106,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
openForwardMenu,
|
||||
focusMessage,
|
||||
toggleChatInfo,
|
||||
searchChatMediaMessages,
|
||||
} = getActions();
|
||||
|
||||
const isOpen = Boolean(avatarOwner || mediaId);
|
||||
@ -133,17 +142,14 @@ const MediaViewer: FC<StateProps> = ({
|
||||
const isVisible = !isHidden && isOpen;
|
||||
|
||||
/* Navigation */
|
||||
const singleMediaId = webPagePhoto || webPageVideo || actionPhoto ? mediaId : undefined;
|
||||
const singleMediaId = webPagePhoto || webPageVideo || actionPhoto || isGif ? mediaId : undefined;
|
||||
|
||||
const mediaIds = useMemo(() => {
|
||||
if (singleMediaId) {
|
||||
return [singleMediaId];
|
||||
} else if (avatarOwner) {
|
||||
return avatarOwner.photos?.map((p, i) => i) || [];
|
||||
} else {
|
||||
return getChatMediaMessageIds(chatMessages || {}, collectionIds || [], isFromSharedMedia);
|
||||
}
|
||||
}, [singleMediaId, avatarOwner, chatMessages, collectionIds, isFromSharedMedia]);
|
||||
if (singleMediaId) return [singleMediaId];
|
||||
if (avatarOwner) return avatarOwner.photos?.map((p, i) => i) || [];
|
||||
if (withDynamicLoading) return collectionIds || [];
|
||||
return getChatMediaMessageIds(chatMessages || {}, collectionIds || [], isFromSharedMedia);
|
||||
}, [singleMediaId, avatarOwner, chatMessages, collectionIds, isFromSharedMedia, withDynamicLoading]);
|
||||
|
||||
const selectedMediaIndex = mediaId ? mediaIds.indexOf(mediaId) : -1;
|
||||
|
||||
@ -252,6 +258,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
mediaId: id,
|
||||
avatarOwnerId: avatarOwner?.id,
|
||||
origin: origin!,
|
||||
withDynamicLoading,
|
||||
}, {
|
||||
forceOnHeavyAnimation: true,
|
||||
});
|
||||
@ -269,6 +276,11 @@ const MediaViewer: FC<StateProps> = ({
|
||||
|
||||
const mediaIdsRef = useStateRef(mediaIds);
|
||||
|
||||
const loadMoreMediaIfNeeded = useLastCallback((activeMediaId?: number) => {
|
||||
if (!activeMediaId || !withDynamicLoading || isLoadingMoreMedia) return;
|
||||
searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: activeMediaId });
|
||||
});
|
||||
|
||||
const getMediaId = useLastCallback((fromId?: number, direction?: number): number | undefined => {
|
||||
if (fromId === undefined) return undefined;
|
||||
const mIds = mediaIdsRef.current;
|
||||
@ -276,6 +288,8 @@ const MediaViewer: FC<StateProps> = ({
|
||||
if ((direction === -1 && index > 0) || (direction === 1 && index < mIds.length - 1)) {
|
||||
return mIds[index + direction];
|
||||
}
|
||||
// Fallback
|
||||
if (isVisible) loadMoreMediaIfNeeded(fromId);
|
||||
return undefined;
|
||||
});
|
||||
|
||||
@ -359,6 +373,9 @@ const MediaViewer: FC<StateProps> = ({
|
||||
</div>
|
||||
<MediaViewerSlides
|
||||
mediaId={mediaId}
|
||||
loadMoreMediaIfNeeded={loadMoreMediaIfNeeded}
|
||||
isLoadingMoreMedia={isLoadingMoreMedia}
|
||||
isSynced={isSynced}
|
||||
getMediaId={getMediaId}
|
||||
chatId={chatId}
|
||||
isPhoto={isPhoto}
|
||||
@ -389,10 +406,11 @@ export default memo(withGlobal(
|
||||
avatarOwnerId,
|
||||
origin,
|
||||
isHidden,
|
||||
withDynamicLoading,
|
||||
} = mediaViewer;
|
||||
const withAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');
|
||||
|
||||
const { currentUserId } = global;
|
||||
const { currentUserId, isSynced } = global;
|
||||
let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId);
|
||||
|
||||
if (origin === MediaViewerOrigin.SearchResult) {
|
||||
@ -466,16 +484,24 @@ export default memo(withGlobal(
|
||||
} else {
|
||||
chatMessages = selectChatMessages(global, chatId);
|
||||
}
|
||||
|
||||
let isLoadingMoreMedia = false;
|
||||
const isOriginInline = origin === MediaViewerOrigin.Inline;
|
||||
const isOriginAlbum = origin === MediaViewerOrigin.Album;
|
||||
let collectionIds: number[] | undefined;
|
||||
|
||||
if (origin === MediaViewerOrigin.Inline
|
||||
|| origin === MediaViewerOrigin.Album) {
|
||||
collectionIds = selectOutlyingListByMessageId(global, chatId, threadId, message.id)
|
||||
|| selectListedIds(global, chatId, threadId);
|
||||
if (withDynamicLoading && (isOriginInline || isOriginAlbum)) {
|
||||
const currentSearch = selectCurrentChatMediaSearch(global);
|
||||
isLoadingMoreMedia = Boolean(currentSearch?.isLoading);
|
||||
const { foundIds } = (currentSearch?.currentSegment) || {};
|
||||
collectionIds = foundIds;
|
||||
} else if (origin === MediaViewerOrigin.SharedMedia) {
|
||||
const currentSearch = selectCurrentMediaSearch(global);
|
||||
const currentSearch = selectCurrentSharedMediaSearch(global);
|
||||
const { foundIds } = (currentSearch && currentSearch.resultsByType && currentSearch.resultsByType.media) || {};
|
||||
collectionIds = foundIds;
|
||||
} else if (isOriginInline || isOriginAlbum) {
|
||||
const outlyingList = selectOutlyingListByMessageId(global, chatId, threadId, message.id);
|
||||
collectionIds = outlyingList || selectListedIds(global, chatId, threadId);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -491,6 +517,9 @@ export default memo(withGlobal(
|
||||
withAnimation,
|
||||
isHidden,
|
||||
shouldSkipHistoryAnimations,
|
||||
withDynamicLoading,
|
||||
isLoadingMoreMedia,
|
||||
isSynced,
|
||||
};
|
||||
},
|
||||
)(MediaViewer));
|
||||
|
||||
@ -39,6 +39,9 @@ const { easeOutCubic, easeOutQuart } = timingFunctions;
|
||||
|
||||
type OwnProps = {
|
||||
mediaId?: number;
|
||||
loadMoreMediaIfNeeded: (activeMediaId?: number) => void;
|
||||
isLoadingMoreMedia?: boolean;
|
||||
isSynced?: boolean;
|
||||
getMediaId: (fromId?: number, direction?: number) => number | undefined;
|
||||
isVideo?: boolean;
|
||||
isGif?: boolean;
|
||||
@ -84,6 +87,7 @@ enum SwipeDirection {
|
||||
|
||||
const MediaViewerSlides: FC<OwnProps> = ({
|
||||
mediaId,
|
||||
loadMoreMediaIfNeeded,
|
||||
getMediaId,
|
||||
selectMedia,
|
||||
isVideo,
|
||||
@ -91,6 +95,8 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
withAnimation,
|
||||
isHidden,
|
||||
isLoadingMoreMedia,
|
||||
isSynced,
|
||||
...rest
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -156,6 +162,11 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
}
|
||||
}, [mediaId, setActiveMediaId, transformRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSynced || isLoadingMoreMedia) return;
|
||||
loadMoreMediaIfNeeded(activeMediaId);
|
||||
}, [activeMediaId, loadMoreMediaIfNeeded, isSynced, isLoadingMoreMedia]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const { x, y, scale } = getTransform();
|
||||
lockControls(scale !== 1);
|
||||
@ -643,6 +654,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
[
|
||||
onClose,
|
||||
setTransform,
|
||||
loadMoreMediaIfNeeded,
|
||||
getMediaId,
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
|
||||
@ -581,6 +581,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
handleMediaClick,
|
||||
handleAudioPlay,
|
||||
handleAlbumMediaClick,
|
||||
handlePhotoMediaClick,
|
||||
handleVideoMediaClick,
|
||||
handleMetaClick,
|
||||
handleTranslationClick,
|
||||
handleOpenThread,
|
||||
@ -1105,7 +1107,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
asForwarded={asForwarded}
|
||||
theme={theme}
|
||||
forcedWidth={contentWidth}
|
||||
onClick={handleMediaClick}
|
||||
onClick={handlePhotoMediaClick}
|
||||
onCancelUpload={handleCancelUpload}
|
||||
/>
|
||||
)}
|
||||
@ -1131,7 +1133,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
isDownloading={isDownloading}
|
||||
isProtected={isProtected}
|
||||
asForwarded={asForwarded}
|
||||
onClick={handleMediaClick}
|
||||
onClick={handleVideoMediaClick}
|
||||
onCancelUpload={handleCancelUpload}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -51,7 +51,7 @@ export type OwnProps = {
|
||||
asForwarded?: boolean;
|
||||
isDownloading?: boolean;
|
||||
isProtected?: boolean;
|
||||
onClick?: (id: number) => void;
|
||||
onClick?: (id: number, isGif?: boolean) => void;
|
||||
onCancelUpload?: (message: ApiMessage) => void;
|
||||
};
|
||||
|
||||
@ -191,7 +191,7 @@ const Video: FC<OwnProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
onClick?.(message.id);
|
||||
onClick?.(message.id, video?.isGif);
|
||||
});
|
||||
|
||||
const className = buildClassName(
|
||||
|
||||
@ -35,7 +35,7 @@ export default function useInnerHandlers(
|
||||
const {
|
||||
openChat, showNotification, focusMessage, openMediaViewer, openAudioPlayer,
|
||||
markMessagesRead, cancelUploadMedia, sendPollVote, openForwardMenu,
|
||||
openChatLanguageModal, openThread, openStoryViewer,
|
||||
openChatLanguageModal, openThread, openStoryViewer, searchChatMediaMessages,
|
||||
} = getActions();
|
||||
|
||||
const {
|
||||
@ -102,17 +102,39 @@ export default function useInnerHandlers(
|
||||
origin: isScheduled ? MediaViewerOrigin.ScheduledInline : MediaViewerOrigin.Inline,
|
||||
});
|
||||
});
|
||||
const openMediaViewerWithPhotoOrVideo = useLastCallback((withDynamicLoading: boolean): void => {
|
||||
if (withDynamicLoading) {
|
||||
searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: messageId });
|
||||
}
|
||||
openMediaViewer({
|
||||
chatId,
|
||||
threadId,
|
||||
mediaId: messageId,
|
||||
origin: isScheduled ? MediaViewerOrigin.ScheduledInline : MediaViewerOrigin.Inline,
|
||||
withDynamicLoading,
|
||||
});
|
||||
});
|
||||
const handlePhotoMediaClick = useLastCallback((): void => {
|
||||
const withDynamicLoading = !isScheduled;
|
||||
openMediaViewerWithPhotoOrVideo(withDynamicLoading);
|
||||
});
|
||||
const handleVideoMediaClick = useLastCallback((id: number, isGif?: boolean): void => {
|
||||
const withDynamicLoading = !isGif && !isScheduled;
|
||||
openMediaViewerWithPhotoOrVideo(withDynamicLoading);
|
||||
});
|
||||
|
||||
const handleAudioPlay = useLastCallback((): void => {
|
||||
openAudioPlayer({ chatId, messageId });
|
||||
});
|
||||
|
||||
const handleAlbumMediaClick = useLastCallback((albumMessageId: number): void => {
|
||||
searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: messageId });
|
||||
openMediaViewer({
|
||||
chatId,
|
||||
threadId,
|
||||
mediaId: albumMessageId,
|
||||
origin: isScheduled ? MediaViewerOrigin.ScheduledAlbum : MediaViewerOrigin.Album,
|
||||
withDynamicLoading: true,
|
||||
});
|
||||
});
|
||||
|
||||
@ -213,6 +235,8 @@ export default function useInnerHandlers(
|
||||
handleMediaClick,
|
||||
handleAudioPlay,
|
||||
handleAlbumMediaClick,
|
||||
handlePhotoMediaClick,
|
||||
handleVideoMediaClick,
|
||||
handleMetaClick: selectWithGroupedId,
|
||||
handleTranslationClick,
|
||||
handleOpenThread,
|
||||
|
||||
@ -33,7 +33,7 @@ import {
|
||||
selectChat,
|
||||
selectChatFullInfo,
|
||||
selectChatMessages,
|
||||
selectCurrentMediaSearch,
|
||||
selectCurrentSharedMediaSearch,
|
||||
selectIsCurrentUserPremium,
|
||||
selectIsRightColumnShown,
|
||||
selectPeerFullInfo,
|
||||
@ -186,11 +186,11 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
forceScrollProfileTab,
|
||||
}) => {
|
||||
const {
|
||||
setLocalMediaSearchType,
|
||||
setSharedMediaSearchType,
|
||||
loadMoreMembers,
|
||||
loadCommonChats,
|
||||
openChat,
|
||||
searchMediaMessagesLocal,
|
||||
searchSharedMediaMessages,
|
||||
openMediaViewer,
|
||||
openAudioPlayer,
|
||||
focusMessage,
|
||||
@ -275,7 +275,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
|
||||
loadMoreMembers,
|
||||
loadCommonChats,
|
||||
searchMediaMessagesLocal,
|
||||
searchSharedMediaMessages,
|
||||
handleLoadPeerStories,
|
||||
handleLoadStoriesArchive,
|
||||
tabType,
|
||||
@ -329,8 +329,8 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
|
||||
// Update search type when switching tabs or forum topics
|
||||
useEffect(() => {
|
||||
setLocalMediaSearchType({ mediaType: tabType as SharedMediaType });
|
||||
}, [setLocalMediaSearchType, tabType, threadId]);
|
||||
setSharedMediaSearchType({ mediaType: tabType as SharedMediaType });
|
||||
}, [setSharedMediaSearchType, tabType, threadId]);
|
||||
|
||||
useEffect(() => {
|
||||
loadProfilePhotos({ profileId });
|
||||
@ -683,7 +683,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const chat = selectChat(global, chatId);
|
||||
const chatFullInfo = selectChatFullInfo(global, chatId);
|
||||
const messagesById = selectChatMessages(global, chatId);
|
||||
const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {};
|
||||
const { currentType: mediaSearchType, resultsByType } = selectCurrentSharedMediaSearch(global) || {};
|
||||
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
|
||||
|
||||
const isTopicInfo = Boolean(chat?.isForum && threadId && threadId !== MAIN_THREAD_ID);
|
||||
|
||||
@ -72,6 +72,7 @@ export const TOPIC_HEIGHT_PX = 65;
|
||||
export const CHAT_LIST_SLICE = isBigScreen ? 30 : 25;
|
||||
export const CHAT_LIST_LOAD_SLICE = 100;
|
||||
export const SHARED_MEDIA_SLICE = 42;
|
||||
export const CHAT_MEDIA_SLICE = 42;
|
||||
export const MESSAGE_SEARCH_SLICE = 42;
|
||||
export const GLOBAL_SEARCH_SLICE = 20;
|
||||
export const GLOBAL_TOPIC_SEARCH_SLICE = 5;
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
import type { ApiChat } from '../../../api/types';
|
||||
import type { SharedMediaType, ThreadId } from '../../../types';
|
||||
import type {
|
||||
ChatMediaSearchParams, ChatMediaSearchSegment, LoadingState, SharedMediaType, ThreadId,
|
||||
} from '../../../types';
|
||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
||||
import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
|
||||
import {
|
||||
CHAT_MEDIA_SLICE, MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE,
|
||||
} from '../../../config';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { buildCollectionByKey, isInsideSortedArrayRange } from '../../../util/iteratees';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { getIsSavedDialog, isSameReaction } from '../../helpers';
|
||||
import { getChatMediaMessageIds, getIsSavedDialog, isSameReaction } from '../../helpers';
|
||||
import {
|
||||
addActionHandler, getGlobal, setGlobal,
|
||||
} from '../../index';
|
||||
@ -14,16 +19,23 @@ import {
|
||||
addChatMessagesById,
|
||||
addChats,
|
||||
addUsers,
|
||||
updateLocalMediaSearchResults,
|
||||
initializeChatMediaSearchResults,
|
||||
mergeWithChatMediaSearchSegment,
|
||||
setChatMediaSearchLoading,
|
||||
updateChatMediaSearchResults,
|
||||
updateLocalTextSearchResults,
|
||||
updateSharedMediaSearchResults,
|
||||
} from '../../reducers';
|
||||
import {
|
||||
selectChat,
|
||||
selectCurrentMediaSearch,
|
||||
selectCurrentChatMediaSearch,
|
||||
selectCurrentMessageList,
|
||||
selectCurrentSharedMediaSearch,
|
||||
selectCurrentTextSearch,
|
||||
} from '../../selectors';
|
||||
|
||||
const MEDIA_PRELOAD_OFFSET = 9;
|
||||
|
||||
addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||
@ -86,7 +98,7 @@ addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Pr
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('searchMediaMessagesLocal', (global, actions, payload): ActionReturnType => {
|
||||
addActionHandler('searchSharedMediaMessages', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||
if (!chatId || !threadId) {
|
||||
@ -97,7 +109,7 @@ addActionHandler('searchMediaMessagesLocal', (global, actions, payload): ActionR
|
||||
const realChatId = isSavedDialog ? String(threadId) : chatId;
|
||||
|
||||
const chat = selectChat(global, realChatId);
|
||||
const currentSearch = selectCurrentMediaSearch(global, tabId);
|
||||
const currentSearch = selectCurrentSharedMediaSearch(global, tabId);
|
||||
|
||||
if (!chat || !currentSearch) {
|
||||
return;
|
||||
@ -113,6 +125,42 @@ addActionHandler('searchMediaMessagesLocal', (global, actions, payload): ActionR
|
||||
|
||||
void searchSharedMedia(global, chat, threadId, type, offsetId, undefined, isSavedDialog, tabId);
|
||||
});
|
||||
addActionHandler('searchChatMediaMessages', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, threadId, currentMediaMessageId, limit, direction, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
if (!chatId || !threadId || !currentMediaMessageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||
const realChatId = isSavedDialog ? String(threadId) : chatId;
|
||||
|
||||
const chat = selectChat(global, realChatId);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
let currentSearch = selectCurrentChatMediaSearch(global, tabId);
|
||||
|
||||
if (!currentSearch) {
|
||||
global = initializeChatMediaSearchResults(global, chatId, threadId, tabId);
|
||||
setGlobal(global);
|
||||
currentSearch = selectCurrentChatMediaSearch(global, tabId);
|
||||
if (!currentSearch) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void searchChatMedia(global,
|
||||
chat,
|
||||
threadId,
|
||||
currentMediaMessageId,
|
||||
currentSearch,
|
||||
direction,
|
||||
isSavedDialog,
|
||||
limit,
|
||||
tabId);
|
||||
});
|
||||
|
||||
addActionHandler('searchMessagesByDate', async (global, actions, payload): Promise<void> => {
|
||||
const { timestamp, tabId = getCurrentTabId() } = payload;
|
||||
@ -177,7 +225,7 @@ async function searchSharedMedia<T extends GlobalState>(
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
const currentSearch = selectCurrentMediaSearch(global, tabId);
|
||||
const currentSearch = selectCurrentSharedMediaSearch(global, tabId);
|
||||
if (!currentSearch) {
|
||||
return;
|
||||
}
|
||||
@ -185,7 +233,7 @@ async function searchSharedMedia<T extends GlobalState>(
|
||||
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||
global = addChatMessagesById(global, resultChatId, byId);
|
||||
global = updateLocalMediaSearchResults(
|
||||
global = updateSharedMediaSearchResults(
|
||||
global, resultChatId, threadId, type, newFoundIds, totalCount, nextOffsetId, tabId,
|
||||
);
|
||||
setGlobal(global);
|
||||
@ -194,3 +242,172 @@ async function searchSharedMedia<T extends GlobalState>(
|
||||
void searchSharedMedia(global, chat, threadId, type, nextOffsetId, true, isSavedDialog, tabId);
|
||||
}
|
||||
}
|
||||
|
||||
function selectCurrentChatMediaSearchSegment(
|
||||
params: ChatMediaSearchParams,
|
||||
currentMediaMessageId: number,
|
||||
): ChatMediaSearchSegment | undefined {
|
||||
if (isInsideSortedArrayRange(currentMediaMessageId, params.currentSegment.foundIds)) {
|
||||
return params.currentSegment;
|
||||
}
|
||||
const index = params.segments.findIndex(
|
||||
(segment) => isInsideSortedArrayRange(currentMediaMessageId, segment.foundIds),
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
if (params.currentSegment && params.currentSegment.foundIds.length) {
|
||||
params.segments.push(params.currentSegment);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const result = params.segments.splice(index, 1)[0];
|
||||
params.segments.push(params.currentSegment);
|
||||
return result;
|
||||
}
|
||||
|
||||
function calcChatMediaSearchAddOffset(
|
||||
direction: LoadMoreDirection,
|
||||
limit: number,
|
||||
): number {
|
||||
if (direction === LoadMoreDirection.Backwards) return 0;
|
||||
if (direction === LoadMoreDirection.Forwards) return -(limit + 1);
|
||||
return -(Math.round(limit / 2) + 1);
|
||||
}
|
||||
|
||||
function calcChatMediaSearchOffsetId(
|
||||
direction: LoadMoreDirection,
|
||||
currentMessageId: number,
|
||||
segment?: ChatMediaSearchSegment,
|
||||
) : number {
|
||||
if (!segment) return currentMessageId;
|
||||
if (direction === LoadMoreDirection.Backwards) return segment.foundIds[0];
|
||||
if (direction === LoadMoreDirection.Forwards) return segment.foundIds[segment.foundIds.length - 1];
|
||||
return currentMessageId;
|
||||
}
|
||||
|
||||
function calcLoadMoreDirection(currentMessageId: number, currentSegment?: ChatMediaSearchSegment) {
|
||||
if (!currentSegment) return LoadMoreDirection.Around;
|
||||
const currentSegmentFoundIdsCount = currentSegment.foundIds.length;
|
||||
|
||||
const idIndexInSegment = currentSegment.foundIds.indexOf(currentMessageId);
|
||||
if (idIndexInSegment === -1) return LoadMoreDirection.Around;
|
||||
|
||||
if (currentSegment.loadingState.areAllItemsLoadedBackwards
|
||||
&& currentSegment.loadingState.areAllItemsLoadedForwards) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const halfMediaCount = Math.floor(currentSegmentFoundIdsCount / 2);
|
||||
|
||||
const preloadOffset = MEDIA_PRELOAD_OFFSET > halfMediaCount ? 0 : MEDIA_PRELOAD_OFFSET;
|
||||
const lastMediaIndex = currentSegmentFoundIdsCount - 1;
|
||||
|
||||
if (idIndexInSegment <= preloadOffset) {
|
||||
if (currentSegment.loadingState.areAllItemsLoadedBackwards) return undefined;
|
||||
return LoadMoreDirection.Backwards;
|
||||
}
|
||||
if (idIndexInSegment >= lastMediaIndex - preloadOffset) {
|
||||
if (currentSegment.loadingState.areAllItemsLoadedForwards) return undefined;
|
||||
return LoadMoreDirection.Forwards;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function calcLoadingState(
|
||||
direction : LoadMoreDirection,
|
||||
limit : number, newFoundIdsCount : number,
|
||||
currentSegment?: ChatMediaSearchSegment,
|
||||
) : LoadingState {
|
||||
let areAllItemsLoadedForwards = Boolean(currentSegment?.loadingState.areAllItemsLoadedForwards);
|
||||
let areAllItemsLoadedBackwards = Boolean(currentSegment?.loadingState.areAllItemsLoadedBackwards);
|
||||
|
||||
if (newFoundIdsCount < limit) {
|
||||
if (direction === LoadMoreDirection.Forwards) {
|
||||
areAllItemsLoadedForwards = true;
|
||||
} else if (direction === LoadMoreDirection.Backwards) {
|
||||
areAllItemsLoadedBackwards = true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
areAllItemsLoadedForwards,
|
||||
areAllItemsLoadedBackwards,
|
||||
};
|
||||
}
|
||||
|
||||
async function searchChatMedia<T extends GlobalState>(
|
||||
global: T,
|
||||
chat: ApiChat,
|
||||
threadId: ThreadId,
|
||||
currentMediaMessageId: number,
|
||||
chatMediaSearchParams: ChatMediaSearchParams,
|
||||
direction?: LoadMoreDirection,
|
||||
isSavedDialog?: boolean,
|
||||
limit = CHAT_MEDIA_SLICE,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
const { isSynced } = global;
|
||||
if (!isSynced || chatMediaSearchParams.isLoading) {
|
||||
return;
|
||||
}
|
||||
let currentSegment = selectCurrentChatMediaSearchSegment(chatMediaSearchParams, currentMediaMessageId);
|
||||
|
||||
if (direction === undefined) {
|
||||
direction = calcLoadMoreDirection(currentMediaMessageId, currentSegment);
|
||||
}
|
||||
|
||||
if (direction === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetId = calcChatMediaSearchOffsetId(direction, currentMediaMessageId, currentSegment);
|
||||
const addOffset = calcChatMediaSearchAddOffset(direction, limit);
|
||||
|
||||
const resultChatId = isSavedDialog ? global.currentUserId! : chat.id;
|
||||
|
||||
global = setChatMediaSearchLoading(global, resultChatId, threadId, true, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const result = await callApi('searchMessagesLocal', {
|
||||
chat,
|
||||
type: 'media',
|
||||
limit,
|
||||
threadId,
|
||||
offsetId,
|
||||
isSavedDialog,
|
||||
addOffset,
|
||||
});
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
if (!result) {
|
||||
global = setChatMediaSearchLoading(global, resultChatId, threadId, false, tabId);
|
||||
setGlobal(global);
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
chats, users, messages,
|
||||
} = result;
|
||||
|
||||
const byId = buildCollectionByKey(messages, 'id');
|
||||
const newFoundIds = Object.keys(byId).map(Number);
|
||||
|
||||
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||
global = addChatMessagesById(global, resultChatId, byId);
|
||||
|
||||
const loadingState = calcLoadingState(direction, limit, newFoundIds.length, currentSegment);
|
||||
|
||||
const filteredIds = getChatMediaMessageIds(byId, newFoundIds, false);
|
||||
currentSegment = mergeWithChatMediaSearchSegment(
|
||||
filteredIds,
|
||||
loadingState,
|
||||
currentSegment,
|
||||
);
|
||||
|
||||
global = updateChatMediaSearchResults(
|
||||
global, resultChatId, threadId, currentSegment, chatMediaSearchParams, tabId,
|
||||
);
|
||||
global = setChatMediaSearchLoading(global, resultChatId, threadId, false, tabId);
|
||||
setGlobal(global);
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ import {
|
||||
replaceThreadParam,
|
||||
updateChat,
|
||||
updateChatLastMessageId,
|
||||
updateChatMediaLoadingState,
|
||||
updateChatMessage,
|
||||
updateListedIds,
|
||||
updateMessageTranslations,
|
||||
@ -109,6 +110,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
global = updateChatLastMessage(global, chatId, newMessage);
|
||||
}
|
||||
|
||||
const threadId = selectThreadIdFromMessage(global, newMessage);
|
||||
global = updateChatMediaLoadingState(global, newMessage, chatId, threadId, tabId);
|
||||
|
||||
if (selectIsMessageInCurrentMessageList(global, chatId, message as ApiMessage, tabId)) {
|
||||
if (isLocal && message.isOutgoing && !(message.content?.action) && !storyReplyInfo?.storyId
|
||||
&& !message.content?.storyData) {
|
||||
|
||||
@ -6,9 +6,9 @@ import { buildChatThreadKey, isSameReaction } from '../../helpers';
|
||||
import { addActionHandler } from '../../index';
|
||||
import {
|
||||
replaceLocalTextSearchResults,
|
||||
updateLocalMediaSearchType,
|
||||
updateLocalTextSearch,
|
||||
updateLocalTextSearchTag,
|
||||
updateSharedMediaSearchType,
|
||||
} from '../../reducers';
|
||||
import { selectCurrentMessageList, selectTabState } from '../../selectors';
|
||||
|
||||
@ -67,14 +67,14 @@ addActionHandler('setLocalTextSearchTag', (global, actions, payload): ActionRetu
|
||||
return global;
|
||||
});
|
||||
|
||||
addActionHandler('setLocalMediaSearchType', (global, actions, payload): ActionReturnType => {
|
||||
addActionHandler('setSharedMediaSearchType', (global, actions, payload): ActionReturnType => {
|
||||
const { mediaType, tabId = getCurrentTabId() } = payload;
|
||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||
if (!chatId || !threadId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return updateLocalMediaSearchType(global, chatId, threadId, mediaType, tabId);
|
||||
return updateSharedMediaSearchType(global, chatId, threadId, mediaType, tabId);
|
||||
});
|
||||
|
||||
export function closeLocalTextSearch<T extends GlobalState>(
|
||||
|
||||
@ -9,7 +9,7 @@ import { selectTabState } from '../../selectors';
|
||||
addActionHandler('openMediaViewer', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, threadId, mediaId, avatarOwnerId, profilePhotoIndex, origin, volume, playbackRate, isMuted,
|
||||
tabId = getCurrentTabId(),
|
||||
withDynamicLoading, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
@ -32,6 +32,7 @@ addActionHandler('openMediaViewer', (global, actions, payload): ActionReturnType
|
||||
|| DEFAULT_PLAYBACK_RATE
|
||||
),
|
||||
isMuted: isMuted || tabState.mediaViewer.isMuted,
|
||||
withDynamicLoading,
|
||||
},
|
||||
forwardMessages: {},
|
||||
}, tabId);
|
||||
|
||||
@ -545,3 +545,10 @@ export function canReplaceMessageMedia(message: ApiMessage, attachment: ApiAttac
|
||||
|| (isFile && (fileType === 'audio' || fileType === 'file'))
|
||||
);
|
||||
}
|
||||
|
||||
export function isMediaLoadableInViewer(newMessage: ApiMessage) {
|
||||
if (!newMessage.content) return false;
|
||||
if (newMessage.content.photo) return true;
|
||||
if (newMessage.content.video && !newMessage.content.video.isRound && !newMessage.content.video.isGif) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import { parseLocationHash } from '../util/routing';
|
||||
import { clearStoredSession } from '../util/sessions';
|
||||
import { updatePeerColors } from '../util/theme';
|
||||
import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment';
|
||||
import { initializeChatMediaSearchResults } from './reducers/localSearch';
|
||||
import { updateTabState } from './reducers/tabs';
|
||||
import { initCache, loadCache } from './cache';
|
||||
import {
|
||||
@ -79,6 +80,7 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => {
|
||||
global = replaceThreadParam(global, chatId, threadId, 'lastViewportIds', undefined);
|
||||
return;
|
||||
}
|
||||
global = initializeChatMediaSearchResults(global, chatId, threadId, tabId);
|
||||
global = replaceTabThreadParam(
|
||||
global,
|
||||
chatId,
|
||||
|
||||
@ -313,7 +313,11 @@ export const INITIAL_TAB_STATE: TabState = {
|
||||
byChatThreadKey: {},
|
||||
},
|
||||
|
||||
localMediaSearch: {
|
||||
sharedMediaSearch: {
|
||||
byChatThreadKey: {},
|
||||
},
|
||||
|
||||
chatMediaSearch: {
|
||||
byChatThreadKey: {},
|
||||
},
|
||||
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import type { ApiMessageSearchType, ApiReaction } from '../../api/types';
|
||||
import type { SharedMediaType, ThreadId } from '../../types';
|
||||
import type { ApiMessage, ApiMessageSearchType, ApiReaction } from '../../api/types';
|
||||
import type {
|
||||
ChatMediaSearchParams, ChatMediaSearchSegment, LoadingState,
|
||||
SharedMediaType, ThreadId,
|
||||
} from '../../types';
|
||||
import type { GlobalState, TabArgs } from '../types';
|
||||
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { areSortedArraysEqual, unique } from '../../util/iteratees';
|
||||
import { buildChatThreadKey } from '../helpers';
|
||||
import { areSortedArraysEqual, areSortedArraysIntersecting, unique } from '../../util/iteratees';
|
||||
import { buildChatThreadKey, isMediaLoadableInViewer } from '../helpers';
|
||||
import { selectTabState } from '../selectors';
|
||||
import { selectChatMediaSearch } from '../selectors/localSearch';
|
||||
import { updateTabState } from './tabs';
|
||||
|
||||
interface TextSearchParams {
|
||||
@ -18,7 +22,7 @@ interface TextSearchParams {
|
||||
};
|
||||
}
|
||||
|
||||
interface MediaSearchParams {
|
||||
interface SharedMediaSearchParams {
|
||||
currentType?: SharedMediaType;
|
||||
resultsByType?: Partial<Record<SharedMediaType, {
|
||||
totalCount?: number;
|
||||
@ -110,32 +114,32 @@ export function updateLocalTextSearchResults<T extends GlobalState>(
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
const { results } = selectTabState(global, tabId).localTextSearch.byChatThreadKey[chatThreadKey] || {};
|
||||
const prevFoundIds = (results?.foundIds) || [];
|
||||
const foundIds = orderFoundIds(unique(Array.prototype.concat(prevFoundIds, newFoundIds)));
|
||||
const foundIds = orderFoundIdsByDescending(unique(Array.prototype.concat(prevFoundIds, newFoundIds)));
|
||||
const foundOrPrevFoundIds = areSortedArraysEqual(prevFoundIds, foundIds) ? prevFoundIds : foundIds;
|
||||
|
||||
return replaceLocalTextSearchResults(global, chatId, threadId, foundOrPrevFoundIds, totalCount, nextOffsetId, tabId);
|
||||
}
|
||||
|
||||
function replaceLocalMediaSearch<T extends GlobalState>(
|
||||
function replaceSharedMediaSearch<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
searchParams: MediaSearchParams,
|
||||
searchParams: SharedMediaSearchParams,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return updateTabState(global, {
|
||||
localMediaSearch: {
|
||||
sharedMediaSearch: {
|
||||
byChatThreadKey: {
|
||||
...selectTabState(global, tabId).localMediaSearch.byChatThreadKey,
|
||||
...selectTabState(global, tabId).sharedMediaSearch.byChatThreadKey,
|
||||
[chatThreadKey]: searchParams,
|
||||
},
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
export function updateLocalMediaSearchType<T extends GlobalState>(
|
||||
export function updateSharedMediaSearchType<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
@ -144,13 +148,13 @@ export function updateLocalMediaSearchType<T extends GlobalState>(
|
||||
): T {
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return replaceLocalMediaSearch(global, chatId, threadId, {
|
||||
...selectTabState(global, tabId).localMediaSearch.byChatThreadKey[chatThreadKey],
|
||||
return replaceSharedMediaSearch(global, chatId, threadId, {
|
||||
...selectTabState(global, tabId).sharedMediaSearch.byChatThreadKey[chatThreadKey],
|
||||
currentType,
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
export function replaceLocalMediaSearchResults<T extends GlobalState>(
|
||||
export function replaceSharedMediaSearchResults<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
@ -162,10 +166,10 @@ export function replaceLocalMediaSearchResults<T extends GlobalState>(
|
||||
): T {
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return replaceLocalMediaSearch(global, chatId, threadId, {
|
||||
...selectTabState(global, tabId).localMediaSearch.byChatThreadKey[chatThreadKey],
|
||||
return replaceSharedMediaSearch(global, chatId, threadId, {
|
||||
...selectTabState(global, tabId).sharedMediaSearch.byChatThreadKey[chatThreadKey],
|
||||
resultsByType: {
|
||||
...(selectTabState(global, tabId).localMediaSearch.byChatThreadKey[chatThreadKey] || {}).resultsByType,
|
||||
...(selectTabState(global, tabId).sharedMediaSearch.byChatThreadKey[chatThreadKey] || {}).resultsByType,
|
||||
[type]: {
|
||||
foundIds,
|
||||
totalCount,
|
||||
@ -175,7 +179,7 @@ export function replaceLocalMediaSearchResults<T extends GlobalState>(
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
export function updateLocalMediaSearchResults<T extends GlobalState>(
|
||||
export function updateSharedMediaSearchResults<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
@ -187,12 +191,12 @@ export function updateLocalMediaSearchResults<T extends GlobalState>(
|
||||
): T {
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
const { resultsByType } = selectTabState(global, tabId).localMediaSearch.byChatThreadKey[chatThreadKey] || {};
|
||||
const { resultsByType } = selectTabState(global, tabId).sharedMediaSearch.byChatThreadKey[chatThreadKey] || {};
|
||||
const prevFoundIds = resultsByType?.[type] ? resultsByType[type]!.foundIds : [];
|
||||
const foundIds = orderFoundIds(unique(Array.prototype.concat(prevFoundIds, newFoundIds)));
|
||||
const foundIds = orderFoundIdsByDescending(unique(Array.prototype.concat(prevFoundIds, newFoundIds)));
|
||||
const foundOrPrevFoundIds = areSortedArraysEqual(prevFoundIds, foundIds) ? prevFoundIds : foundIds;
|
||||
|
||||
return replaceLocalMediaSearchResults(
|
||||
return replaceSharedMediaSearchResults(
|
||||
global,
|
||||
chatId,
|
||||
threadId,
|
||||
@ -204,6 +208,229 @@ export function updateLocalMediaSearchResults<T extends GlobalState>(
|
||||
);
|
||||
}
|
||||
|
||||
function orderFoundIds(listedIds: number[]) {
|
||||
function orderFoundIdsByDescending(listedIds: number[]) {
|
||||
return listedIds.sort((a, b) => b - a);
|
||||
}
|
||||
|
||||
function orderFoundIdsByAscending(array: number[]) {
|
||||
return array.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
export function mergeWithChatMediaSearchSegment(
|
||||
foundIds: number[],
|
||||
loadingState: LoadingState,
|
||||
segment?: ChatMediaSearchSegment,
|
||||
)
|
||||
: ChatMediaSearchSegment {
|
||||
if (!segment) {
|
||||
return {
|
||||
foundIds,
|
||||
loadingState,
|
||||
};
|
||||
}
|
||||
const mergedFoundIds = orderFoundIdsByAscending(unique(Array.prototype.concat(segment.foundIds, foundIds)));
|
||||
if (!areSortedArraysEqual(segment.foundIds, foundIds)) {
|
||||
segment.foundIds = mergedFoundIds;
|
||||
}
|
||||
const mergedLoadingState : LoadingState = {
|
||||
areAllItemsLoadedForwards: loadingState.areAllItemsLoadedForwards
|
||||
|| segment.loadingState.areAllItemsLoadedForwards,
|
||||
areAllItemsLoadedBackwards: loadingState.areAllItemsLoadedBackwards
|
||||
|| segment.loadingState.areAllItemsLoadedBackwards,
|
||||
};
|
||||
segment.loadingState = mergedLoadingState;
|
||||
return segment;
|
||||
}
|
||||
|
||||
function mergeChatMediaSearchSegments(currentSegment: ChatMediaSearchSegment, segments: ChatMediaSearchSegment[]) {
|
||||
return segments.reduce((acc, segment) => {
|
||||
const hasIntersection = areSortedArraysIntersecting(segment.foundIds, currentSegment.foundIds);
|
||||
if (hasIntersection) {
|
||||
currentSegment = mergeWithChatMediaSearchSegment(
|
||||
currentSegment.foundIds,
|
||||
currentSegment.loadingState,
|
||||
segment,
|
||||
);
|
||||
} else {
|
||||
acc.push(segment);
|
||||
}
|
||||
return acc;
|
||||
}, [] as ChatMediaSearchSegment[]);
|
||||
}
|
||||
|
||||
export function updateChatMediaSearchResults<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
currentSegment: ChatMediaSearchSegment,
|
||||
searchParams: ChatMediaSearchParams,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const segments = mergeChatMediaSearchSegments(currentSegment, searchParams.segments);
|
||||
|
||||
return replaceChatMediaSearchResults(
|
||||
global,
|
||||
chatId,
|
||||
threadId,
|
||||
currentSegment,
|
||||
segments,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
function removeIdFromSegment(id: number, segment: ChatMediaSearchSegment): ChatMediaSearchSegment {
|
||||
const foundIds = segment.foundIds.filter((foundId) => foundId !== id);
|
||||
|
||||
return {
|
||||
...segment,
|
||||
foundIds,
|
||||
};
|
||||
}
|
||||
|
||||
function removeIdsFromChatMediaSearchParams(
|
||||
id: number,
|
||||
searchParams: ChatMediaSearchParams,
|
||||
): ChatMediaSearchParams {
|
||||
const currentSegment = removeIdFromSegment(id, searchParams.currentSegment);
|
||||
const segments = searchParams.segments.map((segment) => removeIdFromSegment(id, segment));
|
||||
|
||||
return {
|
||||
...searchParams,
|
||||
currentSegment,
|
||||
segments,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeIdFromSearchResults<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
id: number,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const searchParams = selectChatMediaSearch(global, chatId, threadId, tabId);
|
||||
if (!searchParams) return global;
|
||||
|
||||
const updatedSearchParams = removeIdsFromChatMediaSearchParams(id, searchParams);
|
||||
|
||||
return replaceChatMediaSearch(
|
||||
global,
|
||||
chatId,
|
||||
threadId,
|
||||
updatedSearchParams,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
function resetForwardsLoadingStateInParams(
|
||||
searchParams: ChatMediaSearchParams,
|
||||
) {
|
||||
searchParams.currentSegment.loadingState.areAllItemsLoadedForwards = false;
|
||||
searchParams.segments.forEach((segment) => {
|
||||
segment.loadingState.areAllItemsLoadedForwards = false;
|
||||
});
|
||||
}
|
||||
|
||||
export function updateChatMediaLoadingState<T extends GlobalState>(
|
||||
global: T,
|
||||
newMessage: ApiMessage,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
if (!isMediaLoadableInViewer(newMessage)) {
|
||||
return global;
|
||||
}
|
||||
const searchParams = selectChatMediaSearch(global, chatId, threadId, tabId);
|
||||
if (!searchParams) return global;
|
||||
resetForwardsLoadingStateInParams(searchParams);
|
||||
|
||||
return replaceChatMediaSearch(
|
||||
global,
|
||||
chatId,
|
||||
threadId,
|
||||
searchParams,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
export function initializeChatMediaSearchResults<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const loadingState: LoadingState = {
|
||||
areAllItemsLoadedForwards: false,
|
||||
areAllItemsLoadedBackwards: false,
|
||||
};
|
||||
const currentSegment: ChatMediaSearchSegment = {
|
||||
foundIds: [],
|
||||
loadingState,
|
||||
};
|
||||
const segments: ChatMediaSearchSegment[] = [];
|
||||
|
||||
const isLoading = false;
|
||||
|
||||
return replaceChatMediaSearch(global, chatId, threadId, {
|
||||
currentSegment,
|
||||
segments,
|
||||
isLoading,
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
export function setChatMediaSearchLoading<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
isLoading: boolean,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
const searchParams = selectTabState(global, tabId).chatMediaSearch.byChatThreadKey[chatThreadKey];
|
||||
|
||||
if (!searchParams) {
|
||||
return global;
|
||||
}
|
||||
|
||||
return replaceChatMediaSearch(global, chatId, threadId, {
|
||||
...searchParams,
|
||||
isLoading,
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
export function replaceChatMediaSearchResults<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
currentSegment: ChatMediaSearchSegment,
|
||||
segments: ChatMediaSearchSegment[],
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return replaceChatMediaSearch(global, chatId, threadId, {
|
||||
...selectTabState(global, tabId).chatMediaSearch.byChatThreadKey[chatThreadKey],
|
||||
currentSegment,
|
||||
segments,
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
function replaceChatMediaSearch<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
searchParams: ChatMediaSearchParams,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return updateTabState(global, {
|
||||
chatMediaSearch: {
|
||||
byChatThreadKey: {
|
||||
...selectTabState(global, tabId).chatMediaSearch.byChatThreadKey,
|
||||
[chatThreadKey]: searchParams,
|
||||
},
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
@ -16,7 +16,8 @@ import {
|
||||
} from '../../util/iteratees';
|
||||
import { isLocalMessageId, type MessageKey } from '../../util/messageKey';
|
||||
import {
|
||||
hasMessageTtl, mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
||||
hasMessageTtl, isMediaLoadableInViewer,
|
||||
mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
||||
} from '../helpers';
|
||||
import {
|
||||
selectChat,
|
||||
@ -37,6 +38,7 @@ import {
|
||||
selectThreadInfo,
|
||||
selectViewportIds,
|
||||
} from '../selectors';
|
||||
import { removeIdFromSearchResults } from './localSearch';
|
||||
import { updateTabState } from './tabs';
|
||||
import { clearMessageTranslation } from './translations';
|
||||
|
||||
@ -296,9 +298,13 @@ export function deleteChatMessages<T extends GlobalState>(
|
||||
const updatedThreads = new Map<ThreadId, number[]>();
|
||||
updatedThreads.set(MAIN_THREAD_ID, messageIds);
|
||||
|
||||
const mediaIdsToRemove: number[] = [];
|
||||
messageIds.forEach((messageId) => {
|
||||
const message = byId[messageId];
|
||||
if (!message) return;
|
||||
if (isMediaLoadableInViewer(message)) {
|
||||
mediaIdsToRemove.push(messageId);
|
||||
}
|
||||
const threadId = selectThreadIdFromMessage(global, message);
|
||||
if (!threadId || threadId === MAIN_THREAD_ID) {
|
||||
return;
|
||||
@ -339,6 +345,10 @@ export function deleteChatMessages<T extends GlobalState>(
|
||||
}
|
||||
|
||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||
mediaIdsToRemove.forEach((mediaId) => {
|
||||
global = removeIdFromSearchResults(global, chatId, threadId, mediaId, tabId);
|
||||
});
|
||||
|
||||
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||
if (!viewportIds) return;
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ThreadId } from '../../types';
|
||||
import type { GlobalState, TabArgs } from '../types';
|
||||
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
@ -23,7 +24,7 @@ export function selectCurrentTextSearch<T extends GlobalState>(
|
||||
return currentSearch;
|
||||
}
|
||||
|
||||
export function selectCurrentMediaSearch<T extends GlobalState>(
|
||||
export function selectCurrentSharedMediaSearch<T extends GlobalState>(
|
||||
global: T,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
@ -34,5 +35,32 @@ export function selectCurrentMediaSearch<T extends GlobalState>(
|
||||
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return selectTabState(global, tabId).localMediaSearch.byChatThreadKey[chatThreadKey];
|
||||
return selectTabState(global, tabId).sharedMediaSearch.byChatThreadKey[chatThreadKey];
|
||||
}
|
||||
|
||||
export function selectCurrentChatMediaSearch<T extends GlobalState>(
|
||||
global: T,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||
if (!chatId || !threadId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return selectTabState(global, tabId).chatMediaSearch.byChatThreadKey[chatThreadKey];
|
||||
}
|
||||
|
||||
export function selectChatMediaSearch<T extends GlobalState>(
|
||||
global: T, chatId?: string, threadId?: ThreadId,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
if (!chatId || !threadId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const chatThreadKey = buildChatThreadKey(chatId, threadId);
|
||||
|
||||
return selectTabState(global, tabId).chatMediaSearch.byChatThreadKey[chatThreadKey];
|
||||
}
|
||||
|
||||
@ -92,6 +92,7 @@ import type {
|
||||
ApiPrivacySettings,
|
||||
AudioOrigin,
|
||||
ChatCreationProgress,
|
||||
ChatMediaSearchParams,
|
||||
EmojiKeywords,
|
||||
FocusDirection,
|
||||
GlobalSearchContent,
|
||||
@ -375,7 +376,7 @@ export type TabState = {
|
||||
}>;
|
||||
};
|
||||
|
||||
localMediaSearch: {
|
||||
sharedMediaSearch: {
|
||||
byChatThreadKey: Record<string, {
|
||||
currentType?: SharedMediaType;
|
||||
resultsByType?: Partial<Record<SharedMediaType, {
|
||||
@ -386,6 +387,10 @@ export type TabState = {
|
||||
}>;
|
||||
};
|
||||
|
||||
chatMediaSearch: {
|
||||
byChatThreadKey: Record<string, ChatMediaSearchParams>;
|
||||
};
|
||||
|
||||
management: {
|
||||
progress?: ManagementProgress;
|
||||
byChatId: Record<string, ManagementState>;
|
||||
@ -431,6 +436,7 @@ export type TabState = {
|
||||
playbackRate: number;
|
||||
isMuted: boolean;
|
||||
isHidden?: boolean;
|
||||
withDynamicLoading?: boolean;
|
||||
};
|
||||
|
||||
audioPlayer: {
|
||||
@ -1280,11 +1286,18 @@ export interface ActionPayloads {
|
||||
setLocalTextSearchTag: {
|
||||
tag: ApiReaction | undefined;
|
||||
} & WithTabId;
|
||||
setLocalMediaSearchType: {
|
||||
setSharedMediaSearchType: {
|
||||
mediaType: SharedMediaType;
|
||||
} & WithTabId;
|
||||
searchTextMessagesLocal: WithTabId | undefined;
|
||||
searchMediaMessagesLocal: WithTabId | undefined;
|
||||
searchSharedMediaMessages: WithTabId | undefined;
|
||||
searchChatMediaMessages: {
|
||||
currentMediaMessageId: number;
|
||||
direction?: LoadMoreDirection;
|
||||
chatId?: string;
|
||||
threadId? : ThreadId;
|
||||
limit?: number;
|
||||
} & WithTabId;
|
||||
searchMessagesByDate: {
|
||||
timestamp: number;
|
||||
} & WithTabId;
|
||||
@ -2414,6 +2427,7 @@ export interface ActionPayloads {
|
||||
volume?: number;
|
||||
playbackRate?: number;
|
||||
isMuted?: boolean;
|
||||
withDynamicLoading?: boolean;
|
||||
} & WithTabId;
|
||||
closeMediaViewer: WithTabId | undefined;
|
||||
setMediaViewerVolume: {
|
||||
|
||||
@ -388,6 +388,22 @@ export type ApiPrivacyKey = 'phoneNumber' | 'addByPhone' | 'lastSeen' | 'profile
|
||||
'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P' | 'bio' | 'birthday';
|
||||
export type PrivacyVisibility = 'everybody' | 'contacts' | 'closeFriends' | 'nonContacts' | 'nobody';
|
||||
|
||||
export interface LoadingState {
|
||||
areAllItemsLoadedForwards: boolean;
|
||||
areAllItemsLoadedBackwards: boolean;
|
||||
}
|
||||
|
||||
export interface ChatMediaSearchSegment {
|
||||
foundIds: number[];
|
||||
loadingState: LoadingState;
|
||||
}
|
||||
|
||||
export interface ChatMediaSearchParams {
|
||||
currentSegment: ChatMediaSearchSegment;
|
||||
segments: ChatMediaSearchSegment[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export enum ProfileState {
|
||||
Profile,
|
||||
SharedMedia,
|
||||
|
||||
@ -125,6 +125,9 @@ export function areSortedArraysEqual(array1: any[], array2: any[]) {
|
||||
export function areSortedArraysIntersecting(array1: any[], array2: any[]) {
|
||||
return array1[0] <= array2[array2.length - 1] && array1[array1.length - 1] >= array2[0];
|
||||
}
|
||||
export function isInsideSortedArrayRange(value:any, array: any[]) {
|
||||
return array[0] <= value && value <= array[array.length - 1];
|
||||
}
|
||||
|
||||
export function findIntersectionWithSet<T>(array: T[], set: Set<T>): T[] {
|
||||
return array.filter((a) => set.has(a));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user