mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactored to just use a new dnd context, got reordering working and fixed flicker
This commit is contained in:
parent
8a147bd6e6
commit
2071972a8c
@ -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);
|
@ -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),
|
||||||
|
@ -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,32 +19,46 @@ 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}>
|
||||||
<Flex position="relative" flexDir="column" alignItems="flex-start" p={1} gap={2} h="full" w="full">
|
<SortableContext items={fields.map((field) => `${field.nodeId}.${field.fieldName}`)}>
|
||||||
{isLoading ? (
|
<Flex position="relative" flexDir="column" alignItems="flex-start" p={1} gap={2} h="full" w="full">
|
||||||
<IAINoContentFallback label={t('nodes.loadingNodes')} icon={null} />
|
{isLoading ? (
|
||||||
) : fields.length ? (
|
<IAINoContentFallback label={t('nodes.loadingNodes')} icon={null} />
|
||||||
fields.map(({ nodeId, fieldName }) => (
|
) : fields.length ? (
|
||||||
<LinearViewField key={`${nodeId}.${fieldName}`} nodeId={nodeId} fieldName={fieldName} />
|
fields.map(({ nodeId, fieldName }) => (
|
||||||
))
|
<LinearViewField key={`${nodeId}.${fieldName}`} nodeId={nodeId} fieldName={fieldName} />
|
||||||
) : (
|
))
|
||||||
<IAINoContentFallback label={t('nodes.noFieldsLinearview')} icon={null} />
|
) : (
|
||||||
)}
|
<IAINoContentFallback label={t('nodes.noFieldsLinearview')} icon={null} />
|
||||||
</Flex>
|
)}
|
||||||
</SortableContext>
|
</Flex>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
</ScrollableContent>
|
</ScrollableContent>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user