Spaces:
Configuration error
Configuration error
| import Custom from "../Nodes/Custom.js"; | |
| import CustomNodeIframe from "../Nodes/Custom.js"; | |
| import '../../css/dist/output.css' | |
| import ReactFlow, { Background, | |
| applyNodeChanges, | |
| ReactFlowProvider, | |
| addEdge, | |
| updateEdge, | |
| applyEdgeChanges, | |
| Controls, | |
| MarkerType | |
| } from 'react-flow-renderer'; | |
| import React ,{ useState, useCallback, useRef, useEffect } from 'react'; | |
| import Navbar from '../Navagation/navbar'; | |
| import CustomEdge from '../Edges/Custom' | |
| import CustomLine from "../Edges/CustomLine.js"; | |
| import { useThemeDetector } from '../../helper/visual' | |
| import {CgMoreVerticalAlt} from 'react-icons/cg' | |
| import {BsFillEraserFill} from 'react-icons/bs' | |
| import {FaRegSave} from 'react-icons/fa' | |
| const NODE = { | |
| custom : CustomNodeIframe, | |
| embed: Custom, | |
| } | |
| const EDGE = { | |
| custom : CustomEdge | |
| } | |
| export default function ReactEnviorment() { | |
| const [theme, setTheme] = useState(useThemeDetector) | |
| const [nodes, setNodes] = useState([]); | |
| const [edges, setEdges] = useState([]) | |
| const [reactFlowInstance, setReactFlowInstance] = useState(null); | |
| const reactFlowWrapper = useRef(null); | |
| const [tool, setTool] = useState(false) | |
| const [showProxmoxForm, setShowProxmoxForm] = useState(false); | |
| const deleteNodeContains = useCallback((id) => setNodes((nds) => nds.filter(n => !n.id.includes(`${id}-`))), [setNodes]); | |
| const deleteEdge = useCallback((id) => setEdges((eds) => eds.filter(e => e.id !== id)), [setEdges]); | |
| const deleteNode = useCallback((id) => setNodes((nds) => nds.filter(n => n.id !== id)), [setNodes]); | |
| useEffect(() => { | |
| const restore = () => { | |
| const flow = JSON.parse(localStorage.getItem('flowkey')); | |
| if(flow){ | |
| const updatedNodes = flow.nodes.map((nds) => ({ ...nds, data: { ...nds.data, delete: deleteNode }})); | |
| const updatedEdges = flow.edges.map((eds) => ({ ...eds, data: { ...eds.data, delete: deleteEdge }})); | |
| setNodes(updatedNodes || []); | |
| setEdges(updatedEdges || []); | |
| } | |
| } | |
| restore(); | |
| },[deleteNode, deleteEdge]); | |
| // const handleAddProxmoxVnc = async ({ vmid, node }) => { | |
| // const response = await fetch('http://localhost:5000/api/proxmox/vnc', { | |
| // method: 'POST', | |
| // headers: { 'Content-Type': 'application/json' }, | |
| // body: JSON.stringify({ vmid, node }), | |
| // }); | |
| // const data = await response.json(); | |
| // // Use data.iframe_src to create a new node in React Flow | |
| // const newNode = { | |
| // id: `proxmox-vnc-${nodes.length + 1}`, | |
| // type: 'custom', | |
| // position: reactFlowInstance.project({ x: 0, y: 0 }), | |
| // data: { label: `Proxmox VM ${vmid}`, url: data.iframe_src }, | |
| // }; | |
| // setNodes((nds) => nds.concat(newNode)); | |
| // }; | |
| // const handleAddEmbed = useCallback((embedData) => { | |
| // const newNode = { | |
| // id: `embed-${nodes.length + 1}`, | |
| // type: 'embed', | |
| // position: reactFlowInstance.project({ x: 0, y: 0 }), // Adjust position as needed | |
| // data: { url: embedData.url, width: embedData.width || '100%', height: embedData.height || '400px' }, | |
| // }; | |
| // setNodes((nds) => nds.concat(newNode)); | |
| // console.log(`Adding embed with URL: ${embedData.url} and Label: ${embedData.label}`); | |
| // }, [nodes, reactFlowInstance]); | |
| const onNodesChange = useCallback( | |
| (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), | |
| [setNodes] | |
| ); | |
| const onEdgesChange = useCallback( | |
| (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), | |
| [setEdges] | |
| ); | |
| const onEdgeUpdate = useCallback( | |
| (oldEdge, newConnection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)), | |
| [setEdges] | |
| ); | |
| const onConnect = useCallback( | |
| (params) => { | |
| setEdges((els) => addEdge({...params, type: "custom", animated : true, style : {stroke : "#00FF4A", strokeWidth : "6"}, markerEnd: {type: MarkerType.ArrowClosed, color : "#00FF4A"}, data : { delete : deleteEdge}}, els)) | |
| fetch("http://localhost:2000/api/append/connection", {method : "POST", mode : 'cors', headers : { 'Content-Type' : 'application/json' }, body: JSON.stringify({"source": params.source, "target" : params.target})}).then( res => { | |
| console.log(res) | |
| }).catch(error => { | |
| console.log(error) | |
| }) | |
| }, | |
| [setEdges, deleteEdge] | |
| ); | |
| const onDragOver = useCallback((event) => { | |
| event.preventDefault(); | |
| event.dataTransfer.dropEffect = 'move'; | |
| }, []); | |
| const onSave = useCallback(() => { | |
| if (reactFlowInstance) { | |
| const flow = reactFlowInstance.toObject(); | |
| alert("The current nodes have been saved into the localstorage πΎ") | |
| localStorage.setItem('flowkey', JSON.stringify(flow)); | |
| var labels = []; | |
| var colour = []; | |
| var emoji = []; | |
| flow.nodes.forEach((node) => { | |
| if (!labels.includes(node.data.label)) { | |
| colour.push(node.data.colour); | |
| emoji.push(node.data.emoji); | |
| labels.push(node.data.label); | |
| } | |
| }); | |
| localStorage.setItem('colour', JSON.stringify(colour)); | |
| localStorage.setItem('emoji', JSON.stringify(emoji)); | |
| } | |
| }, [reactFlowInstance]); | |
| const onErase = useCallback(() => { | |
| const flow = localStorage.getItem("flowkey") | |
| if (reactFlowInstance && flow){ | |
| alert("The current nodes have been erased from the localstorage") | |
| localStorage.removeItem("flowkey") | |
| localStorage.removeItem('colour') | |
| localStorage.removeItem('emoji') | |
| } | |
| },[reactFlowInstance]); | |
| const onDrop = useCallback( | |
| (event) => { | |
| event.preventDefault(); | |
| if(event.dataTransfer.getData('application/reactflow') !== ""){ | |
| const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); | |
| const type = event.dataTransfer.getData('application/reactflow'); | |
| const item = JSON.parse(event.dataTransfer.getData('application/item')); | |
| const style = JSON.parse(event.dataTransfer.getData('application/style')); | |
| if (typeof type === 'undefined' || !type) { | |
| return; | |
| } | |
| const position = reactFlowInstance.project({ | |
| x: event.clientX - reactFlowBounds.left, | |
| y: event.clientY - reactFlowBounds.top, | |
| }); | |
| let newNode; | |
| if (type === 'embed') { | |
| newNode = { | |
| id: `embed-${nodes.length + 1}`, | |
| type: 'embed', | |
| position, | |
| data: { url: item.url, width: item.width, height: item.height }, | |
| }; | |
| } else if (type === 'customProxmox') { | |
| newNode = { | |
| id: `proxmox-vm-${nodes.length + 1}`, | |
| type: 'customProxmox', | |
| position, | |
| data: { vmAddress: item.vmAddress }, | |
| }; | |
| } else { | |
| newNode = { | |
| id: `${item.name}-${nodes.length+1}`, | |
| type, | |
| position, | |
| dragHandle : `#draggable`, | |
| data: { label: `${item.name}`, host : `${item.host}`, colour : `${style.colour}`, emoji : `${style.emoji}`, delete : deleteNode }, | |
| }; | |
| } | |
| if (newNode) { | |
| setNodes((nds) => nds.concat(newNode)); | |
| } | |
| } | |
| }, | |
| [reactFlowInstance, nodes, deleteNode]); | |
| // const addProxmoxVMNode = (vmAddress) => { | |
| // const newNode = { | |
| // id: `proxmox-vm-${nodes.length + 1}`, | |
| // type: 'proxmoxVM', | |
| // position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }, | |
| // data: { vmAddress }, | |
| // }; | |
| // setNodes((nds) => nds.concat(newNode)); | |
| // setShowProxmoxForm(false); | |
| // }; | |
| return ( | |
| <div className={`${theme ? "dark" : ""}`}> | |
| <div className={` absolute text-center ${tool ? "h-[203.3333px]" : "h-[41px]"} overflow-hidden w-[41px] text-4xl top-4 right-5 z-50 cursor-default select-none bg-white dark:bg-stone-900 rounded-full border border-black dark:border-white duration-500`} > | |
| <CgMoreVerticalAlt className={` text-black dark:text-white ${tool ? "-rotate-0 mr-auto ml-auto mt-1" : " rotate-180 mr-auto ml-auto mt-1"} duration-300`} onClick={() => setTool(!tool)}/> | |
| <h1 title={theme ? 'Dark Mode' : 'Light Mode'} className={`p-4 px-1 pb-0 ${tool ? "visible" : "invisible"} text-3xl`} onClick={() => setTheme(!theme)} >{theme ? 'π' : 'βοΈ'}</h1> | |
| <FaRegSave title="Save" className={`mt-6 text-black dark:text-white ${tool ? "visible" : " invisible"} ml-auto mr-auto `} onClick={() => onSave()}/> | |
| <BsFillEraserFill title="Erase" className={`mt-6 text-black dark:text-white ml-auto mr-auto ${tool ? "visible" : " invisible"} `} onClick={() => onErase()}/> | |
| </div> | |
| <div className={`flex h-screen w-screen ${theme ? "dark" : ""} transition-all`}> | |
| <ReactFlowProvider> | |
| <Navbar onDelete={deleteNodeContains} colour={JSON.parse(localStorage.getItem('colour'))} emoji={JSON.parse(localStorage.getItem('emoji'))} nodes={nodes}/> | |
| <div className="h-screen w-screen" ref={reactFlowWrapper}> | |
| <ReactFlow nodes={nodes} edges={edges} nodeTypes={NODE} edgeTypes={EDGE} onNodesChange={onNodesChange} onNodesDelete={deleteNode} onEdgesChange={onEdgesChange} onEdgeUpdate={onEdgeUpdate} onConnect={onConnect} onDragOver={onDragOver} onDrop={onDrop} onInit={setReactFlowInstance} connectionLineComponent={CustomLine} fitView> | |
| <Background variant='dots' size={1} className=" bg-white dark:bg-neutral-800"/> | |
| <Controls/> | |
| </ReactFlow> | |
| </div> | |
| </ReactFlowProvider> | |
| </div> | |
| </div> | |
| ); | |
| } | |