refactored to just use a new dnd context, got reordering working and fixed flicker

This commit is contained in:
Jennifer Player 2024-02-14 14:20:08 -05:00
parent 8a147bd6e6
commit 2071972a8c
4 changed files with 91 additions and 33 deletions

View File

@ -0,0 +1,45 @@
import { Box } from '@invoke-ai/ui-library';
import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks';
import type { TypesafeDroppableData } from 'features/dnd/types';
import { isValidDrop } from 'features/dnd/util/isValidDrop';
import { AnimatePresence } from 'framer-motion';
import type { ReactNode } from 'react';
import { memo, useRef } from 'react';
import { v4 as uuidv4 } from 'uuid';
import IAIDropOverlay from './IAIDropOverlay';
type IAISortableItemProps = {
dropLabel?: ReactNode;
disabled?: boolean;
data?: TypesafeDroppableData;
};
const IAISortableItem = (props: IAISortableItemProps) => {
const { dropLabel, data, disabled } = props;
const dndId = useRef(uuidv4());
const { isOver, setNodeRef, active } = useDroppableTypesafe({
id: dndId.current,
disabled,
data,
});
return (
<Box
ref={setNodeRef}
position="absolute"
top={0}
insetInlineStart={0}
w="full"
h="full"
pointerEvents={active ? 'auto' : 'none'}
>
<AnimatePresence>
{isValidDrop(data, active) && <IAIDropOverlay isOver={isOver} label={dropLabel} />}
</AnimatePresence>
</Box>
);
};
export default memo(IAISortableItem);

View File

@ -23,11 +23,12 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId); const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId);
const { t } = useTranslation(); const { t } = useTranslation();
const handleRemoveField = useCallback(() => { const handleRemoveField = useCallback(() => {
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName })); dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
}, [dispatch, fieldName, nodeId]); }, [dispatch, fieldName, nodeId]);
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: nodeId }); const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: `${nodeId}.${fieldName}` });
const style = { const style = {
transform: CSS.Transform.toString(transform), transform: CSS.Transform.toString(transform),

View File

@ -1,15 +1,15 @@
import { DndContext } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { arrayMove, SortableContext } from '@dnd-kit/sortable';
import { Box, Flex } from '@invoke-ai/ui-library'; import { Box, Flex } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDroppable from 'common/components/IAIDroppable';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import type { TypesafeDroppableData } from 'features/dnd/types'; import type { DragEndEvent } from 'features/dnd/types';
import LinearViewField from 'features/nodes/components/flow/nodes/Invocation/fields/LinearViewField'; import LinearViewField from 'features/nodes/components/flow/nodes/Invocation/fields/LinearViewField';
import { selectWorkflowSlice } from 'features/nodes/store/workflowSlice'; import { selectWorkflowSlice, workflowExposedFieldsReordered } from 'features/nodes/store/workflowSlice';
import { memo, useMemo } from 'react'; import { FieldIdentifier } from 'features/nodes/types/field';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo'; import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo';
@ -19,20 +19,33 @@ const WorkflowLinearTab = () => {
const fields = useAppSelector(selector); const fields = useAppSelector(selector);
const { isLoading } = useGetOpenAPISchemaQuery(); const { isLoading } = useGetOpenAPISchemaQuery();
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const droppableData = useMemo<TypesafeDroppableData | undefined>( const handleDragEnd = useCallback(
() => ({ (event: DragEndEvent) => {
id: 'current-image', const { active, over } = event;
actionType: 'SET_CURRENT_IMAGE', console.log({ active, over });
}), const fieldsStrings = fields.map((field) => `${field.nodeId}.${field.fieldName}`);
[]
if (over && active.id !== over.id) {
const oldIndex = fieldsStrings.indexOf(active.id as string);
const newIndex = fieldsStrings.indexOf(over.id as string);
const newFields = arrayMove(fieldsStrings, oldIndex, newIndex)
.map((field) => fields.find((obj) => `${obj.nodeId}.${obj.fieldName}` === field))
.filter((field) => field) as FieldIdentifier[];
dispatch(workflowExposedFieldsReordered(newFields));
}
},
[dispatch, fields]
); );
return ( return (
<Box position="relative" w="full" h="full"> <Box position="relative" w="full" h="full">
<IAIDroppable data={droppableData} disabled={false} dropLabel="drop label" />
<ScrollableContent> <ScrollableContent>
<SortableContext items={fields.map((field) => field.nodeId)} strategy={verticalListSortingStrategy}> <DndContext onDragEnd={handleDragEnd}>
<SortableContext items={fields.map((field) => `${field.nodeId}.${field.fieldName}`)}>
<Flex position="relative" flexDir="column" alignItems="flex-start" p={1} gap={2} h="full" w="full"> <Flex position="relative" flexDir="column" alignItems="flex-start" p={1} gap={2} h="full" w="full">
{isLoading ? ( {isLoading ? (
<IAINoContentFallback label={t('nodes.loadingNodes')} icon={null} /> <IAINoContentFallback label={t('nodes.loadingNodes')} icon={null} />
@ -45,6 +58,7 @@ const WorkflowLinearTab = () => {
)} )}
</Flex> </Flex>
</SortableContext> </SortableContext>
</DndContext>
</ScrollableContent> </ScrollableContent>
</Box> </Box>
); );

View File

@ -42,11 +42,8 @@ export const workflowSlice = createSlice({
state.exposedFields = state.exposedFields.filter((field) => !isEqual(field, action.payload)); state.exposedFields = state.exposedFields.filter((field) => !isEqual(field, action.payload));
state.isTouched = true; state.isTouched = true;
}, },
workflowExposedFieldsReordered: (state, action: PayloadAction<string>) => { workflowExposedFieldsReordered: (state, action: PayloadAction<FieldIdentifier[]>) => {
state.exposedFields = action.payload.split(',').map((id) => { state.exposedFields = action.payload;
const [nodeId, fieldName] = id.split('.');
return { nodeId, fieldName };
});
state.isTouched = true; state.isTouched = true;
}, },
workflowNameChanged: (state, action: PayloadAction<string>) => { workflowNameChanged: (state, action: PayloadAction<string>) => {
@ -113,6 +110,7 @@ export const workflowSlice = createSlice({
export const { export const {
workflowExposedFieldAdded, workflowExposedFieldAdded,
workflowExposedFieldRemoved, workflowExposedFieldRemoved,
workflowExposedFieldsReordered,
workflowNameChanged, workflowNameChanged,
workflowCategoryChanged, workflowCategoryChanged,
workflowDescriptionChanged, workflowDescriptionChanged,