mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-15 05:48:04 -05:00
feat(ui): delete confirmation for videos
This commit is contained in:
committed by
Mary Hipp Rogers
parent
82893804ff
commit
6972cd708d
@@ -365,6 +365,9 @@
|
||||
"deleteImage_one": "Delete Image",
|
||||
"deleteImage_other": "Delete {{count}} Images",
|
||||
"deleteImagePermanent": "Deleted images cannot be restored.",
|
||||
"deleteVideo_one": "Delete Video",
|
||||
"deleteVideo_other": "Delete {{count}} Videos",
|
||||
"deleteVideoPermanent": "Deleted videos cannot be restored.",
|
||||
"displayBoardSearch": "Board Search",
|
||||
"displaySearch": "Image Search",
|
||||
"download": "Download",
|
||||
|
||||
@@ -3,6 +3,7 @@ import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardMo
|
||||
import { CanvasPasteModal } from 'features/controlLayers/components/CanvasPasteModal';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { DeleteImageModal } from 'features/deleteImageModal/components/DeleteImageModal';
|
||||
import { DeleteVideoModal } from 'features/deleteVideoModal/components/DeleteVideoModal';
|
||||
import { FullscreenDropzone } from 'features/dnd/FullscreenDropzone';
|
||||
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
||||
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
||||
@@ -32,6 +33,7 @@ export const GlobalModalIsolator = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<DeleteImageModal />
|
||||
<DeleteVideoModal />
|
||||
<ChangeBoardModal />
|
||||
<DynamicPromptsModal />
|
||||
<StylePresetModal />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useAppStore } from 'app/store/storeHooks';
|
||||
import { useDeleteImageModalApi } from 'features/deleteImageModal/store/state';
|
||||
import { useDeleteVideoModalApi } from 'features/deleteVideoModal/store/state';
|
||||
import { selectSelection } from 'features/gallery/store/gallerySelectors';
|
||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
||||
import { useDeleteCurrentQueueItem } from 'features/queue/hooks/useDeleteCurrentQueueItem';
|
||||
@@ -123,6 +124,8 @@ export const useGlobalHotkeys = () => {
|
||||
});
|
||||
|
||||
const deleteImageModalApi = useDeleteImageModalApi();
|
||||
const deleteVideoModalApi = useDeleteVideoModalApi();
|
||||
|
||||
useRegisteredHotkeys({
|
||||
id: 'deleteSelection',
|
||||
category: 'gallery',
|
||||
@@ -135,7 +138,13 @@ export const useGlobalHotkeys = () => {
|
||||
if (!selection.length) {
|
||||
return;
|
||||
}
|
||||
deleteImageModalApi.delete(selection.map((s) => s.id));
|
||||
if (selection.every(({ type }) => type === 'image')) {
|
||||
deleteImageModalApi.delete(selection.map((s) => s.id));
|
||||
} else if (selection.every(({ type }) => type === 'video')) {
|
||||
deleteVideoModalApi.delete(selection.map((s) => s.id));
|
||||
} else {
|
||||
// no-op, we expect selections to always be only images or only video
|
||||
}
|
||||
},
|
||||
dependencies: [getState, deleteImageModalApi],
|
||||
});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useDeleteVideoModalApi } from 'features/deleteVideoModal/store/state';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { VideoDTO } from 'services/api/types';
|
||||
|
||||
export const useDeleteVideo = (videoDTO?: VideoDTO | null) => {
|
||||
const deleteImageModal = useDeleteVideoModalApi();
|
||||
|
||||
const isEnabled = useMemo(() => {
|
||||
if (!videoDTO) {
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
}, [videoDTO]);
|
||||
const _delete = useCallback(() => {
|
||||
if (!videoDTO) {
|
||||
return;
|
||||
}
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
deleteImageModal.delete([videoDTO.video_id]);
|
||||
}, [deleteImageModal, videoDTO, isEnabled]);
|
||||
|
||||
return {
|
||||
delete: _delete,
|
||||
isEnabled,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { IconButtonProps } from '@invoke-ai/ui-library';
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
import { $isConnected } from 'services/events/stores';
|
||||
|
||||
type Props = Omit<IconButtonProps, 'aria-label'> & {
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const DeleteVideoButton = memo((props: Props) => {
|
||||
const { onClick, isDisabled } = props;
|
||||
const { t } = useTranslation();
|
||||
const isConnected = useStore($isConnected);
|
||||
const count = useAppSelector(selectSelectionCount);
|
||||
const labelMessage: string = `${t('gallery.deleteVideo', { count })} (Del)`;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={onClick}
|
||||
icon={<PiTrashSimpleBold />}
|
||||
tooltip={labelMessage}
|
||||
aria-label={labelMessage}
|
||||
isDisabled={isDisabled || !isConnected}
|
||||
colorScheme="error"
|
||||
variant="link"
|
||||
alignSelf="stretch"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
DeleteVideoButton.displayName = 'DeleteVideoButton';
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ConfirmationAlertDialog, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
|
||||
import { useDeleteVideoModalApi, useDeleteVideoModalState } from 'features/deleteVideoModal/store/state';
|
||||
import { selectSystemShouldConfirmOnDelete, setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const DeleteVideoModal = memo(() => {
|
||||
const state = useDeleteVideoModalState();
|
||||
const api = useDeleteVideoModalApi();
|
||||
const { dispatch } = useAppStore();
|
||||
const { t } = useTranslation();
|
||||
const shouldConfirmOnDelete = useAppSelector(selectSystemShouldConfirmOnDelete);
|
||||
|
||||
const handleChangeShouldConfirmOnDelete = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldConfirmOnDelete(!e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfirmationAlertDialog
|
||||
title={`${t('gallery.deleteVideo', { count: state.video_ids.length })}`}
|
||||
isOpen={state.isOpen}
|
||||
onClose={api.close}
|
||||
cancelButtonText={t('common.cancel')}
|
||||
acceptButtonText={t('common.delete')}
|
||||
acceptCallback={api.confirm}
|
||||
cancelCallback={api.cancel}
|
||||
useInert={false}
|
||||
>
|
||||
<Flex direction="column" gap={3}>
|
||||
<Text>{t('gallery.deleteVideoPermanent')}</Text>
|
||||
<Text>{t('common.areYouSure')}</Text>
|
||||
<FormControl>
|
||||
<FormLabel>{t('common.dontAskMeAgain')}</FormLabel>
|
||||
<Switch isChecked={!shouldConfirmOnDelete} onChange={handleChangeShouldConfirmOnDelete} />
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</ConfirmationAlertDialog>
|
||||
);
|
||||
});
|
||||
DeleteVideoModal.displayName = 'DeleteVideoModal';
|
||||
@@ -0,0 +1,111 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import type { AppStore } from 'app/store/store';
|
||||
import { useAppStore } from 'app/store/storeHooks';
|
||||
import { intersection } from 'es-toolkit/compat';
|
||||
import { selectGetVideoIdsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { itemSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { selectSystemShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
||||
import { atom } from 'nanostores';
|
||||
import { useMemo } from 'react';
|
||||
import { videosApi } from 'services/api/endpoints/videos';
|
||||
|
||||
// Implements an awaitable modal dialog for deleting images
|
||||
|
||||
type DeleteVideosModalState = {
|
||||
video_ids: string[];
|
||||
isOpen: boolean;
|
||||
resolve?: () => void;
|
||||
reject?: (reason?: string) => void;
|
||||
};
|
||||
|
||||
const getInitialState = (): DeleteVideosModalState => ({
|
||||
video_ids: [],
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
const $deleteVideosModalState = atom<DeleteVideosModalState>(getInitialState());
|
||||
|
||||
const deleteVideosWithDialog = async (video_ids: string[], store: AppStore): Promise<void> => {
|
||||
const { getState } = store;
|
||||
const shouldConfirmOnDelete = selectSystemShouldConfirmOnDelete(getState());
|
||||
|
||||
if (!shouldConfirmOnDelete) {
|
||||
// If we don't need to confirm and the resources are not in use, delete them directly
|
||||
await handleDeletions(video_ids, store);
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
$deleteVideosModalState.set({
|
||||
video_ids,
|
||||
isOpen: true,
|
||||
resolve,
|
||||
reject,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeletions = async (video_ids: string[], store: AppStore) => {
|
||||
try {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { data } = videosApi.endpoints.getVideoIds.select(selectGetVideoIdsQueryArgs(state))(state);
|
||||
const index = data?.video_ids.findIndex((id) => id === video_ids[0]);
|
||||
const { deleted_videos } = await dispatch(
|
||||
videosApi.endpoints.deleteVideos.initiate({ video_ids }, { track: false })
|
||||
).unwrap();
|
||||
|
||||
const newVideoIds = data?.video_ids.filter((id) => !deleted_videos.includes(id)) || [];
|
||||
const newSelectedVideoId = newVideoIds[index ?? 0] || null;
|
||||
|
||||
if (
|
||||
intersection(
|
||||
state.gallery.selection.map((s) => s.id),
|
||||
video_ids
|
||||
).length > 0 &&
|
||||
newSelectedVideoId
|
||||
) {
|
||||
// Some selected images were deleted, clear selection
|
||||
dispatch(itemSelected({ type: 'video', id: newSelectedVideoId }));
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDeletion = async (store: AppStore) => {
|
||||
const state = $deleteVideosModalState.get();
|
||||
await handleDeletions(state.video_ids, store);
|
||||
state.resolve?.();
|
||||
closeSilently();
|
||||
};
|
||||
|
||||
const cancelDeletion = () => {
|
||||
const state = $deleteVideosModalState.get();
|
||||
state.reject?.('User canceled');
|
||||
closeSilently();
|
||||
};
|
||||
|
||||
const closeSilently = () => {
|
||||
$deleteVideosModalState.set(getInitialState());
|
||||
};
|
||||
|
||||
export const useDeleteVideoModalState = () => {
|
||||
const state = useStore($deleteVideosModalState);
|
||||
return state;
|
||||
};
|
||||
|
||||
export const useDeleteVideoModalApi = () => {
|
||||
const store = useAppStore();
|
||||
const api = useMemo(
|
||||
() => ({
|
||||
delete: (video_ids: string[]) => deleteVideosWithDialog(video_ids, store),
|
||||
confirm: () => confirmDeletion(store),
|
||||
cancel: cancelDeletion,
|
||||
close: closeSilently,
|
||||
}),
|
||||
[store]
|
||||
);
|
||||
|
||||
return api;
|
||||
};
|
||||
@@ -4,27 +4,22 @@ import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
import { useDeleteVideosMutation } from 'services/api/endpoints/videos';
|
||||
import { isImageDTO } from 'services/api/types';
|
||||
|
||||
export const ContextMenuItemDelete = memo(() => {
|
||||
export const ContextMenuItemDeleteImage = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const deleteImageModal = useDeleteImageModalApi();
|
||||
const [deleteVideos] = useDeleteVideosMutation();
|
||||
const itemDTO = useItemDTOContext();
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
try {
|
||||
if (isImageDTO(itemDTO)) {
|
||||
await deleteImageModal.delete([itemDTO.image_name]);
|
||||
} else {
|
||||
// TODO: Add confirm on delete and video usage functionality
|
||||
await deleteVideos({ video_ids: [itemDTO.video_id] });
|
||||
}
|
||||
} catch {
|
||||
// noop;
|
||||
}
|
||||
}, [deleteImageModal, deleteVideos, itemDTO]);
|
||||
}, [deleteImageModal, itemDTO]);
|
||||
|
||||
return (
|
||||
<IconMenuItem
|
||||
@@ -37,4 +32,4 @@ export const ContextMenuItemDelete = memo(() => {
|
||||
);
|
||||
});
|
||||
|
||||
ContextMenuItemDelete.displayName = 'ContextMenuItemDelete';
|
||||
ContextMenuItemDeleteImage.displayName = 'ContextMenuItemDeleteImage';
|
||||
@@ -0,0 +1,35 @@
|
||||
import { IconMenuItem } from 'common/components/IconMenuItem';
|
||||
import { useDeleteVideoModalApi } from 'features/deleteVideoModal/store/state';
|
||||
import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
import { isVideoDTO } from 'services/api/types';
|
||||
|
||||
export const ContextMenuItemDeleteVideo = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const deleteVideoModal = useDeleteVideoModalApi();
|
||||
const itemDTO = useItemDTOContext();
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
try {
|
||||
if (isVideoDTO(itemDTO)) {
|
||||
await deleteVideoModal.delete([itemDTO.video_id]);
|
||||
}
|
||||
} catch {
|
||||
// noop;
|
||||
}
|
||||
}, [deleteVideoModal, itemDTO]);
|
||||
|
||||
return (
|
||||
<IconMenuItem
|
||||
icon={<PiTrashSimpleBold />}
|
||||
onClickCapture={onClick}
|
||||
aria-label={t('gallery.deleteVideo', { count: 1 })}
|
||||
tooltip={t('gallery.deleteVideo', { count: 1 })}
|
||||
isDestructive
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
ContextMenuItemDeleteVideo.displayName = 'ContextMenuItemDeleteVideo';
|
||||
@@ -3,7 +3,6 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { IconMenuItemGroup } from 'common/components/IconMenuItem';
|
||||
import { ContextMenuItemChangeBoard } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemChangeBoard';
|
||||
import { ContextMenuItemCopy } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemCopy';
|
||||
import { ContextMenuItemDelete } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDelete';
|
||||
import { ContextMenuItemDownload } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDownload';
|
||||
import { ContextMenuItemLoadWorkflow } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLoadWorkflow';
|
||||
import { ContextMenuItemLocateInGalery } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery';
|
||||
@@ -23,6 +22,7 @@ import { ItemDTOContextProvider } from 'features/gallery/contexts/ItemDTOContext
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
import { ContextMenuItemDeleteImage } from './MenuItems/ContextMenuItemDeleteImage';
|
||||
import { ContextMenuItemMetadataRecallActionsUpscaleTab } from './MenuItems/ContextMenuItemMetadataRecallActionsUpscaleTab';
|
||||
|
||||
type SingleSelectionMenuItemsProps = {
|
||||
@@ -40,7 +40,7 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
|
||||
<ContextMenuItemDownload />
|
||||
<ContextMenuItemOpenInViewer />
|
||||
<ContextMenuItemSelectForCompare />
|
||||
<ContextMenuItemDelete />
|
||||
<ContextMenuItemDeleteImage />
|
||||
</IconMenuItemGroup>
|
||||
<MenuDivider />
|
||||
<ContextMenuItemLoadWorkflow />
|
||||
|
||||
@@ -2,7 +2,6 @@ import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { IconMenuItemGroup } from 'common/components/IconMenuItem';
|
||||
import { ContextMenuItemChangeBoard } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemChangeBoard';
|
||||
import { ContextMenuItemCopy } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemCopy';
|
||||
import { ContextMenuItemDelete } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDelete';
|
||||
import { ContextMenuItemDownload } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDownload';
|
||||
import { ContextMenuItemOpenInNewTab } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInNewTab';
|
||||
import { ContextMenuItemOpenInViewer } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInViewer';
|
||||
@@ -10,6 +9,7 @@ import { ContextMenuItemSelectForCompare } from 'features/gallery/components/Con
|
||||
import { ItemDTOContextProvider } from 'features/gallery/contexts/ItemDTOContext';
|
||||
import type { VideoDTO } from 'services/api/types';
|
||||
|
||||
import { ContextMenuItemDeleteVideo } from './MenuItems/ContextMenuItemDeleteVideo';
|
||||
import { ContextMenuItemStarUnstar } from './MenuItems/ContextMenuItemStarUnstar';
|
||||
|
||||
type SingleSelectionVideoMenuItemsProps = {
|
||||
@@ -25,7 +25,7 @@ const SingleSelectionVideoMenuItems = ({ videoDTO }: SingleSelectionVideoMenuIte
|
||||
<ContextMenuItemDownload />
|
||||
<ContextMenuItemOpenInViewer />
|
||||
<ContextMenuItemSelectForCompare />
|
||||
<ContextMenuItemDelete />
|
||||
<ContextMenuItemDeleteVideo />
|
||||
</IconMenuItemGroup>
|
||||
<MenuDivider />
|
||||
<ContextMenuItemStarUnstar />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button, Divider, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
|
||||
import { useDeleteVideo } from 'features/deleteImageModal/hooks/use-delete-video';
|
||||
import { DeleteVideoButton } from 'features/deleteVideoModal/components/DeleteVideoButton';
|
||||
import SingleSelectionVideoMenuItems from 'features/gallery/components/ContextMenu/SingleSelectionVideoMenuItems';
|
||||
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { navigationApi } from 'features/ui/layouts/navigation-api';
|
||||
@@ -12,7 +13,6 @@ import { memo, useCallback, useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCameraBold, PiCrosshairBold, PiDotsThreeOutlineFill, PiSpinnerBold } from 'react-icons/pi';
|
||||
import { useDeleteVideosMutation } from 'services/api/endpoints/videos';
|
||||
import type { VideoDTO } from 'services/api/types';
|
||||
|
||||
export const CurrentVideoButtons = memo(({ videoDTO }: { videoDTO: VideoDTO }) => {
|
||||
@@ -21,7 +21,7 @@ export const CurrentVideoButtons = memo(({ videoDTO }: { videoDTO: VideoDTO }) =
|
||||
const dispatch = useAppDispatch();
|
||||
const activeTab = useAppSelector(selectActiveTab);
|
||||
const galleryPanel = useGalleryPanel(activeTab);
|
||||
const [deleteVideos] = useDeleteVideosMutation();
|
||||
const deleteVideo = useDeleteVideo(videoDTO);
|
||||
|
||||
const captureVideoFrame = useCaptureVideoFrame();
|
||||
const { videoRef } = useVideoViewerContext();
|
||||
@@ -43,10 +43,6 @@ export const CurrentVideoButtons = memo(({ videoDTO }: { videoDTO: VideoDTO }) =
|
||||
});
|
||||
}, [dispatch, galleryPanel, videoDTO]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
deleteVideos({ video_ids: [videoDTO.video_id] });
|
||||
}, [deleteVideos, videoDTO]);
|
||||
|
||||
const onClickSaveFrame = useCallback(async () => {
|
||||
setCapturing(true);
|
||||
await captureVideoFrame(videoRef.current);
|
||||
@@ -98,20 +94,21 @@ export const CurrentVideoButtons = memo(({ videoDTO }: { videoDTO: VideoDTO }) =
|
||||
<Divider orientation="vertical" h={8} mx={2} />
|
||||
|
||||
{doesTabHaveGallery && (
|
||||
<IconButton
|
||||
icon={<PiCrosshairBold />}
|
||||
aria-label={t('boards.locateInGalery')}
|
||||
tooltip={t('boards.locateInGalery')}
|
||||
onClick={locateInGallery}
|
||||
variant="link"
|
||||
size="sm"
|
||||
alignSelf="stretch"
|
||||
/>
|
||||
<>
|
||||
<IconButton
|
||||
icon={<PiCrosshairBold />}
|
||||
aria-label={t('boards.locateInGalery')}
|
||||
tooltip={t('boards.locateInGalery')}
|
||||
onClick={locateInGallery}
|
||||
variant="link"
|
||||
size="sm"
|
||||
alignSelf="stretch"
|
||||
/>
|
||||
<Divider orientation="vertical" h={8} mx={2} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider orientation="vertical" h={8} mx={2} />
|
||||
|
||||
<DeleteImageButton onClick={handleDelete} />
|
||||
<DeleteVideoButton onClick={deleteVideo.delete} isDisabled={!deleteVideo.isEnabled} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user