feat: duplicate a block

This commit is contained in:
abdou6666 2025-02-11 11:18:58 +01:00
parent 8798dfa64b
commit b5287b1da9
3 changed files with 58 additions and 5 deletions

View File

@ -113,7 +113,8 @@
"invalid_file_type": "Invalid file type. Please select a file in the supported format.",
"select_category": "Select a flow",
"logout_failed": "Something went wrong during logout",
"duplicate_labels_not_allowed": "Duplicate labels are not allowed"
"duplicate_labels_not_allowed": "Duplicate labels are not allowed",
"duplicate_block_error": "Something went wrong while duplicating block"
},
"menu": {
"terms": "Terms of Use",

View File

@ -113,7 +113,8 @@
"invalid_file_type": "Type de fichier invalide. Veuillez choisir un fichier dans un format pris en charge.",
"select_category": "Sélectionner une catégorie",
"logout_failed": "Une erreur s'est produite lors de la déconnexion",
"duplicate_labels_not_allowed": "Les étiquettes en double ne sont pas autorisées"
"duplicate_labels_not_allowed": "Les étiquettes en double ne sont pas autorisées",
"duplicate_block_error": "Une erreur est survenue lors de la duplication du bloc"
},
"menu": {
"terms": "Conditions d'utilisation",

View File

@ -1,12 +1,13 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { Add, MoveUp } from "@mui/icons-material";
import { Add, ContentCopyRounded, MoveUp } from "@mui/icons-material";
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import FitScreenIcon from "@mui/icons-material/FitScreen";
@ -28,6 +29,7 @@ import {
DiagramEngine,
DiagramModel,
DiagramModelGenerics,
NodeModel,
} from "@projectstorm/react-diagrams";
import { useRouter } from "next/router";
import { SyntheticEvent, useCallback, useEffect, useState } from "react";
@ -37,6 +39,7 @@ import { ConfirmDialogBody } from "@/app-components/dialogs";
import { CategoryFormDialog } from "@/components/categories/CategoryFormDialog";
import { BlockMoveFormDialog } from "@/components/visual-editor/BlockMoveFormDialog";
import { isSameEntity } from "@/hooks/crud/helpers";
import { useCreate } from "@/hooks/crud/useCreate";
import { useDeleteFromCache } from "@/hooks/crud/useDelete";
import { useDeleteMany } from "@/hooks/crud/useDeleteMany";
import { useFind } from "@/hooks/crud/useFind";
@ -46,9 +49,10 @@ import { useUpdateMany } from "@/hooks/crud/useUpdateMany";
import useDebouncedUpdate from "@/hooks/useDebouncedUpdate";
import { useDialogs } from "@/hooks/useDialogs";
import { useSearch } from "@/hooks/useSearch";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { EntityType, Format, QueryType, RouterType } from "@/services/types";
import { IBlock } from "@/types/block.types";
import { IBlock, IBlockStub } from "@/types/block.types";
import { BlockPorts } from "@/types/visual-editor.types";
import { BlockEditFormDialog } from "../BlockEditFormDialog";
@ -80,6 +84,27 @@ const Diagrams = () => {
const { searchPayload } = useSearch<IBlock>({
$eq: [{ category: selectedCategoryId }],
});
const selectedEntities = engine?.getModel()?.getSelectedEntities();
const selectedLinks = (selectedEntities || []).filter(
(entity) => entity instanceof AdvancedLinkModel,
);
const selectedBlocks = (selectedEntities || []).filter(
(entity) => entity instanceof NodeModel,
);
const { toast } = useToast();
const { mutate: duplicateBlock, isLoading: isDuplicatingBlock } = useCreate(
EntityType.BLOCK,
{
onError: () => {
toast.error(t("message.duplicate_block_error"));
},
},
);
const shouldDisableDuplicateButton =
selectedLinks.length >= 1 ||
selectedBlocks.length > 1 ||
selectedBlocks.length === 0 ||
isDuplicatingBlock;
const { data: categories } = useFind(
{ entity: EntityType.CATEGORY },
{
@ -172,6 +197,23 @@ const Diagrams = () => {
enabled: !!selectedCategoryId,
},
);
const handleDuplicateBlock = () => {
const selectedBlock = selectedBlocks[0] as any;
const block = getBlockFromCache(selectedBlock.options.id) as IBlockStub;
if (!block) {
return;
}
duplicateBlock({
...block,
name: `${block.name} (Copy)`,
position: {
x: block.position.x + 100,
y: block.position.y + 100,
},
});
};
useEffect(() => {
// Case when categories are already cached
@ -670,6 +712,15 @@ const Diagrams = () => {
>
{t("button.move")}
</Button>
<Button
size="small"
variant="contained"
startIcon={<ContentCopyRounded />}
onClick={handleDuplicateBlock}
disabled={shouldDisableDuplicateButton}
>
{t("button.duplicate")}
</Button>
<Button
sx={{}}
size="small"