Left Column: Fix resizer in archive component (#3152)

This commit is contained in:
Alexander Zinchuk 2023-05-05 15:53:10 +04:00
parent dc75eea5d6
commit d04177aea8
7 changed files with 62 additions and 64 deletions

View File

@ -1,9 +1,9 @@
import type { RefObject } from 'react';
import React, { import React, {
memo, useCallback, useEffect, useRef, useState, memo, useCallback, useEffect, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { GlobalState } from '../../global/types'; import type { GlobalState } from '../../global/types';
import { LeftColumnContent, SettingsScreens } from '../../types'; import { LeftColumnContent, SettingsScreens } from '../../types';
import type { ReducerAction } from '../../hooks/useReducer'; import type { ReducerAction } from '../../hooks/useReducer';
@ -13,7 +13,6 @@ import { IS_MAC_OS, IS_PWA, LAYERS_ANIMATION_NAME } from '../../util/windowEnvir
import captureEscKeyListener from '../../util/captureEscKeyListener'; import captureEscKeyListener from '../../util/captureEscKeyListener';
import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors'; import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors';
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer'; import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
import { useResize } from '../../hooks/useResize';
import { useHotkeys } from '../../hooks/useHotkeys'; import { useHotkeys } from '../../hooks/useHotkeys';
import useSyncEffect from '../../hooks/useSyncEffect'; import useSyncEffect from '../../hooks/useSyncEffect';
@ -25,12 +24,15 @@ import ArchivedChats from './ArchivedChats.async';
import './LeftColumn.scss'; import './LeftColumn.scss';
interface OwnProps {
ref: RefObject<HTMLDivElement>;
}
type StateProps = { type StateProps = {
searchQuery?: string; searchQuery?: string;
searchDate?: number; searchDate?: number;
isFirstChatFolderActive: boolean; isFirstChatFolderActive: boolean;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
leftColumnWidth?: number;
currentUserId?: string; currentUserId?: string;
hasPasscode?: boolean; hasPasscode?: boolean;
nextSettingsScreen?: SettingsScreens; nextSettingsScreen?: SettingsScreens;
@ -57,12 +59,12 @@ enum ContentType {
const RENDER_COUNT = Object.keys(ContentType).length / 2; const RENDER_COUNT = Object.keys(ContentType).length / 2;
const RESET_TRANSITION_DELAY_MS = 250; const RESET_TRANSITION_DELAY_MS = 250;
const LeftColumn: FC<StateProps> = ({ function LeftColumn({
ref,
searchQuery, searchQuery,
searchDate, searchDate,
isFirstChatFolderActive, isFirstChatFolderActive,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
leftColumnWidth,
currentUserId, currentUserId,
hasPasscode, hasPasscode,
nextSettingsScreen, nextSettingsScreen,
@ -73,7 +75,7 @@ const LeftColumn: FC<StateProps> = ({
forumPanelChatId, forumPanelChatId,
isClosingSearch, isClosingSearch,
archiveSettings, archiveSettings,
}) => { }: OwnProps & StateProps) {
const { const {
setGlobalSearchQuery, setGlobalSearchQuery,
setGlobalSearchClosing, setGlobalSearchClosing,
@ -82,14 +84,10 @@ const LeftColumn: FC<StateProps> = ({
setGlobalSearchDate, setGlobalSearchDate,
loadPasswordInfo, loadPasswordInfo,
clearTwoFaError, clearTwoFaError,
setLeftColumnWidth,
resetLeftColumnWidth,
openChat, openChat,
requestNextSettingsScreen, requestNextSettingsScreen,
} = getActions(); } = getActions();
// eslint-disable-next-line no-null/no-null
const resizeRef = useRef<HTMLDivElement>(null);
const [content, setContent] = useState<LeftColumnContent>(LeftColumnContent.ChatList); const [content, setContent] = useState<LeftColumnContent>(LeftColumnContent.ChatList);
const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main); const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main);
const [contactsFilter, setContactsFilter] = useState<string>(''); const [contactsFilter, setContactsFilter] = useState<string>('');
@ -410,12 +408,6 @@ const LeftColumn: FC<StateProps> = ({
} }
}, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]); }, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]);
const {
initResize, resetResize, handleMouseUp,
} = useResize(resizeRef, (n) => setLeftColumnWidth({
leftColumnWidth: n,
}), resetLeftColumnWidth, leftColumnWidth, '--left-column-width');
const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => { const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => {
setContent(LeftColumnContent.Settings); setContent(LeftColumnContent.Settings);
setSettingsScreen(screen); setSettingsScreen(screen);
@ -493,7 +485,7 @@ const LeftColumn: FC<StateProps> = ({
return ( return (
<Transition <Transition
ref={resizeRef} ref={ref}
name={shouldSkipHistoryAnimations ? 'none' : LAYERS_ANIMATION_NAME} name={shouldSkipHistoryAnimations ? 'none' : LAYERS_ANIMATION_NAME}
renderCount={RENDER_COUNT} renderCount={RENDER_COUNT}
activeKey={contentType} activeKey={contentType}
@ -502,21 +494,13 @@ const LeftColumn: FC<StateProps> = ({
shouldWrap shouldWrap
wrapExceptionKey={ContentType.Main} wrapExceptionKey={ContentType.Main}
id="LeftColumn" id="LeftColumn"
afterChildren={(
<div
className="resize-handle"
onMouseDown={initResize}
onMouseUp={handleMouseUp}
onDoubleClick={resetResize}
/>
)}
> >
{renderContent} {renderContent}
</Transition> </Transition>
); );
}; }
export default memo(withGlobal( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const tabState = selectTabState(global); const tabState = selectTabState(global);
const { const {
@ -530,7 +514,6 @@ export default memo(withGlobal(
nextFoldersAction, nextFoldersAction,
} = tabState; } = tabState;
const { const {
leftColumnWidth,
currentUserId, currentUserId,
passcode: { passcode: {
hasPasscode, hasPasscode,
@ -549,7 +532,6 @@ export default memo(withGlobal(
searchDate: date, searchDate: date,
isFirstChatFolderActive: activeChatFolder === 0, isFirstChatFolderActive: activeChatFolder === 0,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
leftColumnWidth,
currentUserId, currentUserId,
hasPasscode, hasPasscode,
nextSettingsScreen, nextSettingsScreen,

View File

@ -35,6 +35,7 @@
min-width: var(--left-column-min-width); min-width: var(--left-column-min-width);
max-width: var(--left-column-max-width); max-width: var(--left-column-max-width);
height: 100%; height: 100%;
overflow: hidden;
position: relative; position: relative;
background-color: var(--color-background); background-color: var(--color-background);

View File

@ -257,6 +257,8 @@ const Main: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null
const leftColumnRef = useRef<HTMLDivElement>(null);
const { isDesktop } = useAppLayout(); const { isDesktop } = useAppLayout();
useEffect(() => { useEffect(() => {
@ -488,8 +490,8 @@ const Main: FC<OwnProps & StateProps> = ({
return ( return (
<div ref={containerRef} id="Main" className={className}> <div ref={containerRef} id="Main" className={className}>
<LeftColumn /> <LeftColumn ref={leftColumnRef} />
<MiddleColumn isMobile={isMobile} /> <MiddleColumn leftColumnRef={leftColumnRef} isMobile={isMobile} />
<RightColumn isMobile={isMobile} /> <RightColumn isMobile={isMobile} />
<MediaViewer isOpen={isMediaViewerOpen} /> <MediaViewer isOpen={isMediaViewerOpen} />
<ForwardRecipientPicker isOpen={isForwardModalOpen} /> <ForwardRecipientPicker isOpen={isForwardModalOpen} />

View File

@ -1,4 +1,4 @@
import type { FC } from '../../lib/teact/teact'; import type { RefObject } from 'react';
import React, { import React, {
useEffect, useState, memo, useMemo, useCallback, useEffect, useState, memo, useMemo, useCallback,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
@ -67,6 +67,7 @@ import useForceUpdate from '../../hooks/useForceUpdate';
import useSyncEffect from '../../hooks/useSyncEffect'; import useSyncEffect from '../../hooks/useSyncEffect';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';
import usePinnedMessage from './hooks/usePinnedMessage'; import usePinnedMessage from './hooks/usePinnedMessage';
import { useResize } from '../../hooks/useResize';
import Transition from '../ui/Transition'; import Transition from '../ui/Transition';
import MiddleHeader from './MiddleHeader'; import MiddleHeader from './MiddleHeader';
@ -88,6 +89,7 @@ import './MiddleColumn.scss';
import styles from './MiddleColumn.module.scss'; import styles from './MiddleColumn.module.scss';
interface OwnProps { interface OwnProps {
leftColumnRef: RefObject<HTMLDivElement>;
isMobile?: boolean; isMobile?: boolean;
} }
@ -99,7 +101,6 @@ type StateProps = {
replyingToId?: number; replyingToId?: number;
isPrivate?: boolean; isPrivate?: boolean;
isPinnedMessageList?: boolean; isPinnedMessageList?: boolean;
isScheduledMessageList?: boolean;
canPost?: boolean; canPost?: boolean;
currentUserBannedRights?: ApiChatBannedRights; currentUserBannedRights?: ApiChatBannedRights;
defaultBannedRights?: ApiChatBannedRights; defaultBannedRights?: ApiChatBannedRights;
@ -114,6 +115,7 @@ type StateProps = {
isLeftColumnShown?: boolean; isLeftColumnShown?: boolean;
isRightColumnShown?: boolean; isRightColumnShown?: boolean;
isBackgroundBlurred?: boolean; isBackgroundBlurred?: boolean;
leftColumnWidth?: number;
hasCurrentTextSearch?: boolean; hasCurrentTextSearch?: boolean;
isSelectModeActive?: boolean; isSelectModeActive?: boolean;
isSeenByModalOpen: boolean; isSeenByModalOpen: boolean;
@ -142,7 +144,8 @@ function isImage(item: DataTransferItem) {
const LAYER_ANIMATION_DURATION_MS = 450 + ANIMATION_END_DELAY; const LAYER_ANIMATION_DURATION_MS = 450 + ANIMATION_END_DELAY;
const MiddleColumn: FC<OwnProps & StateProps> = ({ function MiddleColumn({
leftColumnRef,
chatId, chatId,
threadId, threadId,
messageListType, messageListType,
@ -165,6 +168,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
isLeftColumnShown, isLeftColumnShown,
isRightColumnShown, isRightColumnShown,
isBackgroundBlurred, isBackgroundBlurred,
leftColumnWidth,
hasCurrentTextSearch, hasCurrentTextSearch,
isSelectModeActive, isSelectModeActive,
isSeenByModalOpen, isSeenByModalOpen,
@ -185,7 +189,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
shouldLoadFullChat, shouldLoadFullChat,
lastSyncTime, lastSyncTime,
pinnedIds, pinnedIds,
}) => { }: OwnProps & StateProps) {
const { const {
openChat, openChat,
openPreviousChat, openPreviousChat,
@ -199,6 +203,8 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
restartBot, restartBot,
showNotification, showNotification,
loadFullChat, loadFullChat,
setLeftColumnWidth,
resetLeftColumnWidth,
} = getActions(); } = getActions();
const { width: windowWidth } = useWindowSize(); const { width: windowWidth } = useWindowSize();
@ -319,6 +325,12 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
} }
}, [shouldLoadFullChat, chatId, isReady, loadFullChat]); }, [shouldLoadFullChat, chatId, isReady, loadFullChat]);
const {
initResize, resetResize, handleMouseUp,
} = useResize(leftColumnRef, (n) => setLeftColumnWidth({
leftColumnWidth: n,
}), resetLeftColumnWidth, leftColumnWidth, '--left-column-width');
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => { const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
const { items } = e.dataTransfer || {}; const { items } = e.dataTransfer || {};
const shouldDrawQuick = items && items.length > 0 && Array.from(items) const shouldDrawQuick = items && items.length > 0 && Array.from(items)
@ -452,6 +464,12 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
`} `}
onClick={(isTablet && isLeftColumnShown) ? handleTabletFocus : undefined} onClick={(isTablet && isLeftColumnShown) ? handleTabletFocus : undefined}
> >
<div
className="resize-handle"
onMouseDown={initResize}
onMouseUp={handleMouseUp}
onDoubleClick={resetResize}
/>
<div <div
className={bgClassName} className={bgClassName}
style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined} style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined}
@ -622,7 +640,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
<GiftPremiumModal isOpen={isGiftPremiumModalOpen} /> <GiftPremiumModal isOpen={isGiftPremiumModalOpen} />
</div> </div>
); );
}; }
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { isMobile }): StateProps => { (global, { isMobile }): StateProps => {
@ -637,7 +655,7 @@ export default memo(withGlobal<OwnProps>(
messageLanguageModal, messageLanguageModal,
} = selectTabState(global); } = selectTabState(global);
const currentMessageList = selectCurrentMessageList(global); const currentMessageList = selectCurrentMessageList(global);
const { chats: { listIds }, lastSyncTime } = global; const { chats: { listIds }, leftColumnWidth, lastSyncTime } = global;
const state: StateProps = { const state: StateProps = {
theme, theme,
@ -656,6 +674,7 @@ export default memo(withGlobal<OwnProps>(
withInterfaceAnimations: selectCanAnimateInterface(global), withInterfaceAnimations: selectCanAnimateInterface(global),
currentTransitionKey: Math.max(0, messageLists.length - 1), currentTransitionKey: Math.max(0, messageLists.length - 1),
activeEmojiInteractions, activeEmojiInteractions,
leftColumnWidth,
lastSyncTime, lastSyncTime,
}; };
@ -675,7 +694,6 @@ export default memo(withGlobal<OwnProps>(
const canPost = chat && getCanPostInChat(chat, threadId, isComments); const canPost = chat && getCanPostInChat(chat, threadId, isComments);
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId); const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
const isPinnedMessageList = messageListType === 'pinned'; const isPinnedMessageList = messageListType === 'pinned';
const isScheduledMessageList = messageListType === 'scheduled';
const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID; const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID;
const isChannel = Boolean(chat && isChatChannel(chat)); const isChannel = Boolean(chat && isChatChannel(chat));
const canSubscribe = Boolean( const canSubscribe = Boolean(
@ -711,7 +729,6 @@ export default memo(withGlobal<OwnProps>(
&& !(shouldJoinToSend && chat?.isNotJoined) && !(shouldJoinToSend && chat?.isNotJoined)
&& !shouldBlockSendInForum, && !shouldBlockSendInForum,
isPinnedMessageList, isPinnedMessageList,
isScheduledMessageList,
currentUserBannedRights: chat?.currentUserBannedRights, currentUserBannedRights: chat?.currentUserBannedRights,
defaultBannedRights: chat?.defaultBannedRights, defaultBannedRights: chat?.defaultBannedRights,
hasPinned: ( hasPinned: (

View File

@ -40,7 +40,6 @@ export type TransitionProps = {
onStart?: NoneToVoidFunction; onStart?: NoneToVoidFunction;
onStop?: NoneToVoidFunction; onStop?: NoneToVoidFunction;
children: React.ReactNode | ChildrenFn; children: React.ReactNode | ChildrenFn;
afterChildren?: React.ReactNode;
}; };
const FALLBACK_ANIMATION_END = 1000; const FALLBACK_ANIMATION_END = 1000;
@ -50,7 +49,6 @@ const CLASSES = {
from: 'Transition_slide-from', from: 'Transition_slide-from',
to: 'Transition_slide-to', to: 'Transition_slide-to',
inactive: 'Transition_slide-inactive', inactive: 'Transition_slide-inactive',
afterSlides: 'Transition_afterSlides',
}; };
const DISABLEABLE_ANIMATIONS = new Set<AnimationName>([ const DISABLEABLE_ANIMATIONS = new Set<AnimationName>([
'slide', 'slideRtl', 'slideFade', 'zoomFade', 'slideLayers', 'pushSlide', 'reveal', 'slide', 'slideRtl', 'slideFade', 'zoomFade', 'slideLayers', 'pushSlide', 'reveal',
@ -75,7 +73,6 @@ function Transition({
onStart, onStart,
onStop, onStop,
children, children,
afterChildren,
}: TransitionProps) { }: TransitionProps) {
const currentKeyRef = useRef<number>(); const currentKeyRef = useRef<number>();
// No need for a container to update on change // No need for a container to update on change
@ -128,14 +125,12 @@ function Transition({
const prevActiveIndex = renderCount ? prevActiveKey : keys.indexOf(prevActiveKey); const prevActiveIndex = renderCount ? prevActiveKey : keys.indexOf(prevActiveKey);
const activeIndex = renderCount ? activeKey : keys.indexOf(activeKey); const activeIndex = renderCount ? activeKey : keys.indexOf(activeKey);
const childNodes = Array.from(container.childNodes) const childNodes = Array.from(container.childNodes);
.filter((el) => !(el instanceof HTMLElement && el.classList.contains(CLASSES.afterSlides)));
if (!childNodes.length) { if (!childNodes.length) {
return; return;
} }
const childElements = (Array.from(container.children) as HTMLElement[]) const childElements = Array.from(container.children) as HTMLElement[];
.filter((el) => !el.classList.contains(CLASSES.afterSlides));
childElements.forEach((el) => { childElements.forEach((el) => {
addExtraClass(el, CLASSES.slide); addExtraClass(el, CLASSES.slide);
@ -319,12 +314,6 @@ function Transition({
: rendered; : rendered;
}); });
if (afterChildren) {
contents.push((
<div className={CLASSES.afterSlides}>{afterChildren}</div>
));
}
return ( return (
<div <div
ref={containerRef} ref={containerRef}

View File

@ -2,6 +2,7 @@ import type { RefObject } from 'react';
import { import {
useState, useEffect, useLayoutEffect, useCallback, useState, useEffect, useLayoutEffect, useCallback,
} from '../lib/teact/teact'; } from '../lib/teact/teact';
import { requestMutation } from '../lib/fasterdom/fasterdom';
import useFlag from './useFlag'; import useFlag from './useFlag';
export function useResize( export function useResize(
@ -16,15 +17,17 @@ export function useResize(
const [initialElementWidth, setInitialElementWidth] = useState<number>(0); const [initialElementWidth, setInitialElementWidth] = useState<number>(0);
const setElementStyle = useCallback((width?: number) => { const setElementStyle = useCallback((width?: number) => {
if (!elementRef.current) { requestMutation(() => {
return; if (!elementRef.current) {
} return;
}
const widthPx = width ? `${width}px` : ''; const widthPx = width ? `${width}px` : '';
elementRef.current.style.width = widthPx; elementRef.current.style.width = widthPx;
if (cssPropertyName) { if (cssPropertyName) {
elementRef.current.style.setProperty(cssPropertyName, widthPx); elementRef.current.style.setProperty(cssPropertyName, widthPx);
} }
});
}, [cssPropertyName, elementRef]); }, [cssPropertyName, elementRef]);
useLayoutEffect(() => { useLayoutEffect(() => {
@ -36,13 +39,17 @@ export function useResize(
}, [cssPropertyName, elementRef, initialWidth, setElementStyle]); }, [cssPropertyName, elementRef, initialWidth, setElementStyle]);
function handleMouseUp() { function handleMouseUp() {
document.body.classList.remove('cursor-ew-resize'); requestMutation(() => {
document.body.classList.remove('cursor-ew-resize');
});
} }
function initResize(e: React.MouseEvent<HTMLElement, MouseEvent>) { function initResize(e: React.MouseEvent<HTMLElement, MouseEvent>) {
e.preventDefault(); e.preventDefault();
document.body.classList.add('cursor-ew-resize'); requestMutation(() => {
document.body.classList.add('cursor-ew-resize');
});
setInitialMouseX(e.clientX); setInitialMouseX(e.clientX);
setInitialElementWidth(elementRef.current!.offsetWidth); setInitialElementWidth(elementRef.current!.offsetWidth);

View File

@ -115,7 +115,7 @@ body.cursor-ew-resize {
display: none; display: none;
position: absolute; position: absolute;
top: 0; top: 0;
right: -0.25rem; left: 0;
bottom: 0; bottom: 0;
width: 0.25rem; width: 0.25rem;
z-index: var(--z-resize-handle); z-index: var(--z-resize-handle);