Extract useDragDropDataTransfer

This commit is contained in:
Enrico Ros
2024-08-07 12:32:41 -07:00
parent 6a0a76df92
commit 8b9a103fd3
3 changed files with 130 additions and 107 deletions
@@ -73,8 +73,8 @@ import { ComposerTextAreaActions } from './textarea/ComposerTextAreaActions';
import { StatusBar } from '../StatusBar';
import { TokenBadgeMemo } from './tokens/TokenBadge';
import { TokenProgressbarMemo } from './tokens/TokenProgressbar';
import { useComposerDragDrop } from './useComposerDragDrop';
import { useComposerStartupText } from './store-composer';
import { useDragDrop } from './useComposerDragDrop';
const zIndexComposerOverlayMic = 10;
@@ -159,7 +159,7 @@ export function Composer(props: {
const llmAttachmentDraftsCollection = useLLMAttachmentDrafts(attachmentDrafts, props.chatLLM);
// drag/drop
const { dragContainerSx, dragDropComponent, handleDragEnter, handleDragStart } = useDragDrop(!!props.isMobile, attachAppendDataTransfer);
const { dragContainerSx, dropComponent, handleContainerDragEnter, handleContainerDragStart } = useComposerDragDrop(!props.isMobile, attachAppendDataTransfer);
// ai functions
const agiAttachmentPrompts = useAgiAttachmentPrompts(useChatAutoSuggestAttachmentPrompts(), attachmentDrafts);
@@ -572,8 +572,8 @@ export function Composer(props: {
<Grid
container
onDragEnter={handleDragEnter}
onDragStart={handleDragStart}
onDragEnter={handleContainerDragEnter}
onDragStart={handleContainerDragStart}
spacing={{ xs: 1, md: 2 }}
sx={stableGridSx}
>
@@ -877,7 +877,7 @@ export function Composer(props: {
</Grid>
{/* overlay: Drag & Drop*/}
{dragDropComponent}
{dropComponent}
</Grid>
@@ -1,84 +1,15 @@
import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Card, Typography } from '@mui/joy';
import AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded';
import { useDragDropDataTransfer } from '~/common/components/useDragDropDataTransfer';
const zIndexComposerOverlayDrop = 20;
const containerSx: SxProps = {
position: 'relative', /* for Drop overlay */
} as const;
const inactiveCardSx: SxProps = {
display: 'none',
position: 'absolute',
inset: 0,
pointerEvents: 'none',
zIndex: zIndexComposerOverlayDrop,
} as const;
const draggingCardSx: SxProps = {
...inactiveCardSx,
pointerEvents: undefined,
border: '1px dashed',
borderRadius: 'sm',
boxShadow: 'inset 1px 0px 3px -2px var(--joy-palette-success-softColor)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
} as const;
export function useDragDrop(
disabled: boolean,
export function useComposerDragDrop(
enabled: boolean,
onDataTransfer: (dataTransfer: DataTransfer, type: 'paste' | 'drop', isDropOnTextarea: boolean) => Promise<any>,
) {
// state
const [isDragging, setIsDragging] = React.useState(false);
// handlers
const eatDragEvent = React.useCallback((event: React.DragEvent) => {
event.preventDefault();
event.stopPropagation();
}, []);
const handleExtDragEnter = React.useCallback((event: React.DragEvent) => {
const isFromSelf = event.dataTransfer.types.includes('x-app/agi');
if (!isFromSelf) {
eatDragEvent(event);
setIsDragging(true);
}
}, [eatDragEvent]);
const handleExtDragStart = React.useCallback((event: React.DragEvent) => {
event.dataTransfer.setData('x-app/agi', 'do-not-intercept');
}, []);
const handleDragLeave = React.useCallback((event: React.DragEvent) => {
eatDragEvent(event);
setIsDragging(false);
}, [eatDragEvent]);
const handleDragOver = React.useCallback((event: React.DragEvent) => {
eatDragEvent(event);
// this makes sure we don't "transfer" (or move) the item, but we tell the sender we'll copy it
event.dataTransfer.dropEffect = 'copy';
}, [eatDragEvent]);
const handleDrop = React.useCallback(async (event: React.DragEvent) => {
eatDragEvent(event);
setIsDragging(false);
const { dataTransfer } = event;
// drop implementation for the composer
const handleComposerDrop = React.useCallback(async (dataTransfer: DataTransfer) => {
// VSCode: detect failure of dropping from VSCode, details below:
// https://github.com/microsoft/vscode/issues/98629#issuecomment-634475572
@@ -106,34 +37,8 @@ export function useDragDrop(
// textarea drop
void onDataTransfer(dataTransfer, 'drop', true); // fire/forget
}, [eatDragEvent, onDataTransfer]);
}, [onDataTransfer]);
const dropComponent = React.useMemo(() => {
if (disabled) return null;
return (
<Card
color={isDragging ? 'success' : undefined}
variant={isDragging ? 'soft' : undefined}
invertedColors={isDragging}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
sx={isDragging ? draggingCardSx : inactiveCardSx}
>
{isDragging && <AttachFileRoundedIcon sx={{ width: 36, height: 36, pointerEvents: 'none' }} />}
{isDragging && <Typography level='title-sm' sx={{ pointerEvents: 'none' }}>
I will hold on to this for you.
</Typography>}
</Card>
);
}, [handleDragLeave, handleDragOver, handleDrop, isDragging, disabled]);
return {
dragContainerSx: containerSx,
dragDropComponent: dropComponent,
handleDragEnter: handleExtDragEnter,
handleDragStart: handleExtDragStart,
};
return useDragDropDataTransfer(enabled, 'I will hold on to this for you.', handleComposerDrop);
}
@@ -0,0 +1,118 @@
import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Card, Typography } from '@mui/joy';
import AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded';
// constants
const zIndexComposerOverlayDrop = 20;
const EXCLUDE_SELF_TYPE = 'x-app/agi';
// styles
const dragContainerSx: SxProps = {
position: 'relative', /* for Drop overlay */
} as const;
const dropCardInactiveSx: SxProps = {
display: 'none',
position: 'absolute',
inset: 0,
pointerEvents: 'none',
zIndex: zIndexComposerOverlayDrop,
} as const;
const dropCardDraggingCardSx: SxProps = {
...dropCardInactiveSx,
pointerEvents: undefined,
border: '1px dashed',
borderRadius: 'sm',
boxShadow: 'inset 1px 0px 3px -2px var(--joy-palette-success-softColor)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
} as const;
// Drag/Drop that can be used in any component and invokes a DataTransfer callback on success
export function useDragDropDataTransfer(enabled: boolean, dropText: string, onDropCallback: (dataTransfer: DataTransfer) => Promise<any>) {
// state
const [isDragging, setIsDragging] = React.useState(false);
const _eatDragEvent = React.useCallback((event: React.DragEvent) => {
event.preventDefault();
event.stopPropagation();
}, []);
// Container events
const handleContainerDragEnter = React.useCallback((event: React.DragEvent) => {
const isFromSelf = event.dataTransfer.types.includes(EXCLUDE_SELF_TYPE);
if (!isFromSelf) {
_eatDragEvent(event);
setIsDragging(true);
}
}, [_eatDragEvent]);
const handleContainerDragStart = React.useCallback((event: React.DragEvent) => {
event.dataTransfer.setData(EXCLUDE_SELF_TYPE, 'do-not-intercept');
}, []);
// Drop Target events
const _handleDragOver = React.useCallback((event: React.DragEvent) => {
_eatDragEvent(event);
// this makes sure we don't "transfer" (or move) the item, but we tell the sender we'll copy it
event.dataTransfer.dropEffect = 'copy';
}, [_eatDragEvent]);
const _handleDragLeave = React.useCallback((event: React.DragEvent) => {
_eatDragEvent(event);
setIsDragging(false);
}, [_eatDragEvent]);
const _handleDrop = React.useCallback(async (event: React.DragEvent) => {
_eatDragEvent(event);
setIsDragging(false);
await onDropCallback(event.dataTransfer);
}, [_eatDragEvent, onDropCallback]);
const dropComponent = React.useMemo(() => {
if (!enabled) return null;
return (
<Card
color={isDragging ? 'success' : undefined}
variant={isDragging ? 'soft' : undefined}
invertedColors={isDragging}
onDragLeave={_handleDragLeave}
onDragOver={_handleDragOver}
onDrop={_handleDrop}
sx={isDragging ? dropCardDraggingCardSx : dropCardInactiveSx}
>
{isDragging && <AttachFileRoundedIcon sx={{ width: 36, height: 36, pointerEvents: 'none' }} />}
{isDragging && <Typography level='title-sm' sx={{ pointerEvents: 'none' }}>
{dropText}
</Typography>}
</Card>
);
}, [enabled, isDragging, _handleDragLeave, _handleDragOver, _handleDrop, dropText]);
return {
dragContainerSx,
dropComponent,
handleContainerDragEnter,
handleContainerDragStart,
isDragging,
};
}