History support and animation when navigating between chats (#1289)
This commit is contained in:
parent
e69ea676f3
commit
7574b6445b
@ -167,7 +167,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}, [animationLevel, orderDiff, animationType]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
openChat({ id: chatId });
|
||||
openChat({ id: chatId, shouldReplaceHistory: true });
|
||||
|
||||
if (isSelected && messageListType === 'thread') {
|
||||
focusLastMessage();
|
||||
|
||||
@ -154,7 +154,7 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
if (!digit) return;
|
||||
|
||||
if (digit === SAVED_MESSAGES_HOTKEY) {
|
||||
openChat({ id: currentUserId });
|
||||
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -173,7 +173,7 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const position = Number(digit) - 1;
|
||||
if (position > orderedIds.length - 1) return;
|
||||
|
||||
openChat({ id: orderedIds[position] });
|
||||
openChat({ id: orderedIds[position], shouldReplaceHistory: true });
|
||||
}
|
||||
|
||||
if (e.altKey) {
|
||||
|
||||
@ -51,7 +51,7 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const handleClick = useCallback(
|
||||
(id: number) => {
|
||||
openChat({ id });
|
||||
openChat({ id, shouldReplaceHistory: true });
|
||||
},
|
||||
[openChat],
|
||||
);
|
||||
|
||||
@ -138,7 +138,7 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}, [searchQuery, onSearchQuery]);
|
||||
|
||||
const handleSelectSaved = useCallback(() => {
|
||||
openChat({ id: currentUserId });
|
||||
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
||||
}, [currentUserId, openChat]);
|
||||
|
||||
const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
||||
|
||||
@ -80,7 +80,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const handleChatClick = useCallback(
|
||||
(id: number) => {
|
||||
openChat({ id });
|
||||
openChat({ id, shouldReplaceHistory: true });
|
||||
|
||||
if (id !== currentUserId) {
|
||||
addRecentlyFoundChatId({ id });
|
||||
|
||||
@ -60,7 +60,7 @@ const RecentContacts: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const handleClick = useCallback(
|
||||
(id: number) => {
|
||||
openChat({ id });
|
||||
openChat({ id, shouldReplaceHistory: true });
|
||||
onReset();
|
||||
setTimeout(() => {
|
||||
addRecentlyFoundChatId({ id });
|
||||
|
||||
@ -82,6 +82,8 @@
|
||||
}
|
||||
|
||||
.messages-layout {
|
||||
--slide-transition: 450ms cubic-bezier(0.25, 1, 0.5, 1);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@ -4,7 +4,7 @@ import React, {
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { ApiChatBannedRights, MAIN_THREAD_ID } from '../../api/types';
|
||||
import { GlobalActions, MessageListType } from '../../global/types';
|
||||
import { GlobalActions, MessageListType, MessageList as GlobalMessageList } from '../../global/types';
|
||||
import { ThemeKey } from '../../types';
|
||||
|
||||
import {
|
||||
@ -36,7 +36,6 @@ import {
|
||||
selectIsRightColumnShown,
|
||||
selectPinnedIds,
|
||||
selectTheme,
|
||||
selectThreadOriginChat,
|
||||
} from '../../modules/selectors';
|
||||
import { getCanPostInChat, getMessageSendingRestrictionReason, isChatPrivate } from '../../modules/helpers';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
@ -48,6 +47,7 @@ import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
||||
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
import { createMessageHash } from '../../util/routing';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import MiddleHeader from './MiddleHeader';
|
||||
@ -83,8 +83,9 @@ type StateProps = {
|
||||
isMobileSearchActive?: boolean;
|
||||
isSelectModeActive?: boolean;
|
||||
animationLevel?: number;
|
||||
originChatId?: number;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
currentTransitionKey: number;
|
||||
messageLists?: GlobalMessageList[];
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
@ -103,7 +104,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
messageListType,
|
||||
isPrivate,
|
||||
isPinnedMessageList,
|
||||
isScheduledMessageList,
|
||||
messageLists,
|
||||
canPost,
|
||||
currentUserBannedRights,
|
||||
defaultBannedRights,
|
||||
@ -119,8 +120,8 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
isMobileSearchActive,
|
||||
isSelectModeActive,
|
||||
animationLevel,
|
||||
originChatId,
|
||||
shouldSkipHistoryAnimations,
|
||||
currentTransitionKey,
|
||||
openChat,
|
||||
unpinAllMessages,
|
||||
loadUser,
|
||||
@ -260,26 +261,12 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
);
|
||||
|
||||
const closeChat = () => {
|
||||
if (renderingThreadId !== MAIN_THREAD_ID) {
|
||||
openChat({ id: originChatId, threadId: MAIN_THREAD_ID }, true);
|
||||
} else if (isPinnedMessageList || isScheduledMessageList) {
|
||||
openChat({ id: chatId, type: 'thread' });
|
||||
} else {
|
||||
openChat({ id: undefined }, true);
|
||||
}
|
||||
openChat({ id: undefined }, true);
|
||||
};
|
||||
|
||||
useHistoryBack(renderingChatId && renderingThreadId, closeChat, openChat, {
|
||||
id: chatId,
|
||||
threadId: MAIN_THREAD_ID,
|
||||
});
|
||||
|
||||
const isDiscussion = renderingChatId && renderingThreadId !== MAIN_THREAD_ID;
|
||||
|
||||
useHistoryBack(isDiscussion || isPinnedMessageList || isScheduledMessageList, closeChat, openChat, {
|
||||
id: chatId,
|
||||
threadId: renderingThreadId,
|
||||
});
|
||||
useHistoryBack(renderingChatId && renderingThreadId,
|
||||
closeChat, undefined, undefined, undefined,
|
||||
messageLists ? messageLists.map(createMessageHash) : []);
|
||||
|
||||
useHistoryBack(isMobileSearchActive, closeLocalTextSearch);
|
||||
useHistoryBack(isSelectModeActive, exitMessageSelectMode);
|
||||
@ -320,7 +307,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
/>
|
||||
<Transition
|
||||
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
||||
activeKey={renderingMessageListType === 'thread' && renderingThreadId === MAIN_THREAD_ID ? 1 : 2}
|
||||
activeKey={currentTransitionKey}
|
||||
shouldCleanup
|
||||
>
|
||||
{() => (
|
||||
@ -408,6 +395,7 @@ export default memo(withGlobal(
|
||||
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
|
||||
} = global.settings.themes[theme] || {};
|
||||
|
||||
const { messageLists } = global.messages;
|
||||
const currentMessageList = selectCurrentMessageList(global);
|
||||
const { isLeftColumnShown, chats: { listIds } } = global;
|
||||
|
||||
@ -422,6 +410,7 @@ export default memo(withGlobal(
|
||||
isMobileSearchActive: Boolean(IS_SINGLE_COLUMN_LAYOUT && selectCurrentTextSearch(global)),
|
||||
isSelectModeActive: selectIsInSelectMode(global),
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),
|
||||
};
|
||||
|
||||
if (!currentMessageList || !listIds.active) {
|
||||
@ -437,14 +426,12 @@ export default memo(withGlobal(
|
||||
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
||||
const isPinnedMessageList = messageListType === 'pinned';
|
||||
const isScheduledMessageList = messageListType === 'scheduled';
|
||||
const originChat = selectThreadOriginChat(global, chatId, threadId);
|
||||
|
||||
return {
|
||||
...state,
|
||||
chatId,
|
||||
threadId,
|
||||
messageListType,
|
||||
originChatId: originChat ? originChat.id : chatId,
|
||||
isPrivate: isChatPrivate(chatId),
|
||||
canPost: !isPinnedMessageList && (!chat || canPost) && !isBotNotStarted,
|
||||
isPinnedMessageList,
|
||||
@ -458,6 +445,7 @@ export default memo(withGlobal(
|
||||
),
|
||||
pinnedMessagesCount: pinnedIds ? pinnedIds.length : 0,
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
messageLists,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
|
||||
@ -36,7 +36,6 @@ import {
|
||||
selectAllowedMessageActions,
|
||||
selectIsRightColumnShown,
|
||||
selectThreadTopMessageId,
|
||||
selectThreadOriginChat,
|
||||
selectThreadInfo,
|
||||
selectChatMessages,
|
||||
selectPinnedIds,
|
||||
@ -88,7 +87,6 @@ type StateProps = {
|
||||
isRightColumnShown?: boolean;
|
||||
audioMessage?: ApiMessage;
|
||||
chatsById?: Record<number, ApiChat>;
|
||||
originChatId: number;
|
||||
messagesCount?: number;
|
||||
isChatWithSelf?: boolean;
|
||||
isChatWithBot?: boolean;
|
||||
@ -96,11 +94,12 @@ type StateProps = {
|
||||
notifySettings: NotifySettings;
|
||||
notifyExceptions?: Record<number, NotifyException>;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
currentTransitionKey: number;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'openChatWithInfo' | 'pinMessage' | 'focusMessage' | 'openChat' | 'loadPinnedMessages' | 'toggleLeftColumn' |
|
||||
'exitMessageSelectMode'
|
||||
'openChatWithInfo' | 'pinMessage' | 'focusMessage' | 'openChat' | 'openPreviousChat' | 'loadPinnedMessages' |
|
||||
'toggleLeftColumn' | 'exitMessageSelectMode'
|
||||
)>;
|
||||
|
||||
const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
@ -119,7 +118,6 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
audioMessage,
|
||||
chat,
|
||||
chatsById,
|
||||
originChatId,
|
||||
messagesCount,
|
||||
isChatWithSelf,
|
||||
isChatWithBot,
|
||||
@ -127,10 +125,12 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
notifySettings,
|
||||
notifyExceptions,
|
||||
shouldSkipHistoryAnimations,
|
||||
currentTransitionKey,
|
||||
openChatWithInfo,
|
||||
pinMessage,
|
||||
focusMessage,
|
||||
openChat,
|
||||
openPreviousChat,
|
||||
loadPinnedMessages,
|
||||
toggleLeftColumn,
|
||||
exitMessageSelectMode,
|
||||
@ -194,7 +194,8 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
messageInput.blur();
|
||||
}
|
||||
}
|
||||
if (threadId === MAIN_THREAD_ID && messageListType === 'thread') {
|
||||
|
||||
if (threadId === MAIN_THREAD_ID && messageListType === 'thread' && currentTransitionKey === 0) {
|
||||
if (IS_SINGLE_COLUMN_LAYOUT || shouldShowCloseButton) {
|
||||
e.stopPropagation(); // Stop propagation to prevent chat re-opening on tablets
|
||||
openChat({ id: undefined });
|
||||
@ -209,10 +210,10 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
exitMessageSelectMode();
|
||||
}
|
||||
|
||||
openChat({ id: originChatId, threadId: MAIN_THREAD_ID });
|
||||
openPreviousChat();
|
||||
}, [
|
||||
openChat, originChatId, threadId, messageListType, toggleLeftColumn, isSelectModeActive, exitMessageSelectMode,
|
||||
shouldShowCloseButton,
|
||||
threadId, messageListType, currentTransitionKey, isSelectModeActive, openPreviousChat, shouldShowCloseButton,
|
||||
openChat, toggleLeftColumn, exitMessageSelectMode,
|
||||
]);
|
||||
|
||||
const unreadCount = useMemo(() => {
|
||||
@ -339,7 +340,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
function renderMainThreadInfo() {
|
||||
return (
|
||||
<>
|
||||
{isLeftColumnHideable && renderBackButton(shouldShowCloseButton, unreadCount)}
|
||||
{(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, unreadCount)}
|
||||
<div className="chat-info-wrapper" onClick={handleHeaderClick}>
|
||||
{isChatPrivate(chatId) ? (
|
||||
<PrivateChatInfo
|
||||
@ -390,7 +391,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div className="MiddleHeader" ref={componentRef}>
|
||||
<Transition
|
||||
name={shouldSkipHistoryAnimations ? 'none' : 'slide-fade'}
|
||||
activeKey={messageListType === 'thread' ? threadId : 1}
|
||||
activeKey={currentTransitionKey}
|
||||
>
|
||||
{renderInfo}
|
||||
</Transition>
|
||||
@ -439,8 +440,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
? selectChatMessage(global, audioChatId, audioMessageId)
|
||||
: undefined;
|
||||
|
||||
const originChat = selectThreadOriginChat(global, chatId, threadId);
|
||||
|
||||
let messagesCount: number | undefined;
|
||||
if (messageListType === 'pinned') {
|
||||
const pinnedIds = selectPinnedIds(global, chatId);
|
||||
@ -463,7 +462,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
audioMessage,
|
||||
chat,
|
||||
chatsById,
|
||||
originChatId: originChat ? originChat.id : chatId,
|
||||
messagesCount,
|
||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||
isChatWithBot: chat && selectIsChatWithBot(global, chat),
|
||||
@ -471,6 +469,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
notifySettings: selectNotifySettings(global),
|
||||
notifyExceptions: selectNotifyExceptions(global),
|
||||
shouldSkipHistoryAnimations,
|
||||
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),
|
||||
};
|
||||
|
||||
const messagesById = selectChatMessages(global, chatId);
|
||||
@ -514,6 +513,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
'pinMessage',
|
||||
'focusMessage',
|
||||
'openChat',
|
||||
'openPreviousChat',
|
||||
'loadPinnedMessages',
|
||||
'toggleLeftColumn',
|
||||
'exitMessageSelectMode',
|
||||
|
||||
@ -64,7 +64,17 @@ const Modal: FC<OwnProps & StateProps> = ({
|
||||
: undefined), [isOpen, onClose, onEnter]);
|
||||
useEffect(() => (isOpen && modalRef.current ? trapFocus(modalRef.current) : undefined), [isOpen]);
|
||||
|
||||
useHistoryBack(isOpen, onClose);
|
||||
const { forceClose } = useHistoryBack(isOpen, onClose);
|
||||
|
||||
// For modals that are closed by unmounting without changing `isOpen` to `false`
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (isOpen) {
|
||||
forceClose();
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffectWithPrevDeps(([prevIsOpen]) => {
|
||||
document.body.classList.toggle('has-open-dialog', isOpen);
|
||||
|
||||
@ -229,7 +229,7 @@ function reduceMessages(global: GlobalState): GlobalState['messages'] {
|
||||
|
||||
return {
|
||||
byChatId,
|
||||
messageLists: !currentMessageList || IS_SINGLE_COLUMN_LAYOUT ? undefined : [{
|
||||
messageLists: !currentMessageList || IS_SINGLE_COLUMN_LAYOUT ? [] : [{
|
||||
...currentMessageList,
|
||||
threadId: MAIN_THREAD_ID,
|
||||
type: 'thread',
|
||||
|
||||
@ -33,6 +33,7 @@ export const INITIAL_STATE: GlobalState = {
|
||||
|
||||
messages: {
|
||||
byChatId: {},
|
||||
messageLists: [],
|
||||
},
|
||||
|
||||
scheduledMessages: {
|
||||
|
||||
@ -46,6 +46,12 @@ import {
|
||||
|
||||
export type MessageListType = 'thread' | 'pinned' | 'scheduled';
|
||||
|
||||
export interface MessageList {
|
||||
chatId: number;
|
||||
threadId: number;
|
||||
type: MessageListType;
|
||||
}
|
||||
|
||||
export interface Thread {
|
||||
listedIds?: number[];
|
||||
outlyingIds?: number[];
|
||||
@ -134,11 +140,7 @@ export type GlobalState = {
|
||||
byId: Record<number, ApiMessage>;
|
||||
threadsById: Record<number, Thread>;
|
||||
}>;
|
||||
messageLists?: {
|
||||
chatId: number;
|
||||
threadId: number;
|
||||
type: MessageListType;
|
||||
}[];
|
||||
messageLists: MessageList[];
|
||||
contentToBeScheduled?: {
|
||||
gif?: ApiVideo;
|
||||
sticker?: ApiSticker;
|
||||
@ -439,7 +441,7 @@ export type ActionTypes = (
|
||||
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
||||
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
|
||||
'loadProfilePhotos' | 'loadMoreMembers' | 'setActiveChatFolder' | 'openNextChat' |
|
||||
'addChatMembers' | 'deleteChatMember' |
|
||||
'addChatMembers' | 'deleteChatMember' | 'openPreviousChat' |
|
||||
// messages
|
||||
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
|
||||
'markMessageListRead' | 'markMessagesRead' | 'loadMessage' | 'focusMessage' | 'focusLastMessage' | 'sendPollVote' |
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { useEffect, useRef } from '../lib/teact/teact';
|
||||
import { useCallback, useEffect, useRef } from '../lib/teact/teact';
|
||||
|
||||
import { IS_IOS } from '../util/environment';
|
||||
import usePrevious from './usePrevious';
|
||||
import { getDispatch } from '../lib/teact/teactn';
|
||||
import { areSortedArraysEqual } from '../util/iteratees';
|
||||
|
||||
// Carefully selected by swiping and observing visual changes
|
||||
// TODO: may be different on other devices such as iPad, maybe take dpi into account?
|
||||
@ -61,48 +62,19 @@ export default function useHistoryBack(
|
||||
onForward?: (state: any) => void,
|
||||
currentState?: any,
|
||||
shouldReplaceNext = false,
|
||||
hashes?: string[],
|
||||
) {
|
||||
const indexRef = useRef(-1);
|
||||
const isForward = useRef(false);
|
||||
const prevIsActive = usePrevious(isActive);
|
||||
const isClosed = useRef(true);
|
||||
const indexHashRef = useRef<{ index: number; hash: string }[]>([]);
|
||||
const prevHashes = usePrevious(hashes);
|
||||
const isHashChangedFromEvent = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = (event: PopStateEvent) => {
|
||||
if (historyState.isHistoryAltered) {
|
||||
setTimeout(() => {
|
||||
historyState.isHistoryAltered = false;
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
const { index: i } = event.state;
|
||||
const index = i || 0;
|
||||
|
||||
const prev = historyState.currentIndexes[historyState.currentIndexes.indexOf(indexRef.current) - 1];
|
||||
|
||||
if (historyState.isDisabled) return;
|
||||
|
||||
if (!isClosed.current && (index === 0 || index === prev)) {
|
||||
historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(indexRef.current), 1);
|
||||
|
||||
if (onBack) {
|
||||
if (historyState.isEdge) {
|
||||
getDispatch().disableHistoryAnimations();
|
||||
}
|
||||
onBack(!historyState.isEdge);
|
||||
isClosed.current = true;
|
||||
}
|
||||
} else if (index === indexRef.current && isClosed.current && onForward) {
|
||||
isForward.current = true;
|
||||
if (historyState.isEdge) {
|
||||
getDispatch().disableHistoryAnimations();
|
||||
}
|
||||
onForward(event.state.state);
|
||||
}
|
||||
};
|
||||
|
||||
if (!historyState.isDisabled && prevIsActive !== isActive) {
|
||||
if (isActive) {
|
||||
const handleChange = useCallback((isForceClose = false) => {
|
||||
if (!hashes) {
|
||||
if (isActive && !isForceClose) {
|
||||
isClosed.current = false;
|
||||
|
||||
if (isForward.current) {
|
||||
@ -126,6 +98,7 @@ export default function useHistoryBack(
|
||||
state: currentState,
|
||||
}, '');
|
||||
|
||||
|
||||
indexRef.current = index;
|
||||
|
||||
if (shouldReplaceNext) {
|
||||
@ -133,8 +106,10 @@ export default function useHistoryBack(
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} else if (!isClosed.current) {
|
||||
if (indexRef.current === historyState.currentIndex || !shouldReplaceNext) {
|
||||
}
|
||||
|
||||
if ((isForceClose || !isActive) && !isClosed.current) {
|
||||
if ((indexRef.current === historyState.currentIndex || !shouldReplaceNext)) {
|
||||
historyState.isHistoryAltered = true;
|
||||
window.history.back();
|
||||
|
||||
@ -146,9 +121,127 @@ export default function useHistoryBack(
|
||||
|
||||
isClosed.current = true;
|
||||
}
|
||||
} else {
|
||||
const prev = prevHashes || [];
|
||||
if (prev.length < hashes.length) {
|
||||
const index = ++historyState.currentIndex;
|
||||
historyState.currentIndexes.push(index);
|
||||
|
||||
window.history.pushState({
|
||||
index,
|
||||
state: currentState,
|
||||
}, '', `#${hashes[hashes.length - 1]}`);
|
||||
|
||||
indexHashRef.current.push({
|
||||
index,
|
||||
hash: hashes[hashes.length - 1],
|
||||
});
|
||||
} else {
|
||||
const delta = prev.length - hashes.length;
|
||||
if (isHashChangedFromEvent.current) {
|
||||
isHashChangedFromEvent.current = false;
|
||||
} else {
|
||||
if (hashes.length !== indexHashRef.current.length) {
|
||||
if (delta > 0) {
|
||||
const last = indexHashRef.current[indexHashRef.current.length - delta - 1];
|
||||
let realDelta = delta;
|
||||
if (last) {
|
||||
const indexLast = historyState.currentIndexes.findIndex(
|
||||
(l) => l === last.index,
|
||||
);
|
||||
realDelta = historyState.currentIndexes.length - indexLast - 1;
|
||||
}
|
||||
historyState.isHistoryAltered = true;
|
||||
window.history.go(-realDelta);
|
||||
const removed = indexHashRef.current.splice(indexHashRef.current.length - delta - 1, delta);
|
||||
removed.forEach(({ index }) => {
|
||||
historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(index), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (hashes.length > 0) {
|
||||
setTimeout(() => {
|
||||
const index = ++historyState.currentIndex;
|
||||
historyState.currentIndexes[historyState.currentIndexes.length - 1] = index;
|
||||
|
||||
window.history.replaceState({
|
||||
index,
|
||||
state: currentState,
|
||||
}, '', `#${hashes[hashes.length - 1]}`);
|
||||
|
||||
indexHashRef.current[indexHashRef.current.length - 1] = {
|
||||
index,
|
||||
hash: hashes[hashes.length - 1],
|
||||
};
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [currentState, hashes, isActive, prevHashes, shouldReplaceNext]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = (event: PopStateEvent) => {
|
||||
if (historyState.isHistoryAltered) {
|
||||
setTimeout(() => {
|
||||
historyState.isHistoryAltered = false;
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
const { index: i } = event.state;
|
||||
const index = i || 0;
|
||||
try {
|
||||
const currIndex = hashes ? indexHashRef.current[indexHashRef.current.length - 1].index : indexRef.current;
|
||||
|
||||
const prev = historyState.currentIndexes[historyState.currentIndexes.indexOf(currIndex) - 1];
|
||||
|
||||
if (historyState.isDisabled) return;
|
||||
|
||||
if ((!isClosed.current && (index === 0 || index === prev)) || (hashes && (index === 0 || index === prev))) {
|
||||
if (hashes) {
|
||||
isHashChangedFromEvent.current = true;
|
||||
indexHashRef.current.pop();
|
||||
}
|
||||
|
||||
historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(currIndex), 1);
|
||||
|
||||
if (onBack) {
|
||||
if (historyState.isEdge) {
|
||||
getDispatch()
|
||||
.disableHistoryAnimations();
|
||||
}
|
||||
onBack(!historyState.isEdge);
|
||||
isClosed.current = true;
|
||||
}
|
||||
} else if (index === currIndex && isClosed.current && onForward && !hashes) {
|
||||
isForward.current = true;
|
||||
if (historyState.isEdge) {
|
||||
getDispatch()
|
||||
.disableHistoryAnimations();
|
||||
}
|
||||
onForward(event.state.state);
|
||||
}
|
||||
} catch (e) {
|
||||
// Forward navigation for hashed is not supported
|
||||
}
|
||||
};
|
||||
|
||||
const hasChanged = hashes
|
||||
? (!prevHashes || !areSortedArraysEqual(prevHashes, hashes))
|
||||
: prevIsActive !== isActive;
|
||||
|
||||
if (!historyState.isDisabled && hasChanged) {
|
||||
handleChange();
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, [currentState, isActive, onBack, onForward, prevIsActive, shouldReplaceNext]);
|
||||
}, [
|
||||
currentState, handleChange, hashes, isActive, onBack, onForward, prevHashes, prevIsActive, shouldReplaceNext,
|
||||
]);
|
||||
|
||||
return {
|
||||
forceClose: () => handleChange(true),
|
||||
};
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import { closeLocalTextSearch } from './localSearch';
|
||||
|
||||
addReducer('openChat', (global, actions, payload) => {
|
||||
const {
|
||||
id, threadId = -1, type = 'thread',
|
||||
id, threadId = -1, type = 'thread', shouldReplaceHistory = false,
|
||||
} = payload!;
|
||||
|
||||
const currentMessageList = selectCurrentMessageList(global);
|
||||
@ -37,7 +37,11 @@ addReducer('openChat', (global, actions, payload) => {
|
||||
setGlobal(global);
|
||||
}
|
||||
|
||||
return updateCurrentMessageList(global, id, threadId, type);
|
||||
return updateCurrentMessageList(global, id, threadId, type, shouldReplaceHistory);
|
||||
});
|
||||
|
||||
addReducer('openPreviousChat', (global) => {
|
||||
return updateCurrentMessageList(global, undefined);
|
||||
});
|
||||
|
||||
addReducer('openChatWithInfo', (global, actions, payload) => {
|
||||
@ -80,5 +84,5 @@ addReducer('openNextChat', (global, actions, payload) => {
|
||||
}
|
||||
const nextId = orderedIds[position + targetIndexDelta];
|
||||
|
||||
actions.openChat({ id: nextId });
|
||||
actions.openChat({ id: nextId, shouldReplaceHistory: true });
|
||||
});
|
||||
|
||||
@ -58,16 +58,14 @@ addReducer('closeManagement', (global): GlobalState | undefined => {
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('openChat', (global, actions, payload) => {
|
||||
addReducer('openChat', (global) => {
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT && !IS_TABLET_COLUMN_LAYOUT) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { id } = payload!;
|
||||
|
||||
return {
|
||||
...global,
|
||||
isLeftColumnShown: id === undefined,
|
||||
isLeftColumnShown: global.messages.messageLists.length === 0,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { GlobalState, MessageListType, Thread } from '../../global/types';
|
||||
import {
|
||||
GlobalState, MessageList, MessageListType, Thread,
|
||||
} from '../../global/types';
|
||||
import { ApiMessage, ApiThreadInfo, MAIN_THREAD_ID } from '../../api/types';
|
||||
import { FocusDirection } from '../../types';
|
||||
|
||||
@ -21,6 +23,8 @@ import {
|
||||
areSortedArraysEqual, omit, pickTruthy, unique,
|
||||
} from '../../util/iteratees';
|
||||
|
||||
const TMP_CHAT_ID = -1;
|
||||
|
||||
type MessageStoreSections = {
|
||||
byId: Record<number, ApiMessage>;
|
||||
threadsById: Record<number, Thread>;
|
||||
@ -31,13 +35,30 @@ export function updateCurrentMessageList(
|
||||
chatId: number | undefined,
|
||||
threadId: number = MAIN_THREAD_ID,
|
||||
type: MessageListType = 'thread',
|
||||
shouldReplaceHistory?: boolean,
|
||||
): GlobalState {
|
||||
const { messageLists } = global.messages;
|
||||
let newMessageLists: MessageList[] = messageLists;
|
||||
if (shouldReplaceHistory) {
|
||||
newMessageLists = chatId ? [{ chatId, threadId, type }] : [];
|
||||
} else if (chatId) {
|
||||
const last = messageLists[messageLists.length - 1];
|
||||
if (!last || last.chatId !== chatId || last.threadId !== threadId || last.type !== type) {
|
||||
if (last && last.chatId === TMP_CHAT_ID) {
|
||||
newMessageLists = [...messageLists.slice(0, -1), { chatId, threadId, type }];
|
||||
} else {
|
||||
newMessageLists = [...messageLists, { chatId, threadId, type }];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newMessageLists = messageLists.slice(0, -1);
|
||||
}
|
||||
|
||||
return {
|
||||
...global,
|
||||
messages: {
|
||||
...global.messages,
|
||||
// TODO Support stack navigation
|
||||
messageLists: chatId ? [{ chatId, threadId, type }] : undefined,
|
||||
messageLists: newMessageLists,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ const MESSAGE_EDIT_ALLOWED_TIME_MS = 172800000; // 48 hours
|
||||
export function selectCurrentMessageList(global: GlobalState) {
|
||||
const { messageLists } = global.messages;
|
||||
|
||||
if (messageLists && messageLists.length) {
|
||||
if (messageLists.length) {
|
||||
return messageLists[messageLists.length - 1];
|
||||
}
|
||||
|
||||
|
||||
@ -22,21 +22,13 @@ export default function fastSmoothScroll(
|
||||
forceDuration?: number,
|
||||
forceCurrentContainerHeight?: boolean,
|
||||
) {
|
||||
const scrollFrom = calculateScrollFrom(container, element, maxDistance, forceDirection);
|
||||
|
||||
if (forceDirection === FocusDirection.Static) {
|
||||
let block!: ScrollLogicalPosition;
|
||||
|
||||
if (position === 'centerOrTop') {
|
||||
block = element.offsetHeight < container.offsetHeight ? 'center' : 'start';
|
||||
} else {
|
||||
block = position;
|
||||
}
|
||||
|
||||
element.scrollIntoView({ block });
|
||||
|
||||
scrollWithJs(container, element, scrollFrom, position, margin, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollFrom = calculateScrollFrom(container, element, maxDistance, forceDirection);
|
||||
|
||||
if (getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) {
|
||||
forceDuration = 0;
|
||||
|
||||
19
src/util/routing.ts
Normal file
19
src/util/routing.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { MessageList, MessageListType } from '../global/types';
|
||||
import { MAIN_THREAD_ID } from '../api/types';
|
||||
|
||||
export const createMessageHash = (messageList: MessageList): string => (
|
||||
messageList.chatId.toString()
|
||||
+ (messageList.type !== 'thread' ? `_${messageList.type}`
|
||||
: (messageList.threadId !== -1 ? `_${messageList.threadId}` : ''))
|
||||
);
|
||||
|
||||
export const parseMessageHash = (value: string): MessageList => {
|
||||
const [chatId, typeOrThreadId] = value.split('_');
|
||||
const isType = ['thread', 'pinned', 'scheduled'].includes(typeOrThreadId);
|
||||
|
||||
return {
|
||||
chatId: Number(chatId),
|
||||
type: !!typeOrThreadId && isType ? (typeOrThreadId as MessageListType) : 'thread',
|
||||
threadId: !!typeOrThreadId && !isType ? Number(typeOrThreadId) : MAIN_THREAD_ID,
|
||||
};
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user