diff --git a/rsconcept/frontend/src/features/oss/backend/api.ts b/rsconcept/frontend/src/features/oss/backend/api.ts index 3c7d31a7..3add0b3d 100644 --- a/rsconcept/frontend/src/features/oss/backend/api.ts +++ b/rsconcept/frontend/src/features/oss/backend/api.ts @@ -79,7 +79,7 @@ export const ossApi = { successMessage: infoMsg.changesSaved } }), - deleteBlock: ({ itemID, data }: { itemID: number; data: IDeleteBlockDTO }) => + deleteBlock: ({ itemID, data }: { itemID: number; data: IDeleteBlockDTO; beforeUpdate?: () => void }) => axiosPatch({ schema: schemaOperationSchema, endpoint: `/api/oss/${itemID}/delete-block`, @@ -101,7 +101,7 @@ export const ossApi = { } } }), - deleteReference: ({ itemID, data }: { itemID: number; data: IDeleteReferenceDTO }) => + deleteReference: ({ itemID, data }: { itemID: number; data: IDeleteReferenceDTO; beforeUpdate?: () => void }) => axiosPatch({ schema: schemaOperationSchema, endpoint: `/api/oss/${itemID}/delete-reference`, @@ -168,7 +168,7 @@ export const ossApi = { successMessage: infoMsg.changesSaved } }), - deleteOperation: ({ itemID, data }: { itemID: number; data: IDeleteOperationDTO }) => + deleteOperation: ({ itemID, data }: { itemID: number; data: IDeleteOperationDTO; beforeUpdate?: () => void }) => axiosPatch({ schema: schemaOperationSchema, endpoint: `/api/oss/${itemID}/delete-operation`, diff --git a/rsconcept/frontend/src/features/oss/backend/use-delete-block.ts b/rsconcept/frontend/src/features/oss/backend/use-delete-block.ts index c64e89c9..22fa3339 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-delete-block.ts +++ b/rsconcept/frontend/src/features/oss/backend/use-delete-block.ts @@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp'; import { KEYS } from '@/backend/configuration'; +import { PARAMETER } from '@/utils/constants'; import { ossApi } from './api'; import { type IDeleteBlockDTO } from './types'; @@ -13,7 +14,11 @@ export const useDeleteBlock = () => { const mutation = useMutation({ mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-block'], mutationFn: ossApi.deleteBlock, - onSuccess: async data => { + onSuccess: async (data, variables) => { + if (variables.beforeUpdate) { + variables.beforeUpdate(); + await new Promise(resolve => setTimeout(resolve, PARAMETER.minimalTimeout)); + } updateTimestamp(data.id, data.time_update); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); await Promise.allSettled([ @@ -24,6 +29,8 @@ export const useDeleteBlock = () => { onError: () => client.invalidateQueries() }); return { - deleteBlock: (data: { itemID: number; data: IDeleteBlockDTO }) => mutation.mutateAsync(data) + deleteBlock: (data: { itemID: number; data: IDeleteBlockDTO; beforeUpdate?: () => void }) => { + mutation.mutate(data); + } }; }; diff --git a/rsconcept/frontend/src/features/oss/backend/use-delete-operation.ts b/rsconcept/frontend/src/features/oss/backend/use-delete-operation.ts index b8ac77b6..0f54f3ff 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-delete-operation.ts +++ b/rsconcept/frontend/src/features/oss/backend/use-delete-operation.ts @@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp'; import { KEYS } from '@/backend/configuration'; +import { PARAMETER } from '@/utils/constants'; import { ossApi } from './api'; import { type IDeleteOperationDTO } from './types'; @@ -13,7 +14,11 @@ export const useDeleteOperation = () => { const mutation = useMutation({ mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-operation'], mutationFn: ossApi.deleteOperation, - onSuccess: async data => { + onSuccess: async (data, variables) => { + if (variables.beforeUpdate) { + variables.beforeUpdate(); + await new Promise(resolve => setTimeout(resolve, PARAMETER.minimalTimeout)); + } updateTimestamp(data.id, data.time_update); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); await Promise.allSettled([ @@ -24,6 +29,8 @@ export const useDeleteOperation = () => { onError: () => client.invalidateQueries() }); return { - deleteOperation: (data: { itemID: number; data: IDeleteOperationDTO }) => mutation.mutateAsync(data) + deleteOperation: (data: { itemID: number; data: IDeleteOperationDTO; beforeUpdate?: () => void }) => { + mutation.mutate(data); + } }; }; diff --git a/rsconcept/frontend/src/features/oss/backend/use-delete-reference.ts b/rsconcept/frontend/src/features/oss/backend/use-delete-reference.ts index af0d3ee2..471d2c2a 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-delete-reference.ts +++ b/rsconcept/frontend/src/features/oss/backend/use-delete-reference.ts @@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp'; import { KEYS } from '@/backend/configuration'; +import { PARAMETER } from '@/utils/constants'; import { ossApi } from './api'; import { type IDeleteReferenceDTO } from './types'; @@ -13,7 +14,11 @@ export const useDeleteReference = () => { const mutation = useMutation({ mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-reference'], mutationFn: ossApi.deleteReference, - onSuccess: async data => { + onSuccess: async (data, variables) => { + if (variables.beforeUpdate) { + variables.beforeUpdate(); + await new Promise(resolve => setTimeout(resolve, PARAMETER.minimalTimeout)); + } updateTimestamp(data.id, data.time_update); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); await Promise.allSettled([ @@ -24,6 +29,8 @@ export const useDeleteReference = () => { onError: () => client.invalidateQueries() }); return { - deleteReference: (data: { itemID: number; data: IDeleteReferenceDTO }) => mutation.mutateAsync(data) + deleteReference: (data: { itemID: number; data: IDeleteReferenceDTO; beforeUpdate?: () => void }) => { + mutation.mutate(data); + } }; }; diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx index 19418f2c..489cedc3 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx @@ -17,10 +17,11 @@ export interface DlgDeleteOperationProps { oss: IOperationSchema; target: IOperationInput | IOperationSynthesis; layout: IOssLayout; + beforeDelete?: () => void; } export function DlgDeleteOperation() { - const { oss, target, layout } = useDialogsStore(state => state.props as DlgDeleteOperationProps); + const { oss, target, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteOperationProps); const { deleteOperation } = useDeleteOperation(); const { handleSubmit, control } = useForm({ @@ -34,7 +35,7 @@ export function DlgDeleteOperation() { }); function onSubmit(data: IDeleteOperationDTO) { - return deleteOperation({ itemID: oss.id, data: data }); + return deleteOperation({ itemID: oss.id, data: data, beforeUpdate: beforeDelete }); } return ( diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-reference.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-reference.tsx index 3b7ab864..72e8e20e 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-reference.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-reference.tsx @@ -17,10 +17,11 @@ export interface DlgDeleteReferenceProps { oss: IOperationSchema; target: IOperationReference; layout: IOssLayout; + beforeDelete?: () => void; } export function DlgDeleteReference() { - const { oss, target, layout } = useDialogsStore(state => state.props as DlgDeleteReferenceProps); + const { oss, target, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteReferenceProps); const { deleteReference } = useDeleteReference(); const { handleSubmit, control } = useForm({ @@ -35,7 +36,7 @@ export function DlgDeleteReference() { const keep_connections = useWatch({ control, name: 'keep_connections' }); function onSubmit(data: IDeleteReferenceDTO) { - return deleteReference({ itemID: oss.id, data: data }); + return deleteReference({ itemID: oss.id, data: data, beforeUpdate: beforeDelete }); } return ( diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-operation.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-operation.tsx index 465dfaf3..e9197db1 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-operation.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-operation.tsx @@ -38,7 +38,7 @@ interface MenuOperationProps { export function MenuOperation({ operation, onHide }: MenuOperationProps) { const { items: libraryItems } = useLibrary(); - const { schema, setSelected, navigateOperationSchema, isMutable, canDeleteOperation } = useOssEdit(); + const { schema, setSelected, navigateOperationSchema, isMutable, canDeleteOperation, deselectAll } = useOssEdit(); const isProcessing = useMutatingOss(); const getLayout = useGetLayout(); @@ -115,7 +115,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) { showDeleteReference({ oss: schema, target: operation, - layout: getLayout() + layout: getLayout(), + beforeDelete: deselectAll }); break; case OperationType.INPUT: @@ -123,7 +124,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) { showDeleteOperation({ oss: schema, target: operation, - layout: getLayout() + layout: getLayout(), + beforeDelete: deselectAll }); } } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow-state.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow-state.tsx index 3d15bed5..0aa9b494 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow-state.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow-state.tsx @@ -26,6 +26,7 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => { const [containMovement, setContainMovement] = useState(false); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const prevSelected = useRef([]); function onSelectionChange({ nodes }: { nodes: Node[] }) { const ids = nodes.map(node => node.id); @@ -39,19 +40,21 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => { onChange: onSelectionChange }); - const resetGraph = useCallback(() => { + const reloadData = useCallback(() => { const newNodes: Node[] = schema.hierarchy.topologicalOrder().map(nodeID => { const item = schema.itemByNodeID.get(nodeID)!; if (item.nodeType === NodeType.BLOCK) { return { id: nodeID, type: 'block', + selected: prevSelected.current.includes(item.nodeID), data: { label: item.title, block: item }, position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent), style: { width: item.width, height: item.height }, + parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined, zIndex: Z_BLOCK }; @@ -59,6 +62,7 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => { return { id: item.nodeID, type: item.operation_type.toString(), + selected: prevSelected.current.includes(item.nodeID), data: { label: item.alias, operation: item }, position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent), parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined, @@ -83,19 +87,23 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => { setNodes(newNodes); setEdges(newEdges); - - setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout); - }, [schema, setNodes, setEdges, edgeAnimate, edgeStraight, fitView]); + }, [schema, setNodes, setEdges, edgeAnimate, edgeStraight]); useEffect(() => { - resetGraph(); - }, [schema, edgeAnimate, edgeStraight, resetGraph]); + reloadData(); + }, [schema, edgeAnimate, edgeStraight, reloadData]); function resetView() { setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout); } - const prevSelected = useRef([]); + function resetGraph() { + setSelected([]); + prevSelected.current = []; + reloadData(); + setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout); + } + if ( viewportInitialized && (prevSelected.current.length !== selected.length || prevSelected.current.some((id, i) => id !== selected[i])) diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx index 2ada17bd..9ad75ed9 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx @@ -46,8 +46,16 @@ export const flowOptions = { export function OssFlow() { const mainHeight = useMainHeight(); - const { navigateOperationSchema, schema, selected, setSelected, selectedItems, isMutable, canDeleteOperation } = - useOssEdit(); + const { + navigateOperationSchema, + schema, + selected, + setSelected, + selectedItems, + isMutable, + deselectAll, + canDeleteOperation + } = useOssEdit(); const { screenToFlowPosition } = useReactFlow(); const { containMovement, nodes, onNodesChange, edges, onEdgesChange, resetGraph, resetView } = useOssFlow(); const store = useStoreApi(); @@ -157,7 +165,8 @@ export function OssFlow() { showDeleteReference({ oss: schema, target: item, - layout: getLayout() + layout: getLayout(), + beforeDelete: deselectAll }); break; case OperationType.INPUT: @@ -165,14 +174,19 @@ export function OssFlow() { showDeleteOperation({ oss: schema, target: item, - layout: getLayout() + layout: getLayout(), + beforeDelete: deselectAll }); } } else { if (!window.confirm(promptText.deleteBlock)) { return; } - void deleteBlock({ itemID: schema.id, data: { target: item.id, layout: getLayout() } }); + void deleteBlock({ + itemID: schema.id, + data: { target: item.id, layout: getLayout() }, + beforeUpdate: deselectAll + }); } }