Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>React Flow Interface | PixelPulse</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> | |
| <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> | |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/reactflow@11.7.0/dist/umd/react-flow.production.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| margin: 0; | |
| padding: 0; | |
| height: 100vh; | |
| width: 100vw; | |
| overflow: hidden; | |
| } | |
| .react-flow__node { | |
| border-radius: 8px; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| border: 1px solid #e5e7eb; | |
| background: white; | |
| } | |
| .react-flow__node.selected { | |
| box-shadow: 0 0 0 2px #8b5cf6; | |
| } | |
| .sidebar { | |
| transition: all 0.3s ease; | |
| } | |
| .gradient-text { | |
| background: linear-gradient(90deg, #8b5cf6 0%, #ec4899 100%); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <div class="flex h-full"> | |
| <!-- Sidebar --> | |
| <div class="sidebar bg-white border-r border-gray-200 w-64 flex-shrink-0 p-4 flex flex-col"> | |
| <div class="flex items-center mb-6"> | |
| <a href="/" class="text-xl font-bold gradient-text">Flow Builder</a> | |
| </div> | |
| <div class="mb-6"> | |
| <h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-3">Nodes</h3> | |
| <div class="space-y-2"> | |
| <div class="node-item bg-white p-3 rounded border border-gray-200 cursor-move hover:bg-gray-50" | |
| draggable="true" data-type="input"> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 rounded-full bg-green-500 mr-2"></div> | |
| <span class="font-medium">Input Node</span> | |
| </div> | |
| </div> | |
| <div class="node-item bg-white p-3 rounded border border-gray-200 cursor-move hover:bg-gray-50" | |
| draggable="true" data-type="default"> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 rounded-full bg-blue-500 mr-2"></div> | |
| <span class="font-medium">Default Node</span> | |
| </div> | |
| </div> | |
| <div class="node-item bg-white p-3 rounded border border-gray-200 cursor-move hover:bg-gray-50" | |
| draggable="true" data-type="output"> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 rounded-full bg-red-500 mr-2"></div> | |
| <span class="font-medium">Output Node</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="space-y-4 mt-auto"> | |
| <a href="/events.html" class="block text-center bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-4 rounded-md transition"> | |
| Back to Events | |
| </a> | |
| <button class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center"> | |
| <i data-feather="save" class="w-4 h-4 mr-2"></i> | |
| Save Flow | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Flow Area --> | |
| <div id="reactflow-container" class="flex-1 relative"> | |
| <!-- This will be replaced by React Flow --> | |
| </div> | |
| <!-- Controls Panel --> | |
| <div class="sidebar bg-white border-l border-gray-200 w-64 flex-shrink-0 p-4 overflow-y-auto"> | |
| <h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-4">Node Settings</h3> | |
| <div id="settings-panel" class="space-y-4"> | |
| <div class="text-center text-gray-400 py-8"> | |
| <i data-feather="box" class="w-8 h-8 mx-auto mb-2"></i> | |
| <p>Select a node to edit its properties</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script type="text/babel"> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const { useState, useCallback, useRef } = React; | |
| const ReactFlow = window.ReactFlow; | |
| const { useNodesState, useEdgesState, addEdge, MiniMap, Controls, Background, applyNodeChanges, applyEdgeChanges } = ReactFlow; | |
| const initialNodes = [ | |
| { | |
| id: '1', | |
| type: 'input', | |
| data: { label: 'Start' }, | |
| position: { x: 250, y: 100 }, | |
| }, | |
| { | |
| id: '2', | |
| data: { label: 'Process' }, | |
| position: { x: 250, y: 200 }, | |
| }, | |
| { | |
| id: '3', | |
| type: 'output', | |
| data: { label: 'End' }, | |
| position: { x: 250, y: 300 }, | |
| } | |
| ]; | |
| const initialEdges = [ | |
| { id: 'e1-2', source: '1', target: '2' }, | |
| { id: 'e2-3', source: '2', target: '3' } | |
| ]; | |
| const FlowBuilder = () => { | |
| const reactFlowWrapper = useRef(null); | |
| const [nodes, setNodes] = useState(initialNodes); | |
| const [edges, setEdges] = useState(initialEdges); | |
| const [reactFlowInstance, setReactFlowInstance] = useState(null); | |
| const [selectedNode, setSelectedNode] = useState(null); | |
| const onNodesChange = useCallback( | |
| (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), | |
| [] | |
| ); | |
| const onEdgesChange = useCallback( | |
| (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), | |
| [] | |
| ); | |
| const onConnect = useCallback( | |
| (params) => setEdges((eds) => addEdge(params, eds)), | |
| [] | |
| ); | |
| const onDragOver = useCallback((event) => { | |
| event.preventDefault(); | |
| event.dataTransfer.dropEffect = 'move'; | |
| }, []); | |
| const onDrop = useCallback( | |
| (event) => { | |
| event.preventDefault(); | |
| const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); | |
| const type = event.dataTransfer.getData('application/reactflow'); | |
| if (typeof type === 'undefined' || !type) { | |
| return; | |
| } | |
| const position = reactFlowInstance.project({ | |
| x: event.clientX - reactFlowBounds.left, | |
| y: event.clientY - reactFlowBounds.top, | |
| }); | |
| const newNode = { | |
| id: `${Date.now()}`, | |
| type, | |
| position, | |
| data: { label: `${type.charAt(0).toUpperCase() + type.slice(1)} Node` }, | |
| }; | |
| setNodes((nds) => nds.concat(newNode)); | |
| }, | |
| [reactFlowInstance] | |
| ); | |
| const onNodeClick = useCallback((event, node) => { | |
| setSelectedNode(node); | |
| // Update settings panel | |
| const panel = document.getElementById('settings-panel'); | |
| panel.innerHTML = ` | |
| <div> | |
| <h4 class="font-medium text-gray-900 mb-2">${node.data.label}</h4> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Label</label> | |
| <input type="text" value="${node.data.label}" | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Node Type</label> | |
| <select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500"> | |
| <option ${node.type === 'input' ? 'selected' : ''}>Input</option> | |
| <option ${!node.type || node.type === 'default' ? 'selected' : ''}>Default</option> | |
| <option ${node.type === 'output' ? 'selected' : ''}>Output</option> | |
| </select> | |
| </div> | |
| <button class="w-full bg-red-100 text-red-600 py-2 px-4 rounded-md hover:bg-red-200 transition"> | |
| Delete Node | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| }, []); | |
| return ( | |
| <div className="reactflow-wrapper h-full w-full" ref={reactFlowWrapper}> | |
| <ReactFlow | |
| nodes={nodes} | |
| edges={edges} | |
| onNodesChange={onNodesChange} | |
| onEdgesChange={onEdgesChange} | |
| onConnect={onConnect} | |
| onInit={setReactFlowInstance} | |
| onDrop={onDrop} | |
| onDragOver={onDragOver} | |
| onNodeClick={onNodeClick} | |
| fitView | |
| > | |
| <Controls /> | |
| <MiniMap /> | |
| <Background variant="dots" gap={12} size={1} /> | |
| </ReactFlow> | |
| </div> | |
| ); | |
| }; | |
| // Check if the container exists before rendering | |
| const container = document.getElementById('reactflow-container'); | |
| if (container) { | |
| const root = ReactDOM.createRoot(container); | |
| root.render(<FlowBuilder />); | |
| } | |
| }); | |
| </script> | |
| <script> | |
| // Initialize Feather Icons when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', function() { | |
| feather.replace(); | |
| // Make nodes draggable from sidebar | |
| const nodeItems = document.querySelectorAll('.node-item'); | |
| nodeItems.forEach(item => { | |
| item.addEventListener('dragstart', (event) => { | |
| event.dataTransfer.setData('application/reactflow', event.target.getAttribute('data-type')); | |
| event.dataTransfer.effectAllowed = 'move'; | |
| }); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |