up 25/09
This commit is contained in:
@@ -759,3 +759,63 @@ foreignObject {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5); /* Màu đen mờ */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nội dung modal */
|
||||||
|
.modal-content {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Các nút trong modal */
|
||||||
|
.modal-content button {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style cho nút Yes, No, Both */
|
||||||
|
.modal-content button:nth-child(1) {
|
||||||
|
background-color: #28a745; /* Màu xanh cho Yes */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content button:nth-child(2) {
|
||||||
|
background-color: #dc3545; /* Màu đỏ cho No */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content button:nth-child(3) {
|
||||||
|
background-color: #ffc107; /* Màu vàng cho Both */
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nút Cancel */
|
||||||
|
.modal-content button:nth-child(4) {
|
||||||
|
background-color: #6c757d; /* Màu xám cho Cancel */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover effect cho các nút */
|
||||||
|
.modal-content button:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
|
useStoreApi,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import '@xyflow/react/dist/style.css';
|
import '@xyflow/react/dist/style.css';
|
||||||
import '../assets/css/style.css';
|
import '../assets/css/style.css';
|
||||||
@@ -18,6 +19,7 @@ import ShowPopup from './ShowPopup.tsx';
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import CustomEdge from './CustomEdge';
|
import CustomEdge from './CustomEdge';
|
||||||
import MenuFlow from './MenuFlow';
|
import MenuFlow from './MenuFlow';
|
||||||
|
import DeleteNodeModal from './popup/DeleteNodeModal.tsx';
|
||||||
|
|
||||||
// Nội dung cho mỗi node
|
// Nội dung cho mỗi node
|
||||||
const nodeContents = {
|
const nodeContents = {
|
||||||
@@ -53,8 +55,8 @@ const nodeContents = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NodeFlow = ({ initialNodes, initialEdges }) => {
|
const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||||
const [nodes, setNodes] = useState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges] = useState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
const [showTabBar, setShowTabBar] = useState(false);
|
const [showTabBar, setShowTabBar] = useState(false);
|
||||||
const [lastNodeId, setLastNodeId] = useState(nodes[0].id);
|
const [lastNodeId, setLastNodeId] = useState(nodes[0].id);
|
||||||
const [showPopup, setShowPopup] = useState(false);
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
@@ -64,6 +66,9 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
|||||||
const { screenToFlowPosition } = useReactFlow();
|
const { screenToFlowPosition } = useReactFlow();
|
||||||
const [showActions, setShowActions] = useState(null);
|
const [showActions, setShowActions] = useState(null);
|
||||||
const [showPopupSuccess, setShowPopupSuccess] = useState(false);
|
const [showPopupSuccess, setShowPopupSuccess] = useState(false);
|
||||||
|
const store = useStoreApi();
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [deleteNodeId, setDeleteNodeId] = useState(null);
|
||||||
|
|
||||||
const handleAddNoteClick = () => {
|
const handleAddNoteClick = () => {
|
||||||
setShowTabBar(true);
|
setShowTabBar(true);
|
||||||
@@ -337,7 +342,7 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
|||||||
Sửa
|
Sửa
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteNode(newNodeId)}
|
onClick={() => handleDeleteNode(newNodeId, type.key)}
|
||||||
className="btn-remove"
|
className="btn-remove"
|
||||||
>
|
>
|
||||||
Xóa
|
Xóa
|
||||||
@@ -505,8 +510,6 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Updated nodes:', nodes);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragOver = useCallback((event) => {
|
const onDragOver = useCallback((event) => {
|
||||||
@@ -546,32 +549,62 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
|||||||
|
|
||||||
// Hàm xóa node
|
// Hàm xóa node
|
||||||
const handleDeleteNode = useCallback(
|
const handleDeleteNode = useCallback(
|
||||||
(nodeId) => {
|
(nodeId, nodeKey) => {
|
||||||
// Tìm tất cả các edges có nodeId là source hoặc target
|
// Tìm tất cả các edges có nodeId là source hoặc target
|
||||||
const connectedEdges = edges.filter(
|
const allNode = store.getState();
|
||||||
(edge) => edge.source === nodeId || edge.target === nodeId
|
const edgeNode = allNode.edges;
|
||||||
);
|
const Node = allNode.nodes;
|
||||||
|
|
||||||
console.log(connectedEdges);
|
if (nodeKey === 'ifelse') {
|
||||||
|
setDeleteNodeId(nodeId); // Lưu lại ID của node muốn xóa
|
||||||
|
// Hiển thị modal để người dùng chọn nhánh xóa
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
document
|
||||||
|
.querySelector('.actions-' + nodeId)
|
||||||
|
?.classList.remove('staticNode');
|
||||||
|
} else {
|
||||||
|
const isConfirmed = window.confirm(
|
||||||
|
'Bạn có chắc chắn muốn xóa node này không?'
|
||||||
|
);
|
||||||
|
|
||||||
// Lấy các nodes đang nối với node bị xóa
|
if (!isConfirmed) return;
|
||||||
const sourceNode = connectedEdges.find(
|
|
||||||
(edge) => edge.target === nodeId
|
|
||||||
)?.source;
|
|
||||||
const targetNode = connectedEdges.find(
|
|
||||||
(edge) => edge.source === nodeId
|
|
||||||
)?.target;
|
|
||||||
|
|
||||||
// Cập nhật nodes: xóa node bị xóa
|
// Tìm tất cả các edges có nodeId là source hoặc target
|
||||||
setNodes((nds) => nds.filter((node) => node.id !== nodeId));
|
const connectedEdges = edgeNode.filter(
|
||||||
|
(edge) => edge.source === nodeId || edge.target === nodeId
|
||||||
|
);
|
||||||
|
|
||||||
// Cập nhật edges: xóa các cạnh kết nối với node bị xóa
|
console.log('connectedEdges', connectedEdges);
|
||||||
setEdges((eds) =>
|
|
||||||
eds.filter((edge) => edge.source !== nodeId && edge.target !== nodeId)
|
const sourceNode = connectedEdges.find(
|
||||||
);
|
(edge) => edge.target === nodeId
|
||||||
|
)?.source;
|
||||||
|
const targetNode = connectedEdges.find(
|
||||||
|
(edge) => edge.source === nodeId
|
||||||
|
)?.target;
|
||||||
|
|
||||||
|
// Xóa node và các node con
|
||||||
|
setNodes((nds) => {
|
||||||
|
const updatedNodes = nds.filter((node) => node.id !== nodeId);
|
||||||
|
|
||||||
|
// Cập nhật vị trí cho các node còn lại
|
||||||
|
const sortedNodes = updatedNodes.sort(
|
||||||
|
(a, b) => a.position.y - b.position.y
|
||||||
|
);
|
||||||
|
return sortedNodes.map((node, index) => ({
|
||||||
|
...node,
|
||||||
|
position: {
|
||||||
|
...node.position,
|
||||||
|
y: index * 110, // Khoảng cách giữa các node
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Xóa các cạnh kết nối với node bị xóa và các node con
|
||||||
|
setEdges((eds) =>
|
||||||
|
eds.filter((edge) => edge.source !== nodeId && edge.target !== nodeId)
|
||||||
|
);
|
||||||
|
|
||||||
// Nếu có cả sourceNode và targetNode, tạo một edge mới nối 2 node này
|
|
||||||
if (sourceNode && targetNode) {
|
|
||||||
setEdges((eds) => [
|
setEdges((eds) => [
|
||||||
...eds,
|
...eds,
|
||||||
{
|
{
|
||||||
@@ -585,6 +618,111 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
|||||||
[edges, setNodes, setEdges]
|
[edges, setNodes, setEdges]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Hàm xác định và xóa nhánh dựa trên node đã chọn
|
||||||
|
const handleConfirmDeleteBranchNode = (branch) => {
|
||||||
|
const allNode = store.getState();
|
||||||
|
const Node = allNode.nodes;
|
||||||
|
const edgeNode = allNode.edges;
|
||||||
|
|
||||||
|
// Tìm node ifelse
|
||||||
|
const ifelseNode = allNode.nodes.find((node) => node.id === deleteNodeId);
|
||||||
|
|
||||||
|
// Tìm edges của nhánh được chọn (Yes hoặc No)
|
||||||
|
const branchEdges = edgeNode.filter((edge) => edge.data?.label === branch);
|
||||||
|
|
||||||
|
// Lấy ID của tất cả các node trong nhánh dựa trên các edges
|
||||||
|
const branchNodeIds = branchEdges.map((edge) => edge.target);
|
||||||
|
|
||||||
|
// Lấy danh sách các node trong nhánh dựa trên cùng tọa độ x với node được chọn
|
||||||
|
const selectedNode = allNode.nodes.find(
|
||||||
|
(node) => node.id === branchNodeIds[0]
|
||||||
|
);
|
||||||
|
const nodesToDelete = allNode.nodes
|
||||||
|
.filter((node) => node.position.x === selectedNode.position.x) // Lọc các node có cùng tọa độ x với node được chọn
|
||||||
|
.map((node) => node.id); // Lấy ID của các node trong nhánh
|
||||||
|
|
||||||
|
// Bao gồm cả node ifelse trong danh sách node cần xóa
|
||||||
|
nodesToDelete.push(ifelseNode.id);
|
||||||
|
|
||||||
|
// Tìm edges nối đến nhánh còn lại
|
||||||
|
const remainingEdges = edgeNode.filter(
|
||||||
|
(edge) => edge.source === ifelseNode.id && edge.data?.label !== branch
|
||||||
|
);
|
||||||
|
|
||||||
|
const TargetIfelse = edgeNode.filter(
|
||||||
|
(edge) => edge.target === ifelseNode.id
|
||||||
|
);
|
||||||
|
|
||||||
|
const NodeRemaining = allNode.nodes.find(
|
||||||
|
(node) => node.id === remainingEdges[0].target
|
||||||
|
);
|
||||||
|
|
||||||
|
// Xóa tất cả các node trong nhánh
|
||||||
|
setNodes((nds) => {
|
||||||
|
const updatedNodes = nds.filter(
|
||||||
|
(node) => !nodesToDelete.includes(node.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Nếu còn node sau khi xóa
|
||||||
|
if (updatedNodes.length > 0) {
|
||||||
|
// Lấy tất cả các node của nhánh còn lại
|
||||||
|
const nodesToRemaining = allNode.nodes
|
||||||
|
.filter((node) => node.position.x === NodeRemaining.position.x) // Lọc các node có cùng tọa độ x với node được chọn
|
||||||
|
.map((node) => node); // Lấy các node trong nhánh
|
||||||
|
|
||||||
|
// Đặt lại vị trí của tất cả các node còn lại theo thứ tự và khoảng cách đều
|
||||||
|
const updatedRemainingNodes = nodesToRemaining.map((node, index) => ({
|
||||||
|
...node,
|
||||||
|
position: {
|
||||||
|
x: ifelseNode.position.x,
|
||||||
|
y: ifelseNode.position.y + index * 110,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Kết hợp lại với các node khác (nếu có)
|
||||||
|
return updatedNodes.map((node) => {
|
||||||
|
const updatedNode = updatedRemainingNodes.find(
|
||||||
|
(n) => n.id === node.id
|
||||||
|
);
|
||||||
|
return updatedNode ? updatedNode : node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedNodes; // Trả về danh sách nodes đã được cập nhật
|
||||||
|
});
|
||||||
|
|
||||||
|
// Xóa các edges liên quan đến nhánh được chọn
|
||||||
|
setEdges((eds) => {
|
||||||
|
const updatedEdges = [
|
||||||
|
...eds.filter(
|
||||||
|
(edge) =>
|
||||||
|
!nodesToDelete.includes(edge.source) &&
|
||||||
|
!nodesToDelete.includes(edge.target)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: `edge-${NodeRemaining.id}-${TargetIfelse[0].source}`,
|
||||||
|
source: NodeRemaining.id,
|
||||||
|
target: TargetIfelse[0].source,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return updatedEdges;
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsDeleteModalOpen(false); // Đóng modal
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hàm xử lý xác nhận xóa
|
||||||
|
const handleConfirmDelete = (choice) => {
|
||||||
|
if (choice === 'yes') {
|
||||||
|
handleConfirmDeleteBranchNode('Yes');
|
||||||
|
} else if (choice === 'no') {
|
||||||
|
handleConfirmDeleteBranchNode('No');
|
||||||
|
} else if (choice === 'both') {
|
||||||
|
handleConfirmDeleteBranchNode('Yes');
|
||||||
|
handleConfirmDeleteBranchNode('No');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Hàm sửa node
|
// Hàm sửa node
|
||||||
const handleEditNode = (nodeId: string) => {
|
const handleEditNode = (nodeId: string) => {
|
||||||
// Logic chỉnh sửa node
|
// Logic chỉnh sửa node
|
||||||
@@ -614,6 +752,8 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
|||||||
panOnScroll
|
panOnScroll
|
||||||
defaultEdgeOptions={defaultEdgeOptions}
|
defaultEdgeOptions={defaultEdgeOptions}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
>
|
>
|
||||||
<Controls position="right-bottom" />
|
<Controls position="right-bottom" />
|
||||||
<Background variant={BackgroundVariant.Lines} color="#ccccc" />
|
<Background variant={BackgroundVariant.Lines} color="#ccccc" />
|
||||||
@@ -652,6 +792,13 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isDeleteModalOpen && (
|
||||||
|
<DeleteNodeModal
|
||||||
|
onConfirm={handleConfirmDelete}
|
||||||
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
17
src/nodes/popup/DeleteNodeModal.tsx
Normal file
17
src/nodes/popup/DeleteNodeModal.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
const DeleteNodeModal = ({ onConfirm, onClose }) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-overlay">
|
||||||
|
<div className="modal-content">
|
||||||
|
<h3>Bạn muốn xóa nhánh nào?</h3>
|
||||||
|
<button onClick={() => onConfirm('yes')}>Yes</button>
|
||||||
|
<button onClick={() => onConfirm('no')}>No</button>
|
||||||
|
<button onClick={() => onConfirm('both')}>Both</button>
|
||||||
|
<button onClick={() => onClose()}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteNodeModal;
|
||||||
Reference in New Issue
Block a user