mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-15 06:18:03 -05:00
feat(ui): video dnd
This commit is contained in:
committed by
Mary Hipp Rogers
parent
84dc4e4ea9
commit
f5fdba795a
@@ -1219,6 +1219,10 @@
|
||||
"height": "Height",
|
||||
"imageFit": "Fit Initial Image To Output Size",
|
||||
"images": "Images",
|
||||
"images_withCount_one": "Image",
|
||||
"images_withCount_other": "Images",
|
||||
"videos_withCount_one": "Video",
|
||||
"videos_withCount_other": "Videos",
|
||||
"infillMethod": "Infill Method",
|
||||
"infillColorValue": "Fill Color",
|
||||
"info": "Info",
|
||||
|
||||
@@ -21,7 +21,7 @@ const DndDragPreviewMultipleImage = memo(({ image_names }: { image_names: string
|
||||
borderRadius="base"
|
||||
>
|
||||
<Heading>{image_names.length}</Heading>
|
||||
<Heading size="sm">{t('parameters.images')}</Heading>
|
||||
<Heading size="sm">{t('parameters.images_withCount', { count: image_names.length })}</Heading>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { Param0 } from 'tsafe';
|
||||
|
||||
const DndDragPreviewMultipleVideo = memo(({ ids }: { ids: string[] }) => {
|
||||
const DndDragPreviewMultipleVideo = memo(({ video_ids }: { video_ids: string[] }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex
|
||||
@@ -20,8 +20,8 @@ const DndDragPreviewMultipleVideo = memo(({ ids }: { ids: string[] }) => {
|
||||
bg="base.900"
|
||||
borderRadius="base"
|
||||
>
|
||||
<Heading>{ids.length}</Heading>
|
||||
<Heading size="sm">{t('parameters.videos')}</Heading>
|
||||
<Heading>{video_ids.length}</Heading>
|
||||
<Heading size="sm">{t('parameters.videos_withCount', { count: video_ids.length })}</Heading>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
@@ -31,11 +31,11 @@ DndDragPreviewMultipleVideo.displayName = 'DndDragPreviewMultipleVideo';
|
||||
export type DndDragPreviewMultipleVideoState = {
|
||||
type: 'multiple-video';
|
||||
container: HTMLElement;
|
||||
ids: string[];
|
||||
video_ids: string[];
|
||||
};
|
||||
|
||||
export const createMultipleVideoDragPreview = (arg: DndDragPreviewMultipleVideoState) =>
|
||||
createPortal(<DndDragPreviewMultipleVideo ids={arg.ids} />, arg.container);
|
||||
createPortal(<DndDragPreviewMultipleVideo video_ids={arg.video_ids} />, arg.container);
|
||||
|
||||
type SetMultipleDragPreviewArg = {
|
||||
multipleVideoDndData: MultipleVideoDndSourceData;
|
||||
@@ -51,7 +51,7 @@ export const setMultipleVideoDragPreview = ({
|
||||
const { nativeSetDragImage, source, location } = onGenerateDragPreviewArgs;
|
||||
setCustomNativeDragPreview({
|
||||
render({ container }) {
|
||||
setDragPreviewState({ type: 'multiple-video', container, ids: multipleVideoDndData.payload.ids });
|
||||
setDragPreviewState({ type: 'multiple-video', container, video_ids: multipleVideoDndData.payload.video_ids });
|
||||
return () => setDragPreviewState(null);
|
||||
},
|
||||
nativeSetDragImage,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/el
|
||||
import { chakra, Flex, Text } from '@invoke-ai/ui-library';
|
||||
import type { SingleVideoDndSourceData } from 'features/dnd/dnd';
|
||||
import { DND_IMAGE_DRAG_PREVIEW_SIZE, preserveOffsetOnSourceFallbackCentered } from 'features/dnd/util';
|
||||
import { GalleryVideoPlaceholder } from 'features/gallery/components/ImageGrid/GalleryVideo';
|
||||
import { memo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import type { VideoDTO } from 'services/api/types';
|
||||
@@ -12,14 +13,19 @@ const ChakraImg = chakra('img');
|
||||
|
||||
const DndDragPreviewSingleVideo = memo(({ videoDTO }: { videoDTO: VideoDTO }) => {
|
||||
return (
|
||||
<Flex w={DND_IMAGE_DRAG_PREVIEW_SIZE} h={DND_IMAGE_DRAG_PREVIEW_SIZE} bg="cyan">
|
||||
<Text color="base.900">I AM A VIDEO</Text>
|
||||
<Flex position="relative" w={DND_IMAGE_DRAG_PREVIEW_SIZE} h={DND_IMAGE_DRAG_PREVIEW_SIZE}>
|
||||
<GalleryVideoPlaceholder />
|
||||
<ChakraImg
|
||||
position="absolute"
|
||||
margin="auto"
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
objectFit="contain"
|
||||
borderRadius="base"
|
||||
borderWidth={2}
|
||||
borderColor="invokeBlue.300"
|
||||
borderStyle="solid"
|
||||
cursor="grabbing"
|
||||
src={videoDTO.thumbnail_url}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -9,9 +9,11 @@ import { selectComparisonImages } from 'features/gallery/components/ImageViewer/
|
||||
import type { BoardId } from 'features/gallery/store/types';
|
||||
import {
|
||||
addImagesToBoard,
|
||||
addVideosToBoard,
|
||||
createNewCanvasEntityFromImage,
|
||||
newCanvasFromImage,
|
||||
removeImagesFromBoard,
|
||||
removeVideosFromBoard,
|
||||
replaceCanvasEntityObjectsWithImage,
|
||||
setComparisonImage,
|
||||
setGlobalReferenceImage,
|
||||
@@ -91,7 +93,7 @@ const _multipleVideo = buildTypeAndKey('multiple-video');
|
||||
export type MultipleVideoDndSourceData = DndData<
|
||||
typeof _multipleVideo.type,
|
||||
typeof _multipleVideo.key,
|
||||
{ ids: string[]; board_id: BoardId }
|
||||
{ video_ids: string[]; board_id: BoardId }
|
||||
>;
|
||||
export const multipleVideoDndSource: DndSource<MultipleVideoDndSourceData> = {
|
||||
..._multipleVideo,
|
||||
@@ -473,12 +475,22 @@ export type AddImageToBoardDndTargetData = DndData<
|
||||
>;
|
||||
export const addImageToBoardDndTarget: DndTarget<
|
||||
AddImageToBoardDndTargetData,
|
||||
SingleImageDndSourceData | MultipleImageDndSourceData
|
||||
SingleImageDndSourceData | MultipleImageDndSourceData | SingleVideoDndSourceData | MultipleVideoDndSourceData
|
||||
> = {
|
||||
..._addToBoard,
|
||||
typeGuard: buildTypeGuard(_addToBoard.key),
|
||||
getData: buildGetData(_addToBoard.key, _addToBoard.type),
|
||||
isValid: ({ sourceData, targetData }) => {
|
||||
if (singleVideoDndSource.typeGuard(sourceData)) {
|
||||
const currentBoard = sourceData.payload.videoDTO.board_id ?? 'none';
|
||||
const destinationBoard = targetData.payload.boardId;
|
||||
return currentBoard !== destinationBoard;
|
||||
}
|
||||
if (multipleVideoDndSource.typeGuard(sourceData)) {
|
||||
const currentBoard = sourceData.payload.board_id;
|
||||
const destinationBoard = targetData.payload.boardId;
|
||||
return currentBoard !== destinationBoard;
|
||||
}
|
||||
if (singleImageDndSource.typeGuard(sourceData)) {
|
||||
const currentBoard = sourceData.payload.imageDTO.board_id ?? 'none';
|
||||
const destinationBoard = targetData.payload.boardId;
|
||||
@@ -492,6 +504,18 @@ export const addImageToBoardDndTarget: DndTarget<
|
||||
return false;
|
||||
},
|
||||
handler: ({ sourceData, targetData, dispatch }) => {
|
||||
if (singleVideoDndSource.typeGuard(sourceData)) {
|
||||
const { videoDTO } = sourceData.payload;
|
||||
const { boardId } = targetData.payload;
|
||||
addVideosToBoard({ video_ids: [videoDTO.video_id], boardId, dispatch });
|
||||
}
|
||||
|
||||
if (multipleVideoDndSource.typeGuard(sourceData)) {
|
||||
const { video_ids } = sourceData.payload;
|
||||
const { boardId } = targetData.payload;
|
||||
addVideosToBoard({ video_ids, boardId, dispatch });
|
||||
}
|
||||
|
||||
if (singleImageDndSource.typeGuard(sourceData)) {
|
||||
const { imageDTO } = sourceData.payload;
|
||||
const { boardId } = targetData.payload;
|
||||
@@ -517,7 +541,7 @@ export type RemoveImageFromBoardDndTargetData = DndData<
|
||||
>;
|
||||
export const removeImageFromBoardDndTarget: DndTarget<
|
||||
RemoveImageFromBoardDndTargetData,
|
||||
SingleImageDndSourceData | MultipleImageDndSourceData
|
||||
SingleImageDndSourceData | MultipleImageDndSourceData | SingleVideoDndSourceData | MultipleVideoDndSourceData
|
||||
> = {
|
||||
..._removeFromBoard,
|
||||
typeGuard: buildTypeGuard(_removeFromBoard.key),
|
||||
@@ -533,6 +557,16 @@ export const removeImageFromBoardDndTarget: DndTarget<
|
||||
return currentBoard !== 'none';
|
||||
}
|
||||
|
||||
if (singleVideoDndSource.typeGuard(sourceData)) {
|
||||
const currentBoard = sourceData.payload.videoDTO.board_id ?? 'none';
|
||||
return currentBoard !== 'none';
|
||||
}
|
||||
|
||||
if (multipleVideoDndSource.typeGuard(sourceData)) {
|
||||
const currentBoard = sourceData.payload.board_id;
|
||||
return currentBoard !== 'none';
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
handler: ({ sourceData, dispatch }) => {
|
||||
@@ -545,6 +579,16 @@ export const removeImageFromBoardDndTarget: DndTarget<
|
||||
const { image_names } = sourceData.payload;
|
||||
removeImagesFromBoard({ image_names, dispatch });
|
||||
}
|
||||
|
||||
if (singleVideoDndSource.typeGuard(sourceData)) {
|
||||
const { videoDTO } = sourceData.payload;
|
||||
removeVideosFromBoard({ video_ids: [videoDTO.video_id], dispatch });
|
||||
}
|
||||
|
||||
if (multipleVideoDndSource.typeGuard(sourceData)) {
|
||||
const { video_ids } = sourceData.payload;
|
||||
removeVideosFromBoard({ video_ids, dispatch });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,13 @@ import { logger } from 'app/logging/logger';
|
||||
import { getStore } from 'app/store/nanostores/store';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { dndTargets, multipleImageDndSource, singleImageDndSource } from 'features/dnd/dnd';
|
||||
import {
|
||||
dndTargets,
|
||||
multipleImageDndSource,
|
||||
multipleVideoDndSource,
|
||||
singleImageDndSource,
|
||||
singleVideoDndSource,
|
||||
} from 'features/dnd/dnd';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const log = logger('dnd');
|
||||
@@ -19,7 +25,12 @@ export const useDndMonitor = () => {
|
||||
const sourceData = source.data;
|
||||
|
||||
// Check for allowed sources
|
||||
if (!singleImageDndSource.typeGuard(sourceData) && !multipleImageDndSource.typeGuard(sourceData)) {
|
||||
if (
|
||||
!singleImageDndSource.typeGuard(sourceData) &&
|
||||
!multipleImageDndSource.typeGuard(sourceData) &&
|
||||
!singleVideoDndSource.typeGuard(sourceData) &&
|
||||
!multipleVideoDndSource.typeGuard(sourceData)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ export const GalleryVideo = memo(({ videoDTO }: Props) => {
|
||||
// multi-image drag.
|
||||
if (selection.length > 1 && selection.some((s) => s.id === videoDTO.video_id)) {
|
||||
return multipleVideoDndSource.getData({
|
||||
ids: selection.map((s) => s.id),
|
||||
video_ids: selection.map((s) => s.id),
|
||||
board_id: boardId,
|
||||
});
|
||||
} // Otherwise, initiate a single-image drag
|
||||
@@ -149,7 +149,10 @@ export const GalleryVideo = memo(({ videoDTO }: Props) => {
|
||||
onDragStart: ({ source }) => {
|
||||
// When we start dragging multiple images, set the dragging state to true if the dragged image is part of the
|
||||
// selection. This is called for all drag events.
|
||||
if (multipleVideoDndSource.typeGuard(source.data) && source.data.payload.ids.includes(videoDTO.video_id)) {
|
||||
if (
|
||||
multipleVideoDndSource.typeGuard(source.data) &&
|
||||
source.data.payload.video_ids.includes(videoDTO.video_id)
|
||||
) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { navigationApi } from 'features/ui/layouts/navigation-api';
|
||||
import { WORKSPACE_PANEL_ID } from 'features/ui/layouts/shared';
|
||||
import { imageDTOToFile, imagesApi, uploadImage } from 'services/api/endpoints/images';
|
||||
import { videosApi } from 'services/api/endpoints/videos';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import type { Equals } from 'tsafe';
|
||||
import { assert } from 'tsafe';
|
||||
@@ -318,3 +319,15 @@ export const removeImagesFromBoard = (arg: { image_names: string[]; dispatch: Ap
|
||||
dispatch(imagesApi.endpoints.removeImagesFromBoard.initiate({ image_names }, { track: false }));
|
||||
dispatch(selectionChanged([]));
|
||||
};
|
||||
|
||||
export const addVideosToBoard = (arg: { video_ids: string[]; boardId: BoardId; dispatch: AppDispatch }) => {
|
||||
const { video_ids, boardId, dispatch } = arg;
|
||||
dispatch(videosApi.endpoints.addVideosToBoard.initiate({ video_ids, board_id: boardId }, { track: false }));
|
||||
dispatch(selectionChanged([]));
|
||||
};
|
||||
|
||||
export const removeVideosFromBoard = (arg: { video_ids: string[]; dispatch: AppDispatch }) => {
|
||||
const { video_ids, dispatch } = arg;
|
||||
dispatch(videosApi.endpoints.removeVideosFromBoard.initiate({ video_ids }, { track: false }));
|
||||
dispatch(selectionChanged([]));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user