up 25/09
This commit is contained in:
@@ -759,3 +759,63 @@ foreignObject {
|
||||
font-weight: 600;
|
||||
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,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
useStoreApi,
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import '../assets/css/style.css';
|
||||
@@ -18,6 +19,7 @@ import ShowPopup from './ShowPopup.tsx';
|
||||
import { nanoid } from 'nanoid';
|
||||
import CustomEdge from './CustomEdge';
|
||||
import MenuFlow from './MenuFlow';
|
||||
import DeleteNodeModal from './popup/DeleteNodeModal.tsx';
|
||||
|
||||
// Nội dung cho mỗi node
|
||||
const nodeContents = {
|
||||
@@ -53,8 +55,8 @@ const nodeContents = {
|
||||
};
|
||||
|
||||
const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
const [nodes, setNodes] = useState(initialNodes);
|
||||
const [edges, setEdges] = useState(initialEdges);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const [showTabBar, setShowTabBar] = useState(false);
|
||||
const [lastNodeId, setLastNodeId] = useState(nodes[0].id);
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
@@ -64,6 +66,9 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const [showActions, setShowActions] = useState(null);
|
||||
const [showPopupSuccess, setShowPopupSuccess] = useState(false);
|
||||
const store = useStoreApi();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [deleteNodeId, setDeleteNodeId] = useState(null);
|
||||
|
||||
const handleAddNoteClick = () => {
|
||||
setShowTabBar(true);
|
||||
@@ -337,7 +342,7 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
Sửa
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteNode(newNodeId)}
|
||||
onClick={() => handleDeleteNode(newNodeId, type.key)}
|
||||
className="btn-remove"
|
||||
>
|
||||
Xóa
|
||||
@@ -505,8 +510,6 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Updated nodes:', nodes);
|
||||
};
|
||||
|
||||
const onDragOver = useCallback((event) => {
|
||||
@@ -546,15 +549,33 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
|
||||
// Hàm xóa node
|
||||
const handleDeleteNode = useCallback(
|
||||
(nodeId) => {
|
||||
(nodeId, nodeKey) => {
|
||||
// Tìm tất cả các edges có nodeId là source hoặc target
|
||||
const connectedEdges = edges.filter(
|
||||
const allNode = store.getState();
|
||||
const edgeNode = allNode.edges;
|
||||
const Node = allNode.nodes;
|
||||
|
||||
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?'
|
||||
);
|
||||
|
||||
if (!isConfirmed) return;
|
||||
|
||||
// Tìm tất cả các edges có nodeId là source hoặc target
|
||||
const connectedEdges = edgeNode.filter(
|
||||
(edge) => edge.source === nodeId || edge.target === nodeId
|
||||
);
|
||||
|
||||
console.log(connectedEdges);
|
||||
console.log('connectedEdges', connectedEdges);
|
||||
|
||||
// Lấy các nodes đang nối với node bị xóa
|
||||
const sourceNode = connectedEdges.find(
|
||||
(edge) => edge.target === nodeId
|
||||
)?.source;
|
||||
@@ -562,16 +583,28 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
(edge) => edge.source === nodeId
|
||||
)?.target;
|
||||
|
||||
// Cập nhật nodes: xóa node bị xóa
|
||||
setNodes((nds) => nds.filter((node) => node.id !== nodeId));
|
||||
// Xóa node và các node con
|
||||
setNodes((nds) => {
|
||||
const updatedNodes = nds.filter((node) => node.id !== nodeId);
|
||||
|
||||
// Cập nhật edges: xóa các cạnh kết nối với node bị xóa
|
||||
// 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) => [
|
||||
...eds,
|
||||
{
|
||||
@@ -585,6 +618,111 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
[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
|
||||
const handleEditNode = (nodeId: string) => {
|
||||
// Logic chỉnh sửa node
|
||||
@@ -614,6 +752,8 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
panOnScroll
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
onConnect={onConnect}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
>
|
||||
<Controls position="right-bottom" />
|
||||
<Background variant={BackgroundVariant.Lines} color="#ccccc" />
|
||||
@@ -652,6 +792,13 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDeleteModalOpen && (
|
||||
<DeleteNodeModal
|
||||
onConfirm={handleConfirmDelete}
|
||||
onClose={() => setIsDeleteModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</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