Spaces:
Configuration error
Configuration error
zack
commited on
Commit
Β·
0563a7e
1
Parent(s):
9bcd0c7
feat: move components directory to frontend root π
Browse files
frontend/components/Edges/Custom.js
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react';
|
2 |
+
import {BiX} from 'react-icons/bi'
|
3 |
+
import { getBezierPath, getEdgeCenter } from 'react-flow-renderer';
|
4 |
+
|
5 |
+
import '../../css/dist/output.css';
|
6 |
+
|
7 |
+
const foreignObjectSize = 40;
|
8 |
+
|
9 |
+
const onEdgeClick = (evt, id) => {
|
10 |
+
evt(id)
|
11 |
+
};
|
12 |
+
|
13 |
+
export default function CustomEdge({
|
14 |
+
id,
|
15 |
+
sourceX,
|
16 |
+
sourceY,
|
17 |
+
targetX,
|
18 |
+
targetY,
|
19 |
+
sourcePosition,
|
20 |
+
targetPosition,
|
21 |
+
markerEnd,
|
22 |
+
data
|
23 |
+
}) {
|
24 |
+
const edgePath = getBezierPath({
|
25 |
+
sourceX,
|
26 |
+
sourceY,
|
27 |
+
sourcePosition,
|
28 |
+
targetX,
|
29 |
+
targetY,
|
30 |
+
targetPosition,
|
31 |
+
});
|
32 |
+
const [edgeCenterX, edgeCenterY] = getEdgeCenter({
|
33 |
+
sourceX,
|
34 |
+
sourceY,
|
35 |
+
targetX,
|
36 |
+
targetY,
|
37 |
+
});
|
38 |
+
|
39 |
+
return (
|
40 |
+
<>
|
41 |
+
<path
|
42 |
+
id={id}
|
43 |
+
style={{stroke : "#00FF4A", strokeWidth : "6"}}
|
44 |
+
className="react-flow__edge-path"
|
45 |
+
d={edgePath}
|
46 |
+
markerEnd={markerEnd}
|
47 |
+
/>
|
48 |
+
<foreignObject
|
49 |
+
width={foreignObjectSize}
|
50 |
+
height={200}
|
51 |
+
x={edgeCenterX - foreignObjectSize / 2}
|
52 |
+
y={edgeCenterY - foreignObjectSize / 2}
|
53 |
+
className="edgebutton-foreignobject"
|
54 |
+
requiredExtensions="http://www.w3.org/1999/xhtml"
|
55 |
+
>
|
56 |
+
<div className=" flex w-10 h-10 dark:bg-black bg-white border-2 rounded-xl hover:shadow-lg text-center duration-200" onClick={() => onEdgeClick(data.delete, id)}>
|
57 |
+
<BiX className=' flex-1 w-9 h-9 text-black dark:text-white'/>
|
58 |
+
</div>
|
59 |
+
</foreignObject>
|
60 |
+
</>
|
61 |
+
);
|
62 |
+
}
|
frontend/components/Edges/CustomLine.js
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react';
|
2 |
+
import { getBezierPath } from 'react-flow-renderer';
|
3 |
+
|
4 |
+
const CustomLine = ({
|
5 |
+
sourceX,
|
6 |
+
sourceY,
|
7 |
+
sourcePosition,
|
8 |
+
targetX,
|
9 |
+
targetY,
|
10 |
+
targetPosition,
|
11 |
+
connectionLineType,
|
12 |
+
connectionLineStyle,
|
13 |
+
}) => {
|
14 |
+
|
15 |
+
const edgePath = getBezierPath({
|
16 |
+
sourceX,
|
17 |
+
sourceY,
|
18 |
+
sourcePosition,
|
19 |
+
targetX,
|
20 |
+
targetY,
|
21 |
+
targetPosition,
|
22 |
+
});
|
23 |
+
return (
|
24 |
+
<g>
|
25 |
+
<path
|
26 |
+
fill="none"
|
27 |
+
stroke="#00FF4A"
|
28 |
+
strokeWidth={7}
|
29 |
+
className="animated"
|
30 |
+
d={edgePath}
|
31 |
+
/>
|
32 |
+
</g>
|
33 |
+
);
|
34 |
+
};
|
35 |
+
|
36 |
+
export default CustomLine;
|
frontend/components/Modal/importer.js
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Modal, Icon, Message} from 'semantic-ui-react'
|
2 |
+
import "../../css/dist/output.css"
|
3 |
+
import {ReactComponent as Gradio} from '../../images/gradio.svg'
|
4 |
+
import {ReactComponent as Streamlit} from '../../images/streamlit.svg'
|
5 |
+
import {ReactComponent as Exit} from '../../images/exit.svg'
|
6 |
+
import { useState } from 'react'
|
7 |
+
import {BsSearch} from 'react-icons/bs';
|
8 |
+
|
9 |
+
export default function Import(props){
|
10 |
+
const [tab, setTab] = useState("gradio")
|
11 |
+
const [subTab, setSubTab] = useState(0)
|
12 |
+
|
13 |
+
return (<div>
|
14 |
+
<Modal
|
15 |
+
basic
|
16 |
+
className=''
|
17 |
+
open={props.open}
|
18 |
+
size='fullscreen'
|
19 |
+
>
|
20 |
+
<div className='w-full shadow-lg rounded-lg'>
|
21 |
+
<ul className="flex flex-wrap text-sm font-medium text-center text-gray-500 bg-gray-100 rounded-t-lg border-gray-200 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800" id="defaultTab" data-tabs-toggle="#defaultTabContent" role="tablist">
|
22 |
+
<li className="" onClick={()=>{
|
23 |
+
setTab("gradio")
|
24 |
+
props.catch ? props.handelError(false) : props.handelError(props.catch) }}>
|
25 |
+
<button id="gradio-tab" data-tabs-target="#Gradio" type="button" role="tab" aria-controls="gradio" aria-selected={tab === "gradio" ? "true" : "false"} className={`inline-block p-4 rounded-tl-lg ${ tab === "gradio" ? 'bg-gray-200' : 'hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 focus:bg-gray-700'}`}><Gradio className=" w-20 h-10"/></button>
|
26 |
+
</li>
|
27 |
+
<li className="" onClick={()=>{
|
28 |
+
setTab("streamlit")
|
29 |
+
props.catch ? props.handelError(false) : props.handelError(props.catch) }}>
|
30 |
+
<button id="services-tab" data-tabs-target="#Streamlit" type="button" role="tab" aria-controls="services" aria-selected="false" className={`inline-block p-4 rounded-tl-lg ${ tab === "streamlit" ? 'bg-gray-200' : 'hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 focus:bg-gray-700'}`}><Streamlit className=" w-20 h-10"/></button>
|
31 |
+
</li>
|
32 |
+
</ul>
|
33 |
+
<div className='absolute right-5 top-5 z-20 mr-5'
|
34 |
+
onClick={()=>{
|
35 |
+
props.quitHandeler(false)
|
36 |
+
props.catch ? props.handelError(false) : props.handelError(props.catch) }}>
|
37 |
+
<button type="button"
|
38 |
+
className=" bg-neutral-300 rounded-2xl p-2 inline-flex items-center justify-center dark:bg-neutral-700 hover:opacity-70 focus:outline-none">
|
39 |
+
<Exit className=" w-[20px] h-[20px] text-gray-400 dark:text-white"/>
|
40 |
+
</button>
|
41 |
+
</div>
|
42 |
+
</div>
|
43 |
+
{ tab === "gradio" &&
|
44 |
+
<div className='w-full bg-white'>
|
45 |
+
<ul className="flex flex-wrap text-sm font-medium text-center text-gray-500 bg-gray-200 border-gray-200 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800" id="defaultTab" data-tabs-toggle="#defaultTabContent" role="tablist">
|
46 |
+
<li className="" onClick={()=>{
|
47 |
+
setSubTab(0)
|
48 |
+
props.catch ? props.handelError(false) : props.handelError(props.catch) }}>
|
49 |
+
<button id="local-sub-tab" data-tabs-target="#local" type="button" role="tab" aria-controls="local-gradio" aria-selected={tab === "gradio" ? "true" : "false"} className={`inline-block p-4 px-6 text-base font-sans font-bold ${subTab === 0 ? 'bg-gray-300' : '' } hover:bg-gray-300 `}>Local</button>
|
50 |
+
</li>
|
51 |
+
<li className="" onClick={()=>{
|
52 |
+
setSubTab(1)
|
53 |
+
props.catch ? props.handelError(false) : props.handelError(props.catch) }}>
|
54 |
+
<button id="shared-sub-tab" data-tabs-target="#Gradio" type="button" role="tab" aria-controls="shared-gradio" aria-selected={tab === "gradio" ? "true" : "false"} className={`inline-block p-4 px-6 text-base font-sans font-bold ${subTab === 1 ? 'bg-gray-300' : '' } hover:bg-gray-300 `}>Shared</button>
|
55 |
+
</li>
|
56 |
+
</ul>
|
57 |
+
{subTab === 0 && <Local/>}
|
58 |
+
{subTab === 1 && <Shared type="gradio" textHandler={props.textHandler} appendHandler={props.appendHandler} handelError={props.handelError} catch={props.catch}/>}
|
59 |
+
|
60 |
+
{props.catch && <div className='p-5'>
|
61 |
+
<Message floating negative>
|
62 |
+
<Message.Header className=" text-lg text-center">π« Something went wrong...</Message.Header>
|
63 |
+
<br/>
|
64 |
+
<h1 className=" underline pb-3 font-bold text-lg">π€ Possible Things That could of happen <br/></h1>
|
65 |
+
<ul className="font-bold">
|
66 |
+
<li key={"error_1"}>- The input was empty</li>
|
67 |
+
<li key={"error_2"}>- The connection was forbidden</li>
|
68 |
+
<li key={"error_3"}>- The name was already taken</li>
|
69 |
+
<li key={"error_4"}>- The link you gave did not pass the regex</li>
|
70 |
+
<ul className="px-6">
|
71 |
+
<li key={"error_5"}>- http://localhost:xxxx</li>
|
72 |
+
<li key={"error_6"}>- http://xxxxx.gradio.app</li>
|
73 |
+
<li key={"error_7"}>- https://hf.space/embed/$user/$space_name/+</li>
|
74 |
+
</ul>
|
75 |
+
<li>- link already exist within the menu</li>
|
76 |
+
</ul>
|
77 |
+
|
78 |
+
</Message>
|
79 |
+
</div>}
|
80 |
+
</div>
|
81 |
+
}
|
82 |
+
{ tab === "streamlit" &&
|
83 |
+
<div className='w-full bg-white'>
|
84 |
+
<Shared type="streamlit" textHandler={props.textHandler} appendHandler={props.appendHandler} handelError={props.handelError} catch={props.catch}/>
|
85 |
+
</div>
|
86 |
+
}
|
87 |
+
|
88 |
+
</Modal>
|
89 |
+
</div>)
|
90 |
+
}
|
91 |
+
|
92 |
+
function Local(props){
|
93 |
+
return (
|
94 |
+
<div className='p-5'>
|
95 |
+
<Message floating>
|
96 |
+
|
97 |
+
<Message.Header>ποΈ Comming soon...</Message.Header>
|
98 |
+
<Message.Content className='p-5'>
|
99 |
+
This tab will allow you grab your function from a given directory and build
|
100 |
+
your own tabular module gradio functions
|
101 |
+
</Message.Content>
|
102 |
+
|
103 |
+
</Message>
|
104 |
+
</div>
|
105 |
+
)
|
106 |
+
}
|
107 |
+
|
108 |
+
function Shared(props){
|
109 |
+
const [preview, setPreview] = useState("")
|
110 |
+
const [fetchable, setFetch] = useState(false)
|
111 |
+
|
112 |
+
const isFetchable = async (url) => {
|
113 |
+
const pattern = {
|
114 |
+
share : /^https?:\/\/*([0-9]{5})*(-gradio)*(.app)?(\/)?$/,
|
115 |
+
hugginFace : /^https?:\/\/*(hf.space)\/*(embed)\/*([a-zA-Z0-9+_-]+)\/*([a-zA-Z0-9+_-]+)\/*([+])?(\/)?$/
|
116 |
+
}
|
117 |
+
|
118 |
+
if (!pattern.share.test(url) &&
|
119 |
+
!pattern.hugginFace.test(url)){
|
120 |
+
setFetch(false)
|
121 |
+
return
|
122 |
+
}
|
123 |
+
|
124 |
+
|
125 |
+
fetch(url, {mode : "no-cors"}).then((re) => {
|
126 |
+
console.log(re)
|
127 |
+
if(re.url.includes("http://localhost:3000")){
|
128 |
+
setFetch(false)
|
129 |
+
} else {
|
130 |
+
setFetch(true)
|
131 |
+
props.catch ? props.handelError(false) : props.handelError(props.catch)
|
132 |
+
}
|
133 |
+
|
134 |
+
}).catch((err)=>{
|
135 |
+
setFetch(false)
|
136 |
+
})
|
137 |
+
setFetch(false)
|
138 |
+
}
|
139 |
+
|
140 |
+
return (
|
141 |
+
<div className='w-full shadow-lg' onKeyPress={(e)=>{
|
142 |
+
if (e.key.includes("Enter")) props.appendHandler(props.type)
|
143 |
+
}}>
|
144 |
+
<div className='p-5'>
|
145 |
+
<Message floating>
|
146 |
+
<div className={`flex items-center rounded-md bg-light-white mt-6 border-dashed`}>
|
147 |
+
<label className="relative block w-full p-5 focus:shadow-xl">
|
148 |
+
<span className={`absolute inset-y-0 left-0 flex items-center pl-8`}>
|
149 |
+
<BsSearch className="block float-left cursor-pointer text-gray-500"/>
|
150 |
+
</span>
|
151 |
+
<input className={`placeholder:italic placeholder:text-slate-400 text-black dark:text-white block w-full border border-slate-300 border-dashed rounded-md py-2 pl-9 pr-3 focus:shadow-xl focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm bg-transparent`}
|
152 |
+
placeholder={`URL`}
|
153 |
+
type="text" name="search"
|
154 |
+
onChange={(e) => {
|
155 |
+
props.textHandler(e, "text")
|
156 |
+
setPreview(e.target.value)
|
157 |
+
setFetch(isFetchable(e.target.value))
|
158 |
+
}}
|
159 |
+
/>
|
160 |
+
</label>
|
161 |
+
</div>
|
162 |
+
{ fetchable === true && <div className=' w-full'>
|
163 |
+
<h1 className=' text-xl font-sans font-bold text-center text-black mb-2'> Preview </h1>
|
164 |
+
<div className='p-3 px-1 w-3/4 h-80 bg-gray-200 mr-auto ml-auto rounded-xl'>
|
165 |
+
<div className='w-full h-full overflow-hidden relative -ml-[5px]'>
|
166 |
+
<iframe title='Preview' src={preview} className=' absolute top-0 bottom-0 left-0 -right-[25px] overflow-y-scroll w-full h-full mr-auto ml-auto'/>
|
167 |
+
</div>
|
168 |
+
</div>
|
169 |
+
</div>}
|
170 |
+
<div className={`flex items-center rounded-md bg-light-white dark:bg-[#1b1c1d] mt-6 border-dashed`}>
|
171 |
+
<label className="relative block p-5 w-full focus:shadow-xl">
|
172 |
+
<span className={`absolute inset-y-0 left-0 flex items-center pl-7`}>
|
173 |
+
<Icon className=" text-gray-500 block float-left cursor-pointer mr-2" name="address card"/>
|
174 |
+
</span>
|
175 |
+
<input className={`placeholder:italic placeholder:text-slate-400 text-black dark:text-white block bg-transparent w-full border border-slate-300 border-dashed rounded-md py-2 pl-9 pr-3 focus:shadow-xl focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm`}
|
176 |
+
placeholder={`Name ( > 20 Characters)` }
|
177 |
+
type="text" name="search"
|
178 |
+
autoComplete='off'
|
179 |
+
onChange={(e) => {
|
180 |
+
props.textHandler(e, "name")
|
181 |
+
}}
|
182 |
+
/>
|
183 |
+
</label>
|
184 |
+
</div>
|
185 |
+
<div className=' right-0 ml-5'>
|
186 |
+
<button className="relative inline-flex justify-center p-0.5 mb-2 mr-2 overflow-hidden text-sm font-sans font-bold text-gray-900 rounded-lg group bg-gradient-to-br from-purple-600 to-blue-500 group-hover:from-purple-600 group-hover:to-blue-500 hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 dark:focus:ring-blue-800"
|
187 |
+
onClick={()=>{props.appendHandler(props.type)}}>
|
188 |
+
<span className="relative px-5 py-2.5 transition-all ease-in duration-75 bg-white dark:bg-[#1b1c1d] rounded-md group-hover:bg-opacity-0">
|
189 |
+
Enter
|
190 |
+
</span>
|
191 |
+
</button>
|
192 |
+
</div>
|
193 |
+
</Message>
|
194 |
+
</div>
|
195 |
+
</div>
|
196 |
+
|
197 |
+
)
|
198 |
+
}
|
frontend/components/Navagation/navbar.js
ADDED
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { Component } from "react";
|
2 |
+
import { Icon } from 'semantic-ui-react'
|
3 |
+
import Import from '../Modal/importer'
|
4 |
+
import { random_colour, random_emoji } from "../../helper/visual";
|
5 |
+
|
6 |
+
import "../../css/dist/output.css"
|
7 |
+
|
8 |
+
import {BsArrowLeftShort} from 'react-icons/bs';
|
9 |
+
import {ReactComponent as ReactLogo} from '../../images/logo.svg'
|
10 |
+
|
11 |
+
export default class Navbar extends Component{
|
12 |
+
constructor(props){
|
13 |
+
super(props)
|
14 |
+
this.temp_host = 0
|
15 |
+
this.deleteNode = props.onDelete
|
16 |
+
this.state = {
|
17 |
+
open : true,
|
18 |
+
menu : [],
|
19 |
+
colour : props.colour || [],
|
20 |
+
text : "",
|
21 |
+
name : "",
|
22 |
+
emoji : props.emoji || [],
|
23 |
+
mode : false,
|
24 |
+
modal : false,
|
25 |
+
error : false
|
26 |
+
}
|
27 |
+
|
28 |
+
}
|
29 |
+
|
30 |
+
componentDidMount(){
|
31 |
+
this.fetch_classes()
|
32 |
+
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Asynchronously call the Flask api server every second to check if there exist a gradio application info
|
37 |
+
* @return null
|
38 |
+
*/
|
39 |
+
fetch_classes = async () => {
|
40 |
+
try {
|
41 |
+
setInterval( async () => {
|
42 |
+
await fetch("http://localhost:2000/api/open/ports", { method: 'GET', mode : 'cors',})
|
43 |
+
.then(response => response.json())
|
44 |
+
.then(data => {
|
45 |
+
this.handelTabs(this.state.menu,data, data)
|
46 |
+
this.setState({menu : data})
|
47 |
+
})
|
48 |
+
.catch(error => {console.log(error)})
|
49 |
+
|
50 |
+
},1000);
|
51 |
+
}catch(e){
|
52 |
+
console.log(e)
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Append new node from the user
|
58 |
+
*/
|
59 |
+
appendStreamNode = async (type) => {
|
60 |
+
const pattern = {
|
61 |
+
local : /^https?:\/\/(localhost)*(:[0-9]+)?(\/)?$/,
|
62 |
+
share : /^https?:\/\/*([0-9]{5})*(-gradio)*(.app)?(\/)?$/,
|
63 |
+
hugginFace : /^https?:\/\/*(hf.space)\/*(embed)\/*([a-zA-Z0-9+_-]+)\/*([a-zA-Z0-9+_-]+)\/*([+])?(\/)?$/
|
64 |
+
}
|
65 |
+
|
66 |
+
if (this.state.name.length > 20 ||
|
67 |
+
this.state.text === ""||
|
68 |
+
this.state.menu.findIndex(element => {return element.name.toLowerCase() === this.state.name.toLowerCase() || element.host.includes(this.state.text) }) !== -1 ||
|
69 |
+
this.state.text.includes(" ") ||
|
70 |
+
(!pattern.local.test(this.state.text) &&
|
71 |
+
!pattern.share.test(this.state.text) &&
|
72 |
+
!pattern.hugginFace.test(this.state.text))){
|
73 |
+
|
74 |
+
this.setState({
|
75 |
+
'text': '',
|
76 |
+
'name': '',
|
77 |
+
'error': true})
|
78 |
+
return
|
79 |
+
}
|
80 |
+
|
81 |
+
fetch(this.state.text, {method : "GET", mode: 'no-cors'}).then((re) => {
|
82 |
+
fetch("http://localhost:2000/api/append/port", {method: 'POST', mode : 'cors', headers : { 'Content-Type' : 'application/json' }, body: JSON.stringify({file : "", kwargs : { type : type }, name : this.state.name === "" ?`temp_class_${this.temp_host++}` : `${this.state.name}`, port: 0 , host : this.state.text}) }).then(resp => {
|
83 |
+
this.setState({'text': "",'name' : "",'error' : false,'modal' : false })
|
84 |
+
|
85 |
+
}).catch(() => this.setState({'text': '', 'name' : '', 'error' : true, }))
|
86 |
+
}).catch((err)=> this.setState({'text': '','name' : '', 'error' : true,}))
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* error check the user input
|
91 |
+
* @param {*} bool boolean of the current state of the modal
|
92 |
+
*/
|
93 |
+
handelModal = (bool) => {
|
94 |
+
this.setState({'error' : !bool ? false : this.state.error ,
|
95 |
+
'modal' : bool})
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* when dragged get all the information needed
|
100 |
+
* @param {*} event
|
101 |
+
* @param {*} nodeType string 'custom' node type
|
102 |
+
* @param {*} item object information returned from the api
|
103 |
+
* @param {*} index current index
|
104 |
+
*/
|
105 |
+
onDragStart = (event, nodeType, item, index) => {
|
106 |
+
event.dataTransfer.setData('application/reactflow', nodeType);
|
107 |
+
event.dataTransfer.setData('application/style', JSON.stringify({colour : this.state.colour[index], emoji : this.state.emoji[index] }))
|
108 |
+
event.dataTransfer.setData('application/item', JSON.stringify(item))
|
109 |
+
event.dataTransfer.effectAllowed = 'move';
|
110 |
+
};
|
111 |
+
|
112 |
+
/**
|
113 |
+
* droped event that occurs when the user drops the Tab within the tash div.
|
114 |
+
* The function just deletes all nodes within React-Flow enviorment related,
|
115 |
+
* and remove it from the api.
|
116 |
+
* @param {*} e drop event
|
117 |
+
*/
|
118 |
+
onDragDrop = (e) => {
|
119 |
+
e.preventDefault();
|
120 |
+
var item = JSON.parse(e.dataTransfer.getData('application/item'));
|
121 |
+
fetch("http://localhost:2000/api/remove/port", {method : "POST", mode: 'cors', headers : { 'Content-Type' : 'application/json' }, body: JSON.stringify(item) }).then((re)=>{
|
122 |
+
this.deleteNode(item.name)
|
123 |
+
})
|
124 |
+
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* update the tabs within the navbar
|
129 |
+
* @param {*} e current menu
|
130 |
+
* @param {*} d integer variable of the diffence between the current menu and new menu updated ment
|
131 |
+
*/
|
132 |
+
handelTabs = async (e, d) => {
|
133 |
+
// if less then 0 we must remove colour's and emoji's
|
134 |
+
// get index of the object
|
135 |
+
// remove
|
136 |
+
var c = []
|
137 |
+
var j = []
|
138 |
+
if (d.length - e.length === 0) return
|
139 |
+
else if(d.length - e.length < 0){
|
140 |
+
var a = this.state.menu.filter(item => e.includes(item)) // get the items not in menu anymore
|
141 |
+
c = this.state.colour
|
142 |
+
j = this.state.emoji
|
143 |
+
for(var k=0; k < d.length; k++){
|
144 |
+
c.splice(this.state.menu.indexOf(a[k]), 1)
|
145 |
+
j.splice(this.state.menu.indexOf(a[k]), 1)
|
146 |
+
}
|
147 |
+
this.setState({'colour' : c, 'emoji' : j})
|
148 |
+
}else{
|
149 |
+
//append new colours
|
150 |
+
for(var i =0; i < d.length; i++){
|
151 |
+
c.push(random_colour(i === 0 ? null : c[i-1]));
|
152 |
+
j.push(random_emoji(i === 0 ? null : c[i-1]));
|
153 |
+
|
154 |
+
}
|
155 |
+
const colour = [...this.state.colour]
|
156 |
+
const emoji = [...this.state.emoji]
|
157 |
+
this.setState({'colour' : [...colour, ...c], 'emoji' : [...emoji, ...j],})
|
158 |
+
}
|
159 |
+
}
|
160 |
+
|
161 |
+
handelError = (boolean) => {
|
162 |
+
this.setState({'error' : boolean})
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* handel navagation open and close function
|
167 |
+
*/
|
168 |
+
handelNavbar = () => {
|
169 |
+
this.setState({'open' : !this.state.open})
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
*
|
174 |
+
* @param {*} e : event type to get the target value of the current input
|
175 |
+
* @param {*} type : text | name string that set the changed value of the input to the current value
|
176 |
+
*/
|
177 |
+
updateText = (e, type) => {
|
178 |
+
this.setState({[`${type}`] : e.target.value })
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
*
|
183 |
+
* @param {*} item : object infomation from the flask api
|
184 |
+
* @param {*} index : current index with in the list
|
185 |
+
* @returns div component that contians infomation of gradio
|
186 |
+
*/
|
187 |
+
subComponents(item, index){
|
188 |
+
|
189 |
+
return(<>
|
190 |
+
<li key={`${index}-li`} onDragStart={(event) => this.onDragStart(event, 'custom', item, index)}
|
191 |
+
className={` text-white text-md flex flex-col text-center items-center cursor-grab shadow-lg
|
192 |
+
p-5 px-2 mt-4 rounded-md ${ this.state.open ? `hover:animate-pulse ${this.state.colour[index] === null ? "" : this.state.colour[index]} ` : `hidden`} break-all -z-20`} draggable>
|
193 |
+
|
194 |
+
<div key={`${index}-div`} className=" absolute -mt-2 text-4xl opacity-60 z-10 ">{`${this.state.emoji[index] === null ? "" : this.state.emoji[index]}`}</div>
|
195 |
+
<h4 key={`${index}-h4`} className={` max-w-full font-sans text-blue-50 leading-tight font-bold text-xl flex-1 z-20 ${this.state.open ? "" : "hidden"}`} style={{"textShadow" : "0px 1px 2px rgba(0, 0, 0, 0.25)"}} >{`${item.name}`} </h4>
|
196 |
+
|
197 |
+
</li >
|
198 |
+
|
199 |
+
</>)
|
200 |
+
}
|
201 |
+
|
202 |
+
|
203 |
+
render(){
|
204 |
+
|
205 |
+
return (<div>
|
206 |
+
|
207 |
+
<div className={`z-10 flex-1 float-left bg-white dark:bg-stone-900 h-screen p-5 pt-8 ${this.state.open ? "lg:w-72 md:64 sm:w-60" : "w-10"} duration-300 absolute shadow-2xl border-black border-r-[1px] dark:border-white dark:text-white`} >
|
208 |
+
|
209 |
+
<BsArrowLeftShort onClick={this.handelNavbar} className={` bg-white text-Retro-darl-blue text-3xl rounded-full absolute -right-3 top-9 border border-black cursor-pointer ${!this.state.open && 'rotate-180'} dark:border-white duration-300 dark:text-white dark:bg-stone-900 `}/>
|
210 |
+
|
211 |
+
<div className="inline-flex w-full">
|
212 |
+
<h1 className={`font-sans font-bold text-lg ${this.state.open ? "" : "hidden"} duration-500 ml-auto mr-auto`}> <ReactLogo className="w-9 h-9 ml-auto mr-auto"/> Gradio Flow </h1>
|
213 |
+
</div>
|
214 |
+
|
215 |
+
<div className={`rounded-md text-center ${this.state.open ? "" : "px-0"} py-3`} onClick={() => {this.handelModal(true)}}>
|
216 |
+
<div className={` text-center bg-transparent w-full h-10 border border-slate-300 hover:border-Retro-purple hover:animate-pulse border-dashed rounded-md py-2 pl-5 ${this.state.open ? "pr-3" : "hidden"} shadow-sm sm:text-sm`}>
|
217 |
+
<Icon className=" block mr-auto ml-auto" name="plus"/>
|
218 |
+
</div>
|
219 |
+
</div>
|
220 |
+
<Import open={this.state.modal}
|
221 |
+
quitHandeler={this.handelModal}
|
222 |
+
textHandler={this.updateText}
|
223 |
+
appendHandler={this.appendStreamNode}
|
224 |
+
handelError={this.handelError}
|
225 |
+
catch={this.state.error}/>
|
226 |
+
|
227 |
+
<div className=" relative z-10 h-auto overflow-auto pt-4">
|
228 |
+
<ul className="pt-2">
|
229 |
+
{this.state.menu.map((menu, index) => {return this.subComponents(menu, index)})}
|
230 |
+
</ul>
|
231 |
+
</div>
|
232 |
+
|
233 |
+
<div className={`${this.state.open ? "" : "hidden"} absolute bottom-0 left-0 w-full text-center p-5`} onDragOver={(e)=> {e.preventDefault()}} onDrop={(e)=>{this.onDragDrop(e)}}>
|
234 |
+
<div className={` text-center bg-transparent w-full h-10 border border-red-600 border-dashed rounded-md py-2 pl-5 p-4 ${this.state.open ? "pr-3" : "hidden"} shadow-sm sm:text-sm`}>
|
235 |
+
<Icon name='trash alternate' />
|
236 |
+
</div>
|
237 |
+
</div>
|
238 |
+
</div>
|
239 |
+
|
240 |
+
</div>)
|
241 |
+
}
|
242 |
+
}
|
frontend/components/Nodes/Custom.js
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useCallback, useEffect, useRef, useState } from "react"
|
2 |
+
// import { Handle, Position } from "react-flow-renderer"
|
3 |
+
import {TbResize} from 'react-icons/tb'
|
4 |
+
import {BiCube, BiRefresh} from 'react-icons/bi'
|
5 |
+
import {BsTrash} from 'react-icons/bs'
|
6 |
+
import {CgLayoutGridSmall} from 'react-icons/cg'
|
7 |
+
import {useDrag} from '@use-gesture/react'
|
8 |
+
import { useSpring, animated } from 'react-spring'
|
9 |
+
|
10 |
+
import '../../css/counter.css'
|
11 |
+
|
12 |
+
const MINIMUM_HEIGHT = 600;
|
13 |
+
const MINIMUM_WIDTH = 540;
|
14 |
+
|
15 |
+
export default function CustomNodeIframe({id, data}){
|
16 |
+
const [collapsed, setCollapsible] = useState(true)
|
17 |
+
const [{width, height}, api] = useSpring(() => ({width: MINIMUM_WIDTH, height: MINIMUM_HEIGHT }))
|
18 |
+
const [sizeAdjuster, setSizeAdjuster] = useState(false)
|
19 |
+
const [reachable ,setReachable] = useState(false)
|
20 |
+
const [refresh, setRefresh] = useState(0)
|
21 |
+
const dragElement = useRef()
|
22 |
+
|
23 |
+
const bind = useDrag((state) => {
|
24 |
+
const isResizing = (state?.event.target === dragElement.current);
|
25 |
+
if (isResizing) {
|
26 |
+
api.set({
|
27 |
+
width: state.offset[0],
|
28 |
+
height: state.offset[1],
|
29 |
+
|
30 |
+
});
|
31 |
+
}
|
32 |
+
}, {
|
33 |
+
from: (event) => {
|
34 |
+
const isResizing = (event.target === dragElement.current);
|
35 |
+
if (isResizing) {
|
36 |
+
return [width.get(), height.get()];
|
37 |
+
}
|
38 |
+
},
|
39 |
+
});
|
40 |
+
|
41 |
+
const isFetchable =useCallback(async () => {
|
42 |
+
return fetch(data.host, {mode: 'no-cors'}).then((res) => {
|
43 |
+
return true
|
44 |
+
}).catch((err)=>{
|
45 |
+
return false
|
46 |
+
})
|
47 |
+
}, [data.host])
|
48 |
+
|
49 |
+
useEffect(() => {
|
50 |
+
const fetched = setInterval(
|
51 |
+
async () => {
|
52 |
+
const fetch = await isFetchable()
|
53 |
+
if (fetch){
|
54 |
+
setReachable(true)
|
55 |
+
clearInterval(fetched)
|
56 |
+
}
|
57 |
+
},1000)
|
58 |
+
},[isFetchable])
|
59 |
+
|
60 |
+
|
61 |
+
return (
|
62 |
+
<div className="w-10 h-10">
|
63 |
+
|
64 |
+
<div id={'draggable'}className=" flex w-full h-10 top-0 cursor-pointer" onClick={() => {}}>
|
65 |
+
<div id={'draggable'} title={collapsed ? "Collaspse Node" : "Expand Node"} className=" flex-none duration-300 cursor-pointer shadow-xl border-2 border-white h-10 w-10 mr-2 -mt-3 bg-Warm-Blue rounded-xl" onClick={() => {setCollapsible((clps) => !clps)}}><CgLayoutGridSmall className="h-full w-full text-white p-1"/></div>
|
66 |
+
|
67 |
+
<div className={` flex ${!collapsed ? '' : 'w-0 hidden'}`}>
|
68 |
+
<div title="Adjust Node Size" className="duration-300 cursor-pointer shadow-xl border-2 dark:border-white border-white h-10 w-10 mr-2 -mt-3 bg-Warm-Violet rounded-xl" onClick={() => {setSizeAdjuster((size) => !size)}}><TbResize className="h-full w-full text-white p-1"/></div>
|
69 |
+
<a href={data.host} target="_blank" rel="noopener noreferrer"><div title="Gradio Host Site" className="duration-300 cursor-pointer shadow-xl border-2 dark:border-white border-white h-10 w-10 mr-2 -mt-3 bg-Warm-Pink rounded-xl"><BiCube className="h-full w-full text-white p-1"/></div></a>
|
70 |
+
<div title="Delete Node" className="duration-300 cursor-pointer shadow-xl border-2 dark:border-white border-white h-10 w-10 mr-2 -mt-3 bg-Warm-Red rounded-xl" onClick={() => {data.delete([{id}])}}><BsTrash className="h-full w-full text-white p-1"/></div>
|
71 |
+
<div title="Refresh Node" className="duration-300 cursor-pointer shadow-xl border-2 dark:border-white border-white h-10 w-10 mr-2 -mt-3 bg-Warm-Orange rounded-xl" onClick={() => {setRefresh((old) => old++)}}><BiRefresh className="h-full w-full text-white p-1"/></div>
|
72 |
+
</div>
|
73 |
+
</div>
|
74 |
+
|
75 |
+
{ !collapsed && reachable && <>
|
76 |
+
<animated.div className={`border-dashed ${sizeAdjuster ? 'border-4 border-white' : ''} relative top-0 left-0 z-[1000] touch-none shadow-lg rounded-xl`} style={{width, height }} {...bind()}>
|
77 |
+
<div id="draggable" className={`absolute h-full w-full ${data.colour} shadow-2xl rounded-xl -z-20`}></div>
|
78 |
+
<iframe id="iframe"
|
79 |
+
key={refresh}
|
80 |
+
src={data.host}
|
81 |
+
title={data.label}
|
82 |
+
frameBorder="0"
|
83 |
+
className=" p-[0.6rem] -z-10 h-full w-full ml-auto mr-auto overflow-y-scroll"/>
|
84 |
+
<div className={` ${sizeAdjuster ? '' : 'hidden'} rounded-full border-2 absolute -bottom-4 -right-4 w-7 h-7 bg-Blue-Royal cursor-nwse-resize touch-none shadow-lg`} ref={dragElement}>
|
85 |
+
</div>
|
86 |
+
</animated.div>
|
87 |
+
</>
|
88 |
+
}
|
89 |
+
|
90 |
+
{ collapsed &&
|
91 |
+
<div id={`draggable`}
|
92 |
+
className={` w-[340px] h-[140px] text-white text-md flex flex-col text-center items-center cursor-grab shadow-lg
|
93 |
+
p-5 px-2 rounded-md break-all -z-20 ${data.colour} hover:opacity-70 duration-300`} onClick={() => setCollapsible(collapsed => !collapsed)}>
|
94 |
+
<div className="absolute text-6xl opacity-60 z-10 pt-8 ">{data.emoji}</div>
|
95 |
+
<h2 className={`max-w-full font-sans text-blue-50 leading-tight font-bold text-3xl flex-1 z-20 pt-10`} style={{"textShadow" : "0px 1px 2px rgba(0, 0, 0, 0.25)"}} >{data.label}</h2>
|
96 |
+
</div >
|
97 |
+
}
|
98 |
+
</div>)
|
99 |
+
}
|
frontend/components/ReactFlow/ReactFlowEnv.js
ADDED
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import CustomNodeIframe from "../Nodes/Custom.js";
|
2 |
+
import '../../css/dist/output.css'
|
3 |
+
import ReactFlow, { Background,
|
4 |
+
applyNodeChanges,
|
5 |
+
ReactFlowProvider,
|
6 |
+
addEdge,
|
7 |
+
updateEdge,
|
8 |
+
applyEdgeChanges,
|
9 |
+
Controls,
|
10 |
+
MarkerType
|
11 |
+
} from 'react-flow-renderer';
|
12 |
+
import React ,{ useState, useCallback, useRef, useEffect } from 'react';
|
13 |
+
import Navbar from '../Navagation/navbar';
|
14 |
+
import CustomEdge from '../Edges/Custom'
|
15 |
+
import CustomLine from "../Edges/CustomLine.js";
|
16 |
+
import { useThemeDetector } from '../../helper/visual'
|
17 |
+
import {CgMoreVerticalAlt} from 'react-icons/cg'
|
18 |
+
import {BsFillEraserFill} from 'react-icons/bs'
|
19 |
+
import {FaRegSave} from 'react-icons/fa'
|
20 |
+
|
21 |
+
const NODE = {
|
22 |
+
custom : CustomNodeIframe,
|
23 |
+
}
|
24 |
+
|
25 |
+
const EDGE = {
|
26 |
+
custom : CustomEdge
|
27 |
+
}
|
28 |
+
|
29 |
+
|
30 |
+
export default function ReactEnviorment() {
|
31 |
+
|
32 |
+
const [theme, setTheme] = useState(useThemeDetector)
|
33 |
+
const [nodes, setNodes] = useState([]);
|
34 |
+
const [edges, setEdges] = useState([])
|
35 |
+
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
36 |
+
const reactFlowWrapper = useRef(null);
|
37 |
+
const [tool, setTool] = useState(false)
|
38 |
+
|
39 |
+
const deleteNodeContains = (id) =>{setNodes((nds) => nds.filter(n => !n.id.includes(`${id}-`) ))}
|
40 |
+
const deleteEdge = useCallback((id) => setEdges((eds) => eds.filter(e => e.id !== id)), [setEdges])
|
41 |
+
const deleteNode = useCallback((id) =>{
|
42 |
+
setNodes(() => nodes.filter(n => n.id !== id ))
|
43 |
+
}, [setNodes, nodes])
|
44 |
+
|
45 |
+
|
46 |
+
useEffect(() => {
|
47 |
+
const restore = () => {
|
48 |
+
const flow = JSON.parse(localStorage.getItem('flowkey'));
|
49 |
+
|
50 |
+
if(flow){
|
51 |
+
flow.nodes.map((nds) => nds.data.delete = deleteNode)
|
52 |
+
flow.edges.map((eds) => eds.data.delete = deleteEdge)
|
53 |
+
setNodes(flow.nodes || [])
|
54 |
+
setEdges(flow.edges || [])
|
55 |
+
console.log(flow)
|
56 |
+
}
|
57 |
+
}
|
58 |
+
restore()
|
59 |
+
},[deleteNode, deleteEdge])
|
60 |
+
|
61 |
+
|
62 |
+
|
63 |
+
const onNodesChange = useCallback(
|
64 |
+
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
65 |
+
[setNodes]
|
66 |
+
);
|
67 |
+
|
68 |
+
const onEdgesChange = useCallback(
|
69 |
+
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
70 |
+
[setEdges]
|
71 |
+
);
|
72 |
+
|
73 |
+
const onEdgeUpdate = useCallback(
|
74 |
+
(oldEdge, newConnection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)),
|
75 |
+
[]
|
76 |
+
);
|
77 |
+
|
78 |
+
const onConnect = useCallback(
|
79 |
+
(params) => {
|
80 |
+
console.log(params)
|
81 |
+
setEdges((els) => addEdge({...params, type: "custom", animated : true, style : {stroke : "#00FF4A", strokeWidth : "6"}, markerEnd: {type: MarkerType.ArrowClosed, color : "#00FF4A"}, data : { delete : deleteEdge}}, els))
|
82 |
+
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 => {
|
83 |
+
console.log(res)
|
84 |
+
}).catch(error => {
|
85 |
+
console.log(error)
|
86 |
+
})
|
87 |
+
},
|
88 |
+
[setEdges, deleteEdge]
|
89 |
+
);
|
90 |
+
|
91 |
+
|
92 |
+
|
93 |
+
const onDragOver = useCallback((event) => {
|
94 |
+
event.preventDefault();
|
95 |
+
event.dataTransfer.dropEffect = 'move';
|
96 |
+
}, []);
|
97 |
+
|
98 |
+
|
99 |
+
const onSave = useCallback(() => {
|
100 |
+
if (reactFlowInstance) {
|
101 |
+
const flow = reactFlowInstance.toObject();
|
102 |
+
alert("The current nodes have been saved into the localstorage πΎ")
|
103 |
+
localStorage.setItem('flowkey', JSON.stringify(flow));
|
104 |
+
var labels = [];
|
105 |
+
var colour = [];
|
106 |
+
var emoji = [];
|
107 |
+
for(let i = 0; i < flow.nodes.length; i++){
|
108 |
+
if (!labels.includes(flow.nodes[i].data.label))
|
109 |
+
colour.push(flow.nodes[i].data.colour)
|
110 |
+
emoji.push(flow.nodes[i].data.emoji)
|
111 |
+
labels.push(flow.nodes[i].data.label)
|
112 |
+
}
|
113 |
+
localStorage.setItem('colour',JSON.stringify(colour))
|
114 |
+
localStorage.setItem('emoji', JSON.stringify(emoji))
|
115 |
+
}
|
116 |
+
}, [reactFlowInstance]);
|
117 |
+
|
118 |
+
const onErase = useCallback(() => {
|
119 |
+
const flow = localStorage.getItem("flowkey")
|
120 |
+
if (reactFlowInstance && flow){
|
121 |
+
alert("The current nodes have been erased from the localstorage")
|
122 |
+
localStorage.removeItem("flowkey")
|
123 |
+
localStorage.removeItem('colour')
|
124 |
+
localStorage.removeItem('emoji')
|
125 |
+
}
|
126 |
+
},[reactFlowInstance])
|
127 |
+
|
128 |
+
|
129 |
+
const onDrop = useCallback(
|
130 |
+
(event) => {
|
131 |
+
event.preventDefault();
|
132 |
+
|
133 |
+
if(event.dataTransfer.getData('application/reactflow') !== ""){
|
134 |
+
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
135 |
+
const type = event.dataTransfer.getData('application/reactflow');
|
136 |
+
const item = JSON.parse(event.dataTransfer.getData('application/item'));
|
137 |
+
const style = JSON.parse(event.dataTransfer.getData('application/style'));
|
138 |
+
// check if the dropped element is valid
|
139 |
+
if (typeof type === 'undefined' || !type) {
|
140 |
+
return;
|
141 |
+
}
|
142 |
+
|
143 |
+
const position = reactFlowInstance.project({
|
144 |
+
x: event.clientX - reactFlowBounds.left,
|
145 |
+
y: event.clientY - reactFlowBounds.top,
|
146 |
+
});
|
147 |
+
|
148 |
+
const newNode = {
|
149 |
+
id: `${item.name}-${nodes.length+1}`,
|
150 |
+
type,
|
151 |
+
position,
|
152 |
+
dragHandle : `#draggable`,
|
153 |
+
data: { label: `${item.name}`, host : `${item.host}`, colour : `${style.colour}`, emoji : `${style.emoji}`, delete : deleteNode },
|
154 |
+
};
|
155 |
+
|
156 |
+
setNodes((nds) => nds.concat(newNode));
|
157 |
+
}
|
158 |
+
},
|
159 |
+
[reactFlowInstance, nodes, deleteNode]);
|
160 |
+
|
161 |
+
return (
|
162 |
+
<div className={`${theme ? "dark" : ""}`}>
|
163 |
+
<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`} >
|
164 |
+
<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)}/>
|
165 |
+
<h1 title={theme ? 'Dark Mode' : 'Light Mode'} className={`p-4 px-1 pb-0 ${tool ? "visible" : "invisible"} text-3xl`} onClick={() => setTheme(!theme)} >{theme ? 'π' : 'βοΈ'}</h1>
|
166 |
+
<FaRegSave title="Save" className={`mt-6 text-black dark:text-white ${tool ? "visible" : " invisible"} ml-auto mr-auto `} onClick={() => onSave()}/>
|
167 |
+
<BsFillEraserFill title="Erase" className={`mt-6 text-black dark:text-white ml-auto mr-auto ${tool ? "visible" : " invisible"} `} onClick={() => onErase()}/>
|
168 |
+
</div>
|
169 |
+
<div className={`flex h-screen w-screen ${theme ? "dark" : ""} transition-all`}>
|
170 |
+
<ReactFlowProvider>
|
171 |
+
<Navbar onDelete={deleteNodeContains} colour={JSON.parse(localStorage.getItem('colour'))} emoji={JSON.parse(localStorage.getItem('emoji'))}/>
|
172 |
+
<div className="h-screen w-screen" ref={reactFlowWrapper}>
|
173 |
+
<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>
|
174 |
+
<Background variant='dots' size={1} className=" bg-white dark:bg-neutral-800"/>
|
175 |
+
<Controls/>
|
176 |
+
</ReactFlow>
|
177 |
+
</div>
|
178 |
+
</ReactFlowProvider>
|
179 |
+
</div>
|
180 |
+
</div>
|
181 |
+
);
|
182 |
+
}
|
183 |
+
|