Left Search: Support drag-n-drop for attachments
This commit is contained in:
parent
3c3e8401db
commit
888e65cf6c
@ -94,9 +94,10 @@ type OwnProps = {
|
||||
isPreview?: boolean;
|
||||
previewMessageId?: number;
|
||||
className?: string;
|
||||
withTags?: boolean;
|
||||
observeIntersection?: ObserveFn;
|
||||
onDragEnter?: (chatId: string) => void;
|
||||
withTags?: boolean;
|
||||
onDragLeave?: NoneToVoidFunction;
|
||||
onReorderAnimationEnd?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
@ -167,6 +168,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
className,
|
||||
isSynced,
|
||||
onDragEnter,
|
||||
onDragLeave,
|
||||
isAccountFrozen,
|
||||
chatFolderIds,
|
||||
orderedFolderIds,
|
||||
@ -408,9 +410,10 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
style={`top: ${offsetTop}px`}
|
||||
ripple={!isForum && !isMobile}
|
||||
contextActions={contextActions}
|
||||
withPortalForMenu
|
||||
onClick={handleClick}
|
||||
onDragEnter={handleDragEnter}
|
||||
withPortalForMenu
|
||||
onDragLeave={onDragLeave}
|
||||
>
|
||||
<div className={buildClassName('status', 'status-clickable')}>
|
||||
<Avatar
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type React from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo, useEffect, useMemo, useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import type { FC } from '@teact';
|
||||
import { memo, useEffect, useMemo, useRef, useState } from '@teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { ApiSession } from '../../../api/types';
|
||||
@ -21,12 +18,12 @@ import {
|
||||
} from '../../../config';
|
||||
import { IS_APP, IS_MAC_OS } from '../../../util/browser/windowEnvironment';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { onDragEnter, onDragLeave } from '../../../util/dragNDropHandlers.ts';
|
||||
import { getOrderKey, getPinnedChatsCount } from '../../../util/folderManager';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
|
||||
import usePeerStoriesPolling from '../../../hooks/polling/usePeerStoriesPolling';
|
||||
import useTopOverscroll from '../../../hooks/scroll/useTopOverscroll';
|
||||
import useDebouncedCallback from '../../../hooks/useDebouncedCallback';
|
||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||
import { useHotkeys } from '../../../hooks/useHotkeys';
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
@ -58,7 +55,6 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
const INTERSECTION_THROTTLE = 200;
|
||||
const DRAG_ENTER_DEBOUNCE = 500;
|
||||
const RESERVED_HOTKEYS = new Set(['9', '0']);
|
||||
|
||||
const ChatList: FC<OwnProps> = ({
|
||||
@ -84,7 +80,6 @@ const ChatList: FC<OwnProps> = ({
|
||||
openLeftColumnContent,
|
||||
} = getActions();
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const shouldIgnoreDragRef = useRef(false);
|
||||
const [unconfirmedSessionHeight, setUnconfirmedSessionHeight] = useState(0);
|
||||
|
||||
const isArchived = folderType === 'archived';
|
||||
@ -183,30 +178,6 @@ const ChatList: FC<OwnProps> = ({
|
||||
openFrozenAccountModal();
|
||||
});
|
||||
|
||||
const handleArchivedDragEnter = useLastCallback(() => {
|
||||
if (shouldIgnoreDragRef.current) {
|
||||
shouldIgnoreDragRef.current = false;
|
||||
return;
|
||||
}
|
||||
handleArchivedClick();
|
||||
});
|
||||
|
||||
const handleDragEnter = useDebouncedCallback((chatId: string) => {
|
||||
if (shouldIgnoreDragRef.current) {
|
||||
shouldIgnoreDragRef.current = false;
|
||||
return;
|
||||
}
|
||||
openChat({ id: chatId, shouldReplaceHistory: true });
|
||||
}, [openChat], DRAG_ENTER_DEBOUNCE, true);
|
||||
|
||||
const handleDragLeave = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
if (x < rect.width || y < rect.y) return;
|
||||
shouldIgnoreDragRef.current = true;
|
||||
});
|
||||
|
||||
const handleShowStoryRibbon = useLastCallback(() => {
|
||||
toggleStoryRibbon({ isShown: true, isArchived });
|
||||
});
|
||||
@ -215,6 +186,18 @@ const ChatList: FC<OwnProps> = ({
|
||||
toggleStoryRibbon({ isShown: false, isArchived });
|
||||
});
|
||||
|
||||
const handleArchivedDragEnter = useLastCallback(() => {
|
||||
onDragEnter(() => {
|
||||
handleArchivedClick();
|
||||
});
|
||||
});
|
||||
|
||||
const handleChatDragEnter = useLastCallback((chatId: string) => {
|
||||
onDragEnter(() => {
|
||||
openChat({ id: chatId, shouldReplaceHistory: true });
|
||||
});
|
||||
});
|
||||
|
||||
useTopOverscroll({
|
||||
containerRef,
|
||||
onOverscroll: handleShowStoryRibbon,
|
||||
@ -245,7 +228,8 @@ const ChatList: FC<OwnProps> = ({
|
||||
onReorderAnimationEnd={onReorderAnimationEnd}
|
||||
offsetTop={offsetTop}
|
||||
observeIntersection={observe}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragEnter={handleChatDragEnter}
|
||||
onDragLeave={onDragLeave}
|
||||
withTags={withTags}
|
||||
/>
|
||||
);
|
||||
@ -262,7 +246,6 @@ const ChatList: FC<OwnProps> = ({
|
||||
withAbsolutePositioning
|
||||
maxHeight={chatsHeight + archiveHeight + frozenNotificationHeight + unconfirmedSessionHeight}
|
||||
onLoadMore={getMore}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
{shouldShowUnconfirmedSessions && (
|
||||
<UnconfirmedSession
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import { memo, useCallback } from '../../../lib/teact/teact';
|
||||
import type { FC } from '@teact';
|
||||
import { memo, useCallback } from '@teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiUser } from '../../../api/types';
|
||||
@ -8,8 +8,13 @@ import { StoryViewerOrigin } from '../../../types';
|
||||
import { UNMUTE_TIMESTAMP } from '../../../config';
|
||||
import { getIsChatMuted } from '../../../global/helpers/notifications';
|
||||
import {
|
||||
selectChat, selectIsChatPinned, selectNotifyDefaults, selectNotifyException, selectUser,
|
||||
selectChat,
|
||||
selectIsChatPinned,
|
||||
selectNotifyDefaults,
|
||||
selectNotifyException,
|
||||
selectUser,
|
||||
} from '../../../global/selectors';
|
||||
import { onDragEnter, onDragLeave } from '../../../util/dragNDropHandlers.ts';
|
||||
import { isUserId } from '../../../util/entities/ids';
|
||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||
|
||||
@ -105,6 +110,14 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
const handleDragEnter = useLastCallback((e) => {
|
||||
e.preventDefault();
|
||||
|
||||
onDragEnter(() => {
|
||||
onClick(chatId);
|
||||
}, true);
|
||||
});
|
||||
|
||||
const buttonRef = useSelectWithEnter(() => {
|
||||
onClick(chatId);
|
||||
});
|
||||
@ -112,9 +125,11 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<ListItem
|
||||
className="chat-item-clickable search-result"
|
||||
onClick={handleClick}
|
||||
contextActions={contextActions}
|
||||
buttonRef={buttonRef}
|
||||
onClick={handleClick}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={onDragLeave}
|
||||
>
|
||||
{isUserId(chatId) ? (
|
||||
<PrivateChatInfo
|
||||
|
||||
@ -37,8 +37,6 @@ type OwnProps = {
|
||||
onWheel?: (e: React.WheelEvent<HTMLDivElement>) => void;
|
||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent<any>) => void;
|
||||
onDragOver?: (e: React.DragEvent<HTMLDivElement>) => void;
|
||||
onDragLeave?: (e: React.DragEvent<HTMLDivElement>) => void;
|
||||
};
|
||||
|
||||
const DEFAULT_LIST_SELECTOR = '.ListItem';
|
||||
@ -69,8 +67,6 @@ const InfiniteScroll: FC<OwnProps> = ({
|
||||
onWheel,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
onDragOver,
|
||||
onDragLeave,
|
||||
}: OwnProps) => {
|
||||
let containerRef = useRef<HTMLDivElement>();
|
||||
if (ref) {
|
||||
@ -270,13 +266,11 @@ const InfiniteScroll: FC<OwnProps> = ({
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={className}
|
||||
onWheel={onWheel}
|
||||
teactFastList={!noFastList && !withAbsolutePositioning}
|
||||
onKeyDown={onKeyDown}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
teactFastList={!noFastList && !withAbsolutePositioning}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
onWheel={onWheel}
|
||||
>
|
||||
{beforeChildren}
|
||||
{withAbsolutePositioning && items?.length ? (
|
||||
|
||||
@ -69,13 +69,14 @@ interface OwnProps {
|
||||
withPortalForMenu?: boolean;
|
||||
menuBubbleClassName?: string;
|
||||
href?: string;
|
||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
nonInteractive?: boolean;
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>, arg?: any) => void;
|
||||
onContextMenu?: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
clickArg?: any;
|
||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
onContextMenu?: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
onSecondaryIconClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onDragEnter?: (e: React.DragEvent<HTMLDivElement>) => void;
|
||||
nonInteractive?: boolean;
|
||||
onDragLeave?: NoneToVoidFunction;
|
||||
}
|
||||
|
||||
const ListItem = ({
|
||||
@ -107,13 +108,14 @@ const ListItem = ({
|
||||
contextActions,
|
||||
withPortalForMenu,
|
||||
href,
|
||||
onMouseDown,
|
||||
nonInteractive,
|
||||
onClick,
|
||||
onContextMenu,
|
||||
clickArg,
|
||||
onMouseDown,
|
||||
onContextMenu,
|
||||
onSecondaryIconClick,
|
||||
onDragEnter,
|
||||
nonInteractive,
|
||||
onDragLeave,
|
||||
}: OwnProps) => {
|
||||
let containerRef = useRef<HTMLDivElement>();
|
||||
if (ref) {
|
||||
@ -229,6 +231,7 @@ const ListItem = ({
|
||||
style={style}
|
||||
onMouseDown={onMouseDown}
|
||||
onDragEnter={onDragEnter}
|
||||
onDragLeave={onDragLeave}
|
||||
>
|
||||
<ButtonElementTag
|
||||
className={buildClassName('ListItem-button', isTouched && 'active', buttonClassName)}
|
||||
|
||||
34
src/util/dragNDropHandlers.ts
Normal file
34
src/util/dragNDropHandlers.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { debounce } from './schedulers.ts';
|
||||
|
||||
const DRAG_ENTER_DEBOUNCE = 50; // Workaround for `dragenter` firing before previous `dragleave`
|
||||
const DRAG_ENTER_ACTION_DELAY = 500;
|
||||
|
||||
let willSkipNext = false;
|
||||
let timeout: number | undefined;
|
||||
|
||||
export const onDragEnter = debounce((cb: NoneToVoidFunction, shouldSkipNext = false) => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
|
||||
if (willSkipNext) {
|
||||
willSkipNext = false;
|
||||
return;
|
||||
}
|
||||
|
||||
timeout = window.setTimeout(() => {
|
||||
if (shouldSkipNext) {
|
||||
willSkipNext = true;
|
||||
}
|
||||
|
||||
cb();
|
||||
}, DRAG_ENTER_ACTION_DELAY);
|
||||
}, DRAG_ENTER_DEBOUNCE, false);
|
||||
|
||||
export function onDragLeave() {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user