From 902596ce7fab22bfc02ac6182c664885076338bb Mon Sep 17 00:00:00 2001 From: hexastack Date: Wed, 23 Oct 2024 15:25:52 +0100 Subject: [PATCH 1/7] feat: move blocks between categories --- frontend/public/locales/en/translation.json | 4 +- frontend/public/locales/fr/translation.json | 4 +- .../src/app-components/dialogs/MoveDialog.tsx | 86 ++++++++++++++++++ .../components/visual-editor/v2/Diagrams.tsx | 87 ++++++++++++++++++- 4 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 frontend/src/app-components/dialogs/MoveDialog.tsx diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 1fd010a7..259cd6a3 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -106,7 +106,8 @@ "no_label_found": "No label found", "code_is_required": "Language code is required", "text_is_required": "Text is required", - "invalid_file_type": "Invalid file type" + "invalid_file_type": "Invalid file type", + "select_category": "Select a flow" }, "menu": { "terms": "Terms of Use", @@ -505,6 +506,7 @@ "rename": "Rename", "duplicate": "Duplicate", "remove": "Remove", + "move": "Move", "remove_permanently": "Remove", "restore": "Restore", "edit": "Edit", diff --git a/frontend/public/locales/fr/translation.json b/frontend/public/locales/fr/translation.json index 275a80ad..8ad387ca 100644 --- a/frontend/public/locales/fr/translation.json +++ b/frontend/public/locales/fr/translation.json @@ -106,7 +106,8 @@ "no_label_found": "Aucune étiquette trouvée", "code_is_required": "Le code est requis", "text_is_required": "Texte requis", - "invalid_file_type": "Type de fichier invalide" + "invalid_file_type": "Type de fichier invalide", + "select_category": "Sélectionner une catégorie" }, "menu": { "terms": "Conditions d'utilisation", @@ -506,6 +507,7 @@ "rename": "Renommer", "duplicate": "Dupliquer", "remove": "Supprimer", + "move": "Déplacer", "remove_permanently": "Supprimer de façon permanente", "restore": "Restaurer", "edit": "Modifier", diff --git a/frontend/src/app-components/dialogs/MoveDialog.tsx b/frontend/src/app-components/dialogs/MoveDialog.tsx new file mode 100644 index 00000000..ea92aa42 --- /dev/null +++ b/frontend/src/app-components/dialogs/MoveDialog.tsx @@ -0,0 +1,86 @@ +/* + * Copyright © 2024 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 { + Button, + Dialog, + DialogActions, + DialogContent, + Grid, + MenuItem, + Select, +} from "@mui/material"; +import { FC, useState } from "react"; + +import { DialogTitle } from "@/app-components/dialogs/DialogTitle"; +import { DialogControl } from "@/hooks/useDialog"; +import { useTranslate } from "@/hooks/useTranslate"; +import { ICategory } from "@/types/category.types"; + +export interface MoveDialogProps extends DialogControl { + categories: ICategory[]; + callback?: (newCategoryId?: string) => Promise; + openDialog: (data?: string) => void; +} + +export const MoveDialog: FC = ({ + open, + callback, + closeDialog, + categories, +}: MoveDialogProps) => { + const { t } = useTranslate(); + const [selectedCategoryId, setSelectedCategoryId] = useState(""); + const handleMove = async () => { + if (selectedCategoryId && callback) { + await callback(selectedCategoryId); + closeDialog(); + } + }; + + return ( + + + {t("message.select_category")} + + + + + + + + + + + + + + ); +}; diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index 370337c2..43957fdb 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -11,6 +11,7 @@ import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import FitScreenIcon from "@mui/icons-material/FitScreen"; import RestartAltIcon from "@mui/icons-material/RestartAlt"; +import MoveIcon from "@mui/icons-material/Swipe"; import ZoomInIcon from "@mui/icons-material/ZoomIn"; import ZoomOutIcon from "@mui/icons-material/ZoomOut"; import { @@ -38,6 +39,7 @@ import { } from "react"; import { DeleteDialog } from "@/app-components/dialogs"; +import { MoveDialog } from "@/app-components/dialogs/MoveDialog"; import { CategoryDialog } from "@/components/categories/CategoryDialog"; import { useDelete, useDeleteFromCache } from "@/hooks/crud/useDelete"; import { useFind } from "@/hooks/crud/useFind"; @@ -67,6 +69,13 @@ const Diagrams = () => { const [canvas, setCanvas] = useState(); const [selectedBlockId, setSelectedBlockId] = useState(); const deleteDialogCtl = useDialog(false); + const moveDialogCtl = useDialog(false); + const { refetch: refetchBlocks } = useFind( + { entity: EntityType.BLOCK, format: Format.FULL }, + { + hasCount: false, + }, + ); const addCategoryDialogCtl = useDialog(false); const { buildDiagram, @@ -174,10 +183,13 @@ const Diagrams = () => { }, []); useEffect(() => { + const filteredBlocks = blocks.filter( + (block) => block.category === selectedCategoryId, + ); const { canvas, model, engine } = buildDiagram({ zoom: currentCategory?.zoom || 100, offset: currentCategory?.offset || [0, 0], - data: blocks, + data: filteredBlocks, setter: setSelectedBlockId, updateFn: updateBlock, onRemoveNode: (ids, next) => { @@ -291,11 +303,15 @@ const Diagrams = () => { zoomUpdated: debouncedZoomEvent, offsetUpdated: debouncedOffsetEvent, }); + refetchBlocks(); }, [ + selectedCategoryId, JSON.stringify( - blocks.map((b) => { - return { ...b, position: undefined, updatedAt: undefined }; - }), + blocks + .filter((b) => b.category === selectedCategoryId) + .map((b) => { + return { ...b, position: undefined, updatedAt: undefined }; + }), ), ]); @@ -316,6 +332,14 @@ const Diagrams = () => { deleteDialogCtl.openDialog(ids); } }; + const handleMoveButton = () => { + const selectedEntities = engine?.getModel().getSelectedEntities(); + const ids = selectedEntities?.map((model) => model.getID()).join(","); + + if (ids && selectedEntities) { + moveDialogCtl.openDialog(ids); + } + }; const onDelete = async () => { const id = deleteDialogCtl?.data; @@ -429,6 +453,45 @@ const Diagrams = () => { deleteDialogCtl.closeDialog(); } }; + const onMove = async (newCategoryId?: string) => { + if (!newCategoryId) { + return; + } + + const id = moveDialogCtl?.data; + + if (id) { + const ids = id.includes(",") ? id.split(",") : [id]; + + for (const blockId of ids) { + const block = getBlockFromCache(blockId); + + await updateBlock( + { + id: blockId, + params: { + category: newCategoryId, + }, + }, + { + onSuccess() { + updateCachedBlock({ + id: blockId, + payload: { + ...block, + category: newCategoryId, + }, + strategy: "overwrite", + }); + }, + }, + ); + } + setSelectedCategoryId(newCategoryId); + setSelectedBlockId(undefined); + moveDialogCtl.closeDialog(); + } + }; return (
{ + { > {t("button.remove")} + Date: Mon, 28 Oct 2024 08:29:53 +0100 Subject: [PATCH 2/7] fix: remove join/split --- .../src/components/visual-editor/v2/Diagrams.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index 43957fdb..e8f83ae2 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -69,7 +69,7 @@ const Diagrams = () => { const [canvas, setCanvas] = useState(); const [selectedBlockId, setSelectedBlockId] = useState(); const deleteDialogCtl = useDialog(false); - const moveDialogCtl = useDialog(false); + const moveDialogCtl = useDialog(false); const { refetch: refetchBlocks } = useFind( { entity: EntityType.BLOCK, format: Format.FULL }, { @@ -334,7 +334,7 @@ const Diagrams = () => { }; const handleMoveButton = () => { const selectedEntities = engine?.getModel().getSelectedEntities(); - const ids = selectedEntities?.map((model) => model.getID()).join(","); + const ids = selectedEntities?.map((model) => model.getID()); if (ids && selectedEntities) { moveDialogCtl.openDialog(ids); @@ -458,11 +458,9 @@ const Diagrams = () => { return; } - const id = moveDialogCtl?.data; - - if (id) { - const ids = id.includes(",") ? id.split(",") : [id]; + const ids = moveDialogCtl?.data; + if (ids) { for (const blockId of ids) { const block = getBlockFromCache(blockId); @@ -487,6 +485,8 @@ const Diagrams = () => { }, ); } + refetchBlocks(); + setSelectedCategoryId(newCategoryId); setSelectedBlockId(undefined); moveDialogCtl.closeDialog(); From 3cef49d4de58c3f8e6247f49f08bc8f977ceadbf Mon Sep 17 00:00:00 2001 From: hexastack Date: Mon, 28 Oct 2024 14:17:07 +0100 Subject: [PATCH 3/7] fix: reorder buttons --- .../components/visual-editor/v2/Diagrams.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index e8f83ae2..0d1fe187 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -639,6 +639,15 @@ const Diagrams = () => { > {t("button.edit")} + - Date: Tue, 29 Oct 2024 14:54:14 +0100 Subject: [PATCH 4/7] fix: remove refetch --- .../components/visual-editor/v2/Diagrams.tsx | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index 0d1fe187..34ae9542 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -37,10 +37,12 @@ import { useRef, useState, } from "react"; +import { useQueryClient } from "react-query"; import { DeleteDialog } from "@/app-components/dialogs"; import { MoveDialog } from "@/app-components/dialogs/MoveDialog"; import { CategoryDialog } from "@/components/categories/CategoryDialog"; +import { isSameEntity } from "@/hooks/crud/helpers"; import { useDelete, useDeleteFromCache } from "@/hooks/crud/useDelete"; import { useFind } from "@/hooks/crud/useFind"; import { useGetFromCache } from "@/hooks/crud/useGet"; @@ -49,7 +51,7 @@ import useDebouncedUpdate from "@/hooks/useDebouncedUpdate"; import { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; import { useSearch } from "@/hooks/useSearch"; import { useTranslate } from "@/hooks/useTranslate"; -import { EntityType, Format } from "@/services/types"; +import { EntityType, Format, QueryType } from "@/services/types"; import { IBlock } from "@/types/block.types"; import { ICategory } from "@/types/category.types"; import { BlockPorts } from "@/types/visual-editor.types"; @@ -70,12 +72,6 @@ const Diagrams = () => { const [selectedBlockId, setSelectedBlockId] = useState(); const deleteDialogCtl = useDialog(false); const moveDialogCtl = useDialog(false); - const { refetch: refetchBlocks } = useFind( - { entity: EntityType.BLOCK, format: Format.FULL }, - { - hasCount: false, - }, - ); const addCategoryDialogCtl = useDialog(false); const { buildDiagram, @@ -153,6 +149,7 @@ const Diagrams = () => { }, [selectedCategoryId, debouncedUpdateCategory], ); + const queryClient = useQueryClient(); const getBlockFromCache = useGetFromCache(EntityType.BLOCK); const updateCachedBlock = useUpdateCache(EntityType.BLOCK); const deleteCachedBlock = useDeleteFromCache(EntityType.BLOCK); @@ -183,13 +180,10 @@ const Diagrams = () => { }, []); useEffect(() => { - const filteredBlocks = blocks.filter( - (block) => block.category === selectedCategoryId, - ); const { canvas, model, engine } = buildDiagram({ zoom: currentCategory?.zoom || 100, offset: currentCategory?.offset || [0, 0], - data: filteredBlocks, + data: blocks, setter: setSelectedBlockId, updateFn: updateBlock, onRemoveNode: (ids, next) => { @@ -303,15 +297,12 @@ const Diagrams = () => { zoomUpdated: debouncedZoomEvent, offsetUpdated: debouncedOffsetEvent, }); - refetchBlocks(); }, [ selectedCategoryId, JSON.stringify( - blocks - .filter((b) => b.category === selectedCategoryId) - .map((b) => { - return { ...b, position: undefined, updatedAt: undefined }; - }), + blocks.map((b) => { + return { ...b, position: undefined, updatedAt: undefined }; + }), ), ]); @@ -485,7 +476,18 @@ const Diagrams = () => { }, ); } - refetchBlocks(); + + queryClient.removeQueries({ + predicate: ({ queryKey }) => { + const [qType, qEntity] = queryKey; + + return ( + (qType === QueryType.collection && + isSameEntity(qEntity, EntityType.BLOCK)) || + isSameEntity(qEntity, EntityType.CATEGORY) + ); + }, + }); setSelectedCategoryId(newCategoryId); setSelectedBlockId(undefined); From ce20bb725acf7150884d884cac054edbe6eafcd0 Mon Sep 17 00:00:00 2001 From: hexastack Date: Thu, 31 Oct 2024 10:39:34 +0100 Subject: [PATCH 5/7] fix: remove unnecessary snippet --- .../components/visual-editor/v2/Diagrams.tsx | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index 34ae9542..37837557 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -453,28 +453,12 @@ const Diagrams = () => { if (ids) { for (const blockId of ids) { - const block = getBlockFromCache(blockId); - - await updateBlock( - { - id: blockId, - params: { - category: newCategoryId, - }, - }, - { - onSuccess() { - updateCachedBlock({ - id: blockId, - payload: { - ...block, - category: newCategoryId, - }, - strategy: "overwrite", - }); - }, + await updateBlock({ + id: blockId, + params: { + category: newCategoryId, }, - ); + }); } queryClient.removeQueries({ From 310d9e47aab6ab2335908d3cf5d4ab7e352e8d04 Mon Sep 17 00:00:00 2001 From: hexastack Date: Thu, 31 Oct 2024 17:06:28 +0100 Subject: [PATCH 6/7] fix: resolve conflicts --- api/src/chat/repositories/block.repository.ts | 16 +++++++++++++++- .../src/components/visual-editor/v2/Diagrams.tsx | 14 +++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/api/src/chat/repositories/block.repository.ts b/api/src/chat/repositories/block.repository.ts index 4ddb9aae..319878d6 100644 --- a/api/src/chat/repositories/block.repository.ts +++ b/api/src/chat/repositories/block.repository.ts @@ -89,12 +89,26 @@ export class BlockRepository extends BaseRepository< Block, 'findOneAndUpdate' >, - _criteria: TFilterQuery, + criteria: TFilterQuery, _updates: | UpdateWithAggregationPipeline | UpdateQuery>, ): Promise { const updates: BlockUpdateDto = _updates?.['$set']; + if (updates?.category) { + const movedBlockId = criteria._id; + + // Find and update blocks that reference the moved block + await this.model.updateMany( + { nextBlocks: movedBlockId }, + { $pull: { nextBlocks: movedBlockId } }, + ); + + await this.model.updateMany( + { attachedBlock: movedBlockId }, + { $set: { attachedBlock: null } }, + ); + } this.checkDeprecatedAttachmentUrl(updates); } diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index 37837557..d8083d43 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -324,7 +324,7 @@ const Diagrams = () => { } }; const handleMoveButton = () => { - const selectedEntities = engine?.getModel().getSelectedEntities(); + const selectedEntities = engine?.getModel().getSelectedEntities().reverse(); const ids = selectedEntities?.map((model) => model.getID()); if (ids && selectedEntities) { @@ -453,10 +453,22 @@ const Diagrams = () => { if (ids) { for (const blockId of ids) { + const block = getBlockFromCache(blockId); + const updatedNextBlocks = block?.nextBlocks?.filter((nextBlockId) => + ids.includes(nextBlockId), + ); + const updatedAttachedBlock = ids.includes( + block?.attachedBlock as string, + ) + ? block?.attachedBlock + : null; + await updateBlock({ id: blockId, params: { category: newCategoryId, + nextBlocks: updatedNextBlocks, + attachedBlock: updatedAttachedBlock, }, }); } From 9a4aa004d2c4491b0af64b69fb5d05213fd43bfb Mon Sep 17 00:00:00 2001 From: hexastack Date: Fri, 1 Nov 2024 10:35:06 +0100 Subject: [PATCH 7/7] fix: predicate --- frontend/src/components/visual-editor/v2/Diagrams.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index d8083d43..bdb0e8a9 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -475,12 +475,14 @@ const Diagrams = () => { queryClient.removeQueries({ predicate: ({ queryKey }) => { - const [qType, qEntity] = queryKey; + const [qType, qEntity, qId] = queryKey; return ( (qType === QueryType.collection && - isSameEntity(qEntity, EntityType.BLOCK)) || - isSameEntity(qEntity, EntityType.CATEGORY) + isSameEntity(qEntity, EntityType.BLOCK) && + qId === selectedCategoryId) || + (isSameEntity(qEntity, EntityType.CATEGORY) && + qId === selectedCategoryId) ); }, });