Spaces:
Configuration error
Configuration error
Commit
·
ca09a0b
1
Parent(s):
06d4327
Node Connection
Browse files- README.md +4 -2
- backend/app.py +5 -0
- backend/src/demo/stream.py +22 -0
- backend/src/resources/module.py +32 -16
- frontend/src/components/Modal/importer.js +100 -3
- frontend/src/components/Navagation/navbar.js +10 -7
- frontend/src/components/Nodes/Custom.js +36 -8
- frontend/src/components/ReactFlow/ReactFlowEnv.js +35 -4
- frontend/src/css/dist/output.css +79 -10
README.md
CHANGED
@@ -112,6 +112,10 @@ stream both [Gradio](https://gradio.app) ( and later [Streamlit](https://streaml
|
|
112 |
|
113 |
|
114 |
### Frontend 🖥️
|
|
|
|
|
|
|
|
|
115 |
- Node Menu
|
116 |
- fixed some bugs from ``+ button`` for catching errors and wrong inputs
|
117 |
- ``+ button`` now includes hugginface spaces, and gradio share
|
@@ -128,9 +132,7 @@ stream both [Gradio](https://gradio.app) ( and later [Streamlit](https://streaml
|
|
128 |
|
129 |
### In The Works 🚧
|
130 |
- App Name change
|
131 |
-
- Streamlit application within Gradio-Flow
|
132 |
- Mutiple windows within the react-flow environment
|
133 |
-
- Appending streamlit into gradio-flow
|
134 |
- Directory tree search that looks for files that contain classes and functions that are registered under the decorators that are in ``backend/src/resources`` allowing you to append all your registered functions with only using the frontend.
|
135 |
|
136 |
|
|
|
112 |
|
113 |
|
114 |
### Frontend 🖥️
|
115 |
+
- Node
|
116 |
+
- Append edges together
|
117 |
+
- (In the works) Connection API paramters; Allow people to use Gradio Flow as a module base platform
|
118 |
+
|
119 |
- Node Menu
|
120 |
- fixed some bugs from ``+ button`` for catching errors and wrong inputs
|
121 |
- ``+ button`` now includes hugginface spaces, and gradio share
|
|
|
132 |
|
133 |
### In The Works 🚧
|
134 |
- App Name change
|
|
|
135 |
- Mutiple windows within the react-flow environment
|
|
|
136 |
- Directory tree search that looks for files that contain classes and functions that are registered under the decorators that are in ``backend/src/resources`` allowing you to append all your registered functions with only using the frontend.
|
137 |
|
138 |
|
backend/app.py
CHANGED
@@ -72,6 +72,11 @@ def append_port():
|
|
72 |
visable.append(current)
|
73 |
return jsonify({"executed" : True})
|
74 |
|
|
|
|
|
|
|
|
|
|
|
75 |
@app.route("/api/remove/port" , methods=["POST"])
|
76 |
def remove_port():
|
77 |
current = request.json
|
|
|
72 |
visable.append(current)
|
73 |
return jsonify({"executed" : True})
|
74 |
|
75 |
+
@app.route("/api/append/connection", methods=["POST"])
|
76 |
+
def append_connection():
|
77 |
+
current = request.json
|
78 |
+
return jsonify({"executed" : True})
|
79 |
+
|
80 |
@app.route("/api/remove/port" , methods=["POST"])
|
81 |
def remove_port():
|
82 |
current = request.json
|
backend/src/demo/stream.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
def add_port(type='p2p', mode='input', key="bitconnect", additional_info={}):
|
4 |
+
return {
|
5 |
+
'type' : type,
|
6 |
+
'mode' : mode,
|
7 |
+
'key' : key,
|
8 |
+
'additional': additional_info
|
9 |
+
}
|
10 |
+
|
11 |
+
if __name__ == "__main__":
|
12 |
+
st.title("Test Add Handel")
|
13 |
+
with open('./stream.py') as f:
|
14 |
+
st.code(f.read(), "python")
|
15 |
+
with st.expander("Add_Port"):
|
16 |
+
type = st.multiselect("type", ["p2p", 'https', 'local'])
|
17 |
+
mode = st.selectbox("mode", ("input", "output"))
|
18 |
+
key = st.text_input("Key", "")
|
19 |
+
btn_click = st.button("package")
|
20 |
+
|
21 |
+
if btn_click:
|
22 |
+
st.json(add_port(type, mode, key))
|
backend/src/resources/module.py
CHANGED
@@ -62,10 +62,10 @@ def InterLauncher(name, interface, listen=2000, **kwargs):
|
|
62 |
package and send it to the flaks api
|
63 |
"""
|
64 |
port= kwargs["port"] if "port" in kwargs else DOCKER_PORT.determinePort() # determine the next open port is no port is listed **kwargs
|
65 |
-
|
66 |
try:
|
67 |
# (POST) send the information of the gradio to the flask api
|
68 |
-
requests.post(f"http://{DOCKER_LOCAL_HOST}:{listen}/api/append/port", json=
|
69 |
except Exception as e:
|
70 |
# If there is an Exception then notify the user and end the method
|
71 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The listening api is either not up or you choose the wrong port.🐛 \n {e}")
|
@@ -93,13 +93,14 @@ def InterLauncher(name, interface, listen=2000, **kwargs):
|
|
93 |
ssl_keyfile_password=kwargs['ssl_keyfile_password'] if "ssl_keyfile_password" in kwargs else None,
|
94 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
95 |
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
|
|
103 |
|
104 |
def tabularGradio(funcs, names=[], name="Tabular Temp Name", **kwargs):
|
105 |
"""
|
@@ -112,12 +113,13 @@ def tabularGradio(funcs, names=[], name="Tabular Temp Name", **kwargs):
|
|
112 |
names = [_.__name__ for _ in fn]
|
113 |
|
114 |
port= kwargs["port"] if "port" in kwargs else DOCKER_PORT.determinePort()
|
115 |
-
|
116 |
# send this to the backend api for it to be read by the react frontend
|
117 |
if 'listen' in kwargs:
|
|
|
118 |
try:
|
119 |
# (POST) send the information of the gradio to the flask api
|
120 |
-
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/append/port", json=
|
121 |
except Exception as e:
|
122 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The listening api is either not up or you choose the wrong port.🐛 \n {e}")
|
123 |
return
|
@@ -145,9 +147,10 @@ def tabularGradio(funcs, names=[], name="Tabular Temp Name", **kwargs):
|
|
145 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
146 |
|
147 |
# Ctrl+C that ends the process and then continue the code which will remove from the api
|
148 |
-
if 'listen' in kwargs:
|
|
|
149 |
try:
|
150 |
-
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/remove/port", json=
|
151 |
except Exception as e:
|
152 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The api either lost connection or was turned off...🐛 \n {e}")
|
153 |
|
@@ -304,9 +307,11 @@ def GradioModule(cls):
|
|
304 |
then when the gradio stops then remove it from the api
|
305 |
"""
|
306 |
port= kwargs["port"] if "port" in kwargs else DOCKER_PORT.determinePort()
|
|
|
307 |
if 'listen' in kwargs:
|
|
|
308 |
try:
|
309 |
-
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/append/port", json=
|
310 |
except Exception:
|
311 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The listening api is either not up or you choose the wrong port.🐛")
|
312 |
return
|
@@ -331,14 +336,25 @@ def GradioModule(cls):
|
|
331 |
ssl_certfile=kwargs['ssl_certfile'] if "ssl_certfile" in kwargs else None,
|
332 |
ssl_keyfile_password=kwargs['ssl_keyfile_password'] if "ssl_keyfile_password" in kwargs else None,
|
333 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
334 |
-
if 'listen' in kwargs:
|
335 |
try:
|
336 |
-
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/remove/port", json=
|
337 |
except Exception:
|
338 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The api either lost connection or was turned off...🐛")
|
339 |
return
|
340 |
return Decorator
|
341 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
342 |
# console colour changer
|
343 |
class bcolor:
|
344 |
HEADER = '\033[95m'
|
|
|
62 |
package and send it to the flaks api
|
63 |
"""
|
64 |
port= kwargs["port"] if "port" in kwargs else DOCKER_PORT.determinePort() # determine the next open port is no port is listed **kwargs
|
65 |
+
packet = {"port" : port, "host" : f'http://localhost:{port}', "file" : "Not Applicable", "name" : name, "kwargs" : kwargs}
|
66 |
try:
|
67 |
# (POST) send the information of the gradio to the flask api
|
68 |
+
requests.post(f"http://{DOCKER_LOCAL_HOST}:{listen}/api/append/port", json=packet)
|
69 |
except Exception as e:
|
70 |
# If there is an Exception then notify the user and end the method
|
71 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The listening api is either not up or you choose the wrong port.🐛 \n {e}")
|
|
|
93 |
ssl_keyfile_password=kwargs['ssl_keyfile_password'] if "ssl_keyfile_password" in kwargs else None,
|
94 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
95 |
|
96 |
+
if stream_exist(packet=packet, listen=listen):
|
97 |
+
try:
|
98 |
+
# (POST) stop the interface if user hits ctrl+c
|
99 |
+
# send the information of the gradio to the flask
|
100 |
+
# api to remove it from the list within the flask api
|
101 |
+
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ listen }/api/remove/port", json=packet)
|
102 |
+
except Exception as e:
|
103 |
+
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The api either lost connection or was turned off...🐛 \n {e}")
|
104 |
|
105 |
def tabularGradio(funcs, names=[], name="Tabular Temp Name", **kwargs):
|
106 |
"""
|
|
|
113 |
names = [_.__name__ for _ in fn]
|
114 |
|
115 |
port= kwargs["port"] if "port" in kwargs else DOCKER_PORT.determinePort()
|
116 |
+
packet = None
|
117 |
# send this to the backend api for it to be read by the react frontend
|
118 |
if 'listen' in kwargs:
|
119 |
+
packet = {"port" : port, "host" : f'http://localhost:{port}', "file" : 'Not Applicable', "name" : name, "kwargs" : kwargs}
|
120 |
try:
|
121 |
# (POST) send the information of the gradio to the flask api
|
122 |
+
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/append/port", json=packet)
|
123 |
except Exception as e:
|
124 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The listening api is either not up or you choose the wrong port.🐛 \n {e}")
|
125 |
return
|
|
|
147 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
148 |
|
149 |
# Ctrl+C that ends the process and then continue the code which will remove from the api
|
150 |
+
if 'listen' in kwargs and stream_exist(packet=packet,listen=kwargs['listen']):
|
151 |
+
|
152 |
try:
|
153 |
+
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/remove/port", json=packet)
|
154 |
except Exception as e:
|
155 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The api either lost connection or was turned off...🐛 \n {e}")
|
156 |
|
|
|
307 |
then when the gradio stops then remove it from the api
|
308 |
"""
|
309 |
port= kwargs["port"] if "port" in kwargs else DOCKER_PORT.determinePort()
|
310 |
+
packet = None
|
311 |
if 'listen' in kwargs:
|
312 |
+
packet = { type : { 'format' : "class", 'stream' : "gradio" }, "port" : port, "host" : f'http://localhost:{port}', "file" : getfile(self.__cls__.__class__), "name" : self.__cls__.__class__.__name__, "kwargs" : kwargs}
|
313 |
try:
|
314 |
+
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/append/port", json=packet)
|
315 |
except Exception:
|
316 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The listening api is either not up or you choose the wrong port.🐛")
|
317 |
return
|
|
|
336 |
ssl_certfile=kwargs['ssl_certfile'] if "ssl_certfile" in kwargs else None,
|
337 |
ssl_keyfile_password=kwargs['ssl_keyfile_password'] if "ssl_keyfile_password" in kwargs else None,
|
338 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
339 |
+
if 'listen' in kwargs and stream_exist(packet=packet, listen=kwargs[ 'listen' ]):
|
340 |
try:
|
341 |
+
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/remove/port", json=packet)
|
342 |
except Exception:
|
343 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** 🐛The api either lost connection or was turned off...🐛")
|
344 |
return
|
345 |
return Decorator
|
346 |
|
347 |
+
def stream_exist(packet, listen):
|
348 |
+
"""
|
349 |
+
Given the port is still up this will return if the
|
350 |
+
json sent in the beginning still exsit with the api
|
351 |
+
"""
|
352 |
+
state = requests.get(f"http://{DOCKER_LOCAL_HOST}:{listen}/api/open/ports").json()
|
353 |
+
for mod in state:
|
354 |
+
if packet == mod:
|
355 |
+
return True
|
356 |
+
return False
|
357 |
+
|
358 |
# console colour changer
|
359 |
class bcolor:
|
360 |
HEADER = '\033[95m'
|
frontend/src/components/Modal/importer.js
CHANGED
@@ -55,7 +55,7 @@ export default function Import(props){
|
|
55 |
</li>
|
56 |
</ul>
|
57 |
{subTab === 0 && <Local/>}
|
58 |
-
{subTab === 1 && <Shared textHandler={props.textHandler} appendHandler={props.appendHandler} handelError={props.handelError} catch={props.catch}/>}
|
59 |
|
60 |
{props.catch && <div className='p-5'>
|
61 |
<Message floating negative>
|
@@ -79,6 +79,12 @@ export default function Import(props){
|
|
79 |
</div>}
|
80 |
</div>
|
81 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
</Modal>
|
83 |
</div>)
|
84 |
}
|
@@ -99,6 +105,97 @@ function Local(props){
|
|
99 |
)
|
100 |
}
|
101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
function Shared(props){
|
103 |
const [preview, setPreview] = useState("")
|
104 |
const [fetchable, setFetch] = useState(false)
|
@@ -133,7 +230,7 @@ function Shared(props){
|
|
133 |
|
134 |
return (
|
135 |
<div className='w-full shadow-lg' onKeyPress={(e)=>{
|
136 |
-
if (e.key.includes("Enter")) props.appendHandler()
|
137 |
}}>
|
138 |
<div className='p-5'>
|
139 |
<Message floating>
|
@@ -178,7 +275,7 @@ function Shared(props){
|
|
178 |
</div>
|
179 |
<div className=' right-0 ml-5'>
|
180 |
<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"
|
181 |
-
onClick={()=>{props.appendHandler()}}>
|
182 |
<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">
|
183 |
Enter
|
184 |
</span>
|
|
|
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>
|
|
|
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 |
}
|
|
|
105 |
)
|
106 |
}
|
107 |
|
108 |
+
function Stream(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 |
+
|
199 |
function Shared(props){
|
200 |
const [preview, setPreview] = useState("")
|
201 |
const [fetchable, setFetch] = useState(false)
|
|
|
230 |
|
231 |
return (
|
232 |
<div className='w-full shadow-lg' onKeyPress={(e)=>{
|
233 |
+
if (e.key.includes("Enter")) props.appendHandler(props.type)
|
234 |
}}>
|
235 |
<div className='p-5'>
|
236 |
<Message floating>
|
|
|
275 |
</div>
|
276 |
<div className=' right-0 ml-5'>
|
277 |
<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"
|
278 |
+
onClick={()=>{props.appendHandler(props.type)}}>
|
279 |
<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">
|
280 |
Enter
|
281 |
</span>
|
frontend/src/components/Navagation/navbar.js
CHANGED
@@ -1,10 +1,13 @@
|
|
1 |
import React, { Component } from "react";
|
2 |
-
import {BsArrowLeftShort} from 'react-icons/bs';
|
3 |
-
import "../../css/dist/output.css"
|
4 |
-
import {ReactComponent as ReactLogo} from '../../images/logo.svg'
|
5 |
-
import { random_colour, random_emoji } from "../../helper/visual";
|
6 |
import { Icon } from 'semantic-ui-react'
|
7 |
import Import from '../Modal/importer'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
export default class Navbar extends Component{
|
9 |
constructor(props){
|
10 |
super(props)
|
@@ -55,7 +58,7 @@ export default class Navbar extends Component{
|
|
55 |
/**
|
56 |
* Append new node from the user
|
57 |
*/
|
58 |
-
|
59 |
const pattern = {
|
60 |
local : /^https?:\/\/(localhost)*(:[0-9]+)?(\/)?$/,
|
61 |
share : /^https?:\/\/*([0-9]{5})*(-gradio)*(.app)?(\/)?$/,
|
@@ -78,7 +81,7 @@ export default class Navbar extends Component{
|
|
78 |
}
|
79 |
|
80 |
fetch(this.state.text, {method : "GET", mode: 'no-cors'}).then((re) => {
|
81 |
-
fetch("http://localhost:2000/api/append/port", {method: 'POST', mode : 'cors', headers : { 'Content-Type' : 'application/json' }, body: JSON.stringify({file : "", kwargs : {}, name : this.state.name === "" ?`temp_class_${this.temp_host++}` : `${this.state.name}`, port: 0 , host : this.state.text}) }).then(resp => {
|
82 |
this.setState({'text': "",'name' : "",'error' : false,'modal' : false })
|
83 |
|
84 |
}).catch(() => this.setState({'text': '', 'name' : '', 'error' : true, }))
|
@@ -218,7 +221,7 @@ export default class Navbar extends Component{
|
|
218 |
<Import open={this.state.modal}
|
219 |
quitHandeler={this.handelModal}
|
220 |
textHandler={this.updateText}
|
221 |
-
appendHandler={this.
|
222 |
handelError={this.handelError}
|
223 |
catch={this.state.error}/>
|
224 |
|
|
|
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)
|
|
|
58 |
/**
|
59 |
* Append new node from the user
|
60 |
*/
|
61 |
+
appendStreamNode = async (type) => {
|
62 |
const pattern = {
|
63 |
local : /^https?:\/\/(localhost)*(:[0-9]+)?(\/)?$/,
|
64 |
share : /^https?:\/\/*([0-9]{5})*(-gradio)*(.app)?(\/)?$/,
|
|
|
81 |
}
|
82 |
|
83 |
fetch(this.state.text, {method : "GET", mode: 'no-cors'}).then((re) => {
|
84 |
+
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 => {
|
85 |
this.setState({'text': "",'name' : "",'error' : false,'modal' : false })
|
86 |
|
87 |
}).catch(() => this.setState({'text': '', 'name' : '', 'error' : true, }))
|
|
|
221 |
<Import open={this.state.modal}
|
222 |
quitHandeler={this.handelModal}
|
223 |
textHandler={this.updateText}
|
224 |
+
appendHandler={this.appendStreamNode}
|
225 |
handelError={this.handelError}
|
226 |
catch={this.state.error}/>
|
227 |
|
frontend/src/components/Nodes/Custom.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import React from "react"
|
|
|
2 |
import {TbResize} from 'react-icons/tb'
|
3 |
import {BiCube, BiRefresh} from 'react-icons/bi'
|
4 |
import {BsTrash, BsArrowDownRightSquare} from 'react-icons/bs'
|
@@ -20,22 +21,22 @@ export default class CustomNodeIframe extends React.Component {
|
|
20 |
this.state = {
|
21 |
id : id,
|
22 |
reachable : this.isFetchable(data.host),
|
23 |
-
selected :
|
24 |
data : data,
|
25 |
width : 540,
|
26 |
height : 600,
|
27 |
-
size :
|
28 |
iframe : 0,
|
29 |
}
|
30 |
|
31 |
}
|
32 |
|
33 |
handelSelected = () => {
|
34 |
-
this.setState({'selected' : !
|
35 |
}
|
36 |
|
37 |
handelSizeState = () => {
|
38 |
-
this.setState({'size' : !
|
39 |
}
|
40 |
|
41 |
isFetchable = async (host) => {
|
@@ -57,7 +58,7 @@ export default class CustomNodeIframe extends React.Component {
|
|
57 |
if(!this.isFetchable(this.state.data.host)){
|
58 |
this.onNodeClick(this.state.id)
|
59 |
} else{
|
60 |
-
this.setState({'iframe' :
|
61 |
}
|
62 |
}
|
63 |
|
@@ -68,7 +69,7 @@ export default class CustomNodeIframe extends React.Component {
|
|
68 |
|
69 |
handelSize(evt, increment, change){
|
70 |
if (evt === "increment") {
|
71 |
-
this.setState({[`${change}`] : change === "width" ?
|
72 |
change === "width" ? this.myRef.current.style.width = `${this.state.width + increment}px` : this.myRef.current.style.height = `${this.state.height + increment}px`
|
73 |
}
|
74 |
|
@@ -128,7 +129,7 @@ export default class CustomNodeIframe extends React.Component {
|
|
128 |
if (!this.state.reachable) {this.onNodeClick(this.state.id) }
|
129 |
return (<>
|
130 |
<div className=" flex w-full h-10 top-0 cursor-pointer" onClick={this.handelEvent}>
|
131 |
-
<div title="Collaspse Node" className=" duration-300 cursor-pointer shadow-xl border-2 border-white h-10 w-10 mr-2 -mt-3 bg-Warm-Blue rounded-xl" onClick={this.handelSelected}><CgLayoutGridSmall className="h-full w-full text-white p-1"/></div>
|
132 |
|
133 |
|
134 |
<div className={` flex ${this.state.selected ? '' : 'w-0 hidden'}`}>
|
@@ -145,7 +146,7 @@ export default class CustomNodeIframe extends React.Component {
|
|
145 |
|
146 |
</div>
|
147 |
|
148 |
-
<div id={`draggable`} className={`relative w-[540px] h-[600px]
|
149 |
|
150 |
<div className={`absolute p-5 h-full w-full ${this.state.data.colour} shadow-2xl rounded-xl -z-20`}></div>
|
151 |
<iframe
|
@@ -158,6 +159,32 @@ export default class CustomNodeIframe extends React.Component {
|
|
158 |
sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"
|
159 |
></iframe>
|
160 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
{ this.state.size && !navigator.userAgent.match(/firefox|fxios/i) && <>
|
162 |
|
163 |
<div id="remove-ghost" className={`absolute select-none -bottom-0 right-0 w-5 h-5border-2 shadow-2xl rounded-xl z-10 cursor-nwse-resize hover:opacity-50 `}
|
@@ -172,6 +199,7 @@ export default class CustomNodeIframe extends React.Component {
|
|
172 |
|
173 |
</>
|
174 |
}
|
|
|
175 |
|
176 |
</>)
|
177 |
}
|
|
|
1 |
import React 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, BsArrowDownRightSquare} from 'react-icons/bs'
|
|
|
21 |
this.state = {
|
22 |
id : id,
|
23 |
reachable : this.isFetchable(data.host),
|
24 |
+
selected : false,
|
25 |
data : data,
|
26 |
width : 540,
|
27 |
height : 600,
|
28 |
+
size : false,
|
29 |
iframe : 0,
|
30 |
}
|
31 |
|
32 |
}
|
33 |
|
34 |
handelSelected = () => {
|
35 |
+
this.setState(prevState => ({'selected' : !prevState.selected, 'size' : false }))
|
36 |
}
|
37 |
|
38 |
handelSizeState = () => {
|
39 |
+
this.setState(prevState => ({'size' : !prevState.size}))
|
40 |
}
|
41 |
|
42 |
isFetchable = async (host) => {
|
|
|
58 |
if(!this.isFetchable(this.state.data.host)){
|
59 |
this.onNodeClick(this.state.id)
|
60 |
} else{
|
61 |
+
this.setState(prevState => ({'iframe' : prevState.iframe + 1}))
|
62 |
}
|
63 |
}
|
64 |
|
|
|
69 |
|
70 |
handelSize(evt, increment, change){
|
71 |
if (evt === "increment") {
|
72 |
+
this.setState(prevState => ({[`${change}`] : change === "width" ? prevState.width + increment : prevState.height + increment }))
|
73 |
change === "width" ? this.myRef.current.style.width = `${this.state.width + increment}px` : this.myRef.current.style.height = `${this.state.height + increment}px`
|
74 |
}
|
75 |
|
|
|
129 |
if (!this.state.reachable) {this.onNodeClick(this.state.id) }
|
130 |
return (<>
|
131 |
<div className=" flex w-full h-10 top-0 cursor-pointer" onClick={this.handelEvent}>
|
132 |
+
<div title={this.state.selected ? "Collaspse Node" : "Expand Node"} className=" duration-300 cursor-pointer shadow-xl border-2 border-white h-10 w-10 mr-2 -mt-3 bg-Warm-Blue rounded-xl" onClick={this.handelSelected}><CgLayoutGridSmall className="h-full w-full text-white p-1"/></div>
|
133 |
|
134 |
|
135 |
<div className={` flex ${this.state.selected ? '' : 'w-0 hidden'}`}>
|
|
|
146 |
|
147 |
</div>
|
148 |
|
149 |
+
<div id={`draggable`} className={`relative overflow-hidden m-0 p-0 shadow-2xl ${this.state.selected ? "w-[540px] h-[600px]" : "hidden"} duration-200`} ref={this.myRef}>
|
150 |
|
151 |
<div className={`absolute p-5 h-full w-full ${this.state.data.colour} shadow-2xl rounded-xl -z-20`}></div>
|
152 |
<iframe
|
|
|
159 |
sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"
|
160 |
></iframe>
|
161 |
</div>
|
162 |
+
|
163 |
+
{/*Input*/}
|
164 |
+
<Handle type="target"
|
165 |
+
id="input"
|
166 |
+
position={Position.Left}
|
167 |
+
style={!this.state.selected ?
|
168 |
+
{"paddingRight" : "5px" , "marginTop" : "15px", "height" : "25px", "width" : "25px", "borderRadius" : "3px", "zIndex" : "10000", "background" : "white", "boxShadow" : "3px 3px #888888"}
|
169 |
+
:{"paddingRight" : "5px" ,"height" : "25px", "width" : "25px", "borderRadius" : "3px", "zIndex" : "10000", "background" : "white", "boxShadow" : "3px 3px #888888"}}
|
170 |
+
/>
|
171 |
+
|
172 |
+
{/*Output*/}
|
173 |
+
<Handle type="source" id="output" position={Position.Right} style={ !this.state.selected ?
|
174 |
+
{"paddingLeft" : "5px", "marginTop" : "15px" ,"height" : "25px", "width" : "25px", "borderRadius" : "3px", "zIndex" : "10000", "background" : "white", "boxShadow" : "3px 3px #888888"}
|
175 |
+
: {"paddingLeft" : "5px", "marginTop" : "0px" ,"height" : "25px", "width" : "25px", "borderRadius" : "3px", "zIndex" : "10000", "background" : "white", "boxShadow" : "3px 3px #888888"}}/>
|
176 |
+
|
177 |
+
{
|
178 |
+
!this.state.selected &&
|
179 |
+
<div
|
180 |
+
id={`draggable`}
|
181 |
+
className={` w-[340px] h-[140px] text-white text-md flex flex-col text-center items-center cursor-grab shadow-lg
|
182 |
+
p-5 px-2 rounded-md hover:animate-pulse break-all -z-20 ${this.state.data.colour}`}>
|
183 |
+
|
184 |
+
<div className="absolute text-6xl opacity-60 z-10 pt-8 ">{this.state.data.emoji}</div>
|
185 |
+
<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)"}} >{this.state.data.label}</h2>
|
186 |
+
</div >
|
187 |
+
}
|
188 |
{ this.state.size && !navigator.userAgent.match(/firefox|fxios/i) && <>
|
189 |
|
190 |
<div id="remove-ghost" className={`absolute select-none -bottom-0 right-0 w-5 h-5border-2 shadow-2xl rounded-xl z-10 cursor-nwse-resize hover:opacity-50 `}
|
|
|
199 |
|
200 |
</>
|
201 |
}
|
202 |
+
|
203 |
|
204 |
</>)
|
205 |
}
|
frontend/src/components/ReactFlow/ReactFlowEnv.js
CHANGED
@@ -3,6 +3,11 @@ import '../../css/dist/output.css'
|
|
3 |
import ReactFlow, { Background,
|
4 |
applyNodeChanges,
|
5 |
ReactFlowProvider,
|
|
|
|
|
|
|
|
|
|
|
6 |
} from 'react-flow-renderer';
|
7 |
import React ,{ useState, useCallback, useRef, useEffect } from 'react';
|
8 |
import Navbar from '../Navagation/navbar';
|
@@ -19,6 +24,7 @@ export default function ReactEnviorment() {
|
|
19 |
|
20 |
const [theme, setTheme] = useState(useThemeDetector)
|
21 |
const [nodes, setNodes] = useState([]);
|
|
|
22 |
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
23 |
const reactFlowWrapper = useRef(null);
|
24 |
const [tool, setTool] = useState(false)
|
@@ -42,6 +48,30 @@ export default function ReactEnviorment() {
|
|
42 |
[setNodes]
|
43 |
);
|
44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
const onDragOver = useCallback((event) => {
|
47 |
event.preventDefault();
|
@@ -122,12 +152,13 @@ export default function ReactEnviorment() {
|
|
122 |
<BsFillEraserFill title="Erase" className={`mt-6 text-black dark:text-white ml-auto mr-auto ${tool ? "visible" : " invisible"} `} onClick={() => onErase()}/>
|
123 |
</div>
|
124 |
<div className={`flex h-screen w-screen ${theme ? "dark" : ""} transition-all`}>
|
125 |
-
<Navbar onDelete={deleteNodeContains} colour={JSON.parse(localStorage.getItem('colour'))} emoji={JSON.parse(localStorage.getItem('emoji'))}/>
|
126 |
<ReactFlowProvider>
|
|
|
127 |
<div className="h-screen w-screen" ref={reactFlowWrapper}>
|
128 |
-
<ReactFlow nodes={nodes} nodeTypes={types} onNodesChange={onNodesChange} onNodesDelete={deleteNode} onDragOver={onDragOver} onDrop={onDrop} onInit={setReactFlowInstance} fitView>
|
129 |
-
|
130 |
-
|
|
|
131 |
</div>
|
132 |
</ReactFlowProvider>
|
133 |
</div>
|
|
|
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';
|
|
|
24 |
|
25 |
const [theme, setTheme] = useState(useThemeDetector)
|
26 |
const [nodes, setNodes] = useState([]);
|
27 |
+
const [edges, setEdges] = useState([])
|
28 |
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
29 |
const reactFlowWrapper = useRef(null);
|
30 |
const [tool, setTool] = useState(false)
|
|
|
48 |
[setNodes]
|
49 |
);
|
50 |
|
51 |
+
const onEdgesChange = useCallback(
|
52 |
+
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
53 |
+
[setEdges]
|
54 |
+
);
|
55 |
+
|
56 |
+
const onEdgeUpdate = useCallback(
|
57 |
+
(oldEdge, newConnection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)),
|
58 |
+
[]
|
59 |
+
);
|
60 |
+
|
61 |
+
const onConnect = useCallback(
|
62 |
+
(params) => {
|
63 |
+
console.log(params)
|
64 |
+
setEdges((els) => addEdge({...params, animated : true, style : {stroke : "#00FF4A", strokeWidth : "3"}, markerEnd: {type: MarkerType.ArrowClosed, color : "#00FF4A"}}, els))
|
65 |
+
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 => {
|
66 |
+
console.log(res)
|
67 |
+
}).catch(error => {
|
68 |
+
console.log(error)
|
69 |
+
})
|
70 |
+
},
|
71 |
+
[setEdges]
|
72 |
+
);
|
73 |
+
|
74 |
+
|
75 |
|
76 |
const onDragOver = useCallback((event) => {
|
77 |
event.preventDefault();
|
|
|
152 |
<BsFillEraserFill title="Erase" className={`mt-6 text-black dark:text-white ml-auto mr-auto ${tool ? "visible" : " invisible"} `} onClick={() => onErase()}/>
|
153 |
</div>
|
154 |
<div className={`flex h-screen w-screen ${theme ? "dark" : ""} transition-all`}>
|
|
|
155 |
<ReactFlowProvider>
|
156 |
+
<Navbar onDelete={deleteNodeContains} colour={JSON.parse(localStorage.getItem('colour'))} emoji={JSON.parse(localStorage.getItem('emoji'))}/>
|
157 |
<div className="h-screen w-screen" ref={reactFlowWrapper}>
|
158 |
+
<ReactFlow nodes={nodes} edges={edges} nodeTypes={types} onNodesChange={onNodesChange} onNodesDelete={deleteNode} onEdgesChange={onEdgesChange} onEdgeUpdate={onEdgeUpdate} onConnect={onConnect} onDragOver={onDragOver} onDrop={onDrop} onInit={setReactFlowInstance} fitView>
|
159 |
+
<Background variant='dots' size={1} className=" bg-white dark:bg-neutral-800"/>
|
160 |
+
<Controls/>
|
161 |
+
</ReactFlow>
|
162 |
</div>
|
163 |
</ReactFlowProvider>
|
164 |
</div>
|
frontend/src/css/dist/output.css
CHANGED
@@ -557,6 +557,10 @@ video {
|
|
557 |
visibility: hidden;
|
558 |
}
|
559 |
|
|
|
|
|
|
|
|
|
560 |
.absolute {
|
561 |
position: absolute;
|
562 |
}
|
@@ -634,6 +638,14 @@ video {
|
|
634 |
z-index: 50;
|
635 |
}
|
636 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
637 |
.float-left {
|
638 |
float: left;
|
639 |
}
|
@@ -694,6 +706,14 @@ video {
|
|
694 |
margin-top: 0.25rem;
|
695 |
}
|
696 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
697 |
.block {
|
698 |
display: block;
|
699 |
}
|
@@ -754,6 +774,22 @@ video {
|
|
754 |
height: 41px;
|
755 |
}
|
756 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
757 |
.w-full {
|
758 |
width: 100%;
|
759 |
}
|
@@ -794,6 +830,10 @@ video {
|
|
794 |
width: 540px;
|
795 |
}
|
796 |
|
|
|
|
|
|
|
|
|
797 |
.w-5 {
|
798 |
width: 1.25rem;
|
799 |
}
|
@@ -806,6 +846,14 @@ video {
|
|
806 |
width: 100vw;
|
807 |
}
|
808 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
809 |
.max-w-full {
|
810 |
max-width: 100%;
|
811 |
}
|
@@ -1125,6 +1173,12 @@ video {
|
|
1125 |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1126 |
}
|
1127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
1128 |
.from-Deep-Space-Black {
|
1129 |
--tw-gradient-from: #000000;
|
1130 |
--tw-gradient-to: rgb(0 0 0 / 0);
|
@@ -1143,12 +1197,6 @@ video {
|
|
1143 |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1144 |
}
|
1145 |
|
1146 |
-
.from-Peach-Yellow {
|
1147 |
-
--tw-gradient-from: #FFEDBC;
|
1148 |
-
--tw-gradient-to: rgb(255 237 188 / 0);
|
1149 |
-
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1150 |
-
}
|
1151 |
-
|
1152 |
.via-Vapor-Purple {
|
1153 |
--tw-gradient-to: rgb(148 22 127 / 0);
|
1154 |
--tw-gradient-stops: var(--tw-gradient-from), #94167f, var(--tw-gradient-to);
|
@@ -1224,6 +1272,10 @@ video {
|
|
1224 |
--tw-gradient-to: #6E48AA;
|
1225 |
}
|
1226 |
|
|
|
|
|
|
|
|
|
1227 |
.to-Peach-Red {
|
1228 |
--tw-gradient-to: #ED4264;
|
1229 |
}
|
@@ -1240,10 +1292,6 @@ video {
|
|
1240 |
--tw-gradient-to: #3b82f6;
|
1241 |
}
|
1242 |
|
1243 |
-
.to-Peach-Yellow {
|
1244 |
-
--tw-gradient-to: #FFEDBC;
|
1245 |
-
}
|
1246 |
-
|
1247 |
.p-4 {
|
1248 |
padding: 1rem;
|
1249 |
}
|
@@ -1352,6 +1400,14 @@ video {
|
|
1352 |
padding-bottom: 0px;
|
1353 |
}
|
1354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1355 |
.text-center {
|
1356 |
text-align: center;
|
1357 |
}
|
@@ -1395,6 +1451,11 @@ video {
|
|
1395 |
line-height: 2rem;
|
1396 |
}
|
1397 |
|
|
|
|
|
|
|
|
|
|
|
1398 |
.font-medium {
|
1399 |
font-weight: 500;
|
1400 |
}
|
@@ -1507,6 +1568,14 @@ video {
|
|
1507 |
transition-duration: 500ms;
|
1508 |
}
|
1509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1510 |
.ease-in {
|
1511 |
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
1512 |
}
|
|
|
557 |
visibility: hidden;
|
558 |
}
|
559 |
|
560 |
+
.fixed {
|
561 |
+
position: fixed;
|
562 |
+
}
|
563 |
+
|
564 |
.absolute {
|
565 |
position: absolute;
|
566 |
}
|
|
|
638 |
z-index: 50;
|
639 |
}
|
640 |
|
641 |
+
.z-40 {
|
642 |
+
z-index: 40;
|
643 |
+
}
|
644 |
+
|
645 |
+
.z-30 {
|
646 |
+
z-index: 30;
|
647 |
+
}
|
648 |
+
|
649 |
.float-left {
|
650 |
float: left;
|
651 |
}
|
|
|
706 |
margin-top: 0.25rem;
|
707 |
}
|
708 |
|
709 |
+
.mt-auto {
|
710 |
+
margin-top: auto;
|
711 |
+
}
|
712 |
+
|
713 |
+
.mb-auto {
|
714 |
+
margin-bottom: auto;
|
715 |
+
}
|
716 |
+
|
717 |
.block {
|
718 |
display: block;
|
719 |
}
|
|
|
774 |
height: 41px;
|
775 |
}
|
776 |
|
777 |
+
.h-\[200px\] {
|
778 |
+
height: 200px;
|
779 |
+
}
|
780 |
+
|
781 |
+
.h-\[140px\] {
|
782 |
+
height: 140px;
|
783 |
+
}
|
784 |
+
|
785 |
+
.h-\[0px\] {
|
786 |
+
height: 0px;
|
787 |
+
}
|
788 |
+
|
789 |
+
.h-0 {
|
790 |
+
height: 0px;
|
791 |
+
}
|
792 |
+
|
793 |
.w-full {
|
794 |
width: 100%;
|
795 |
}
|
|
|
830 |
width: 540px;
|
831 |
}
|
832 |
|
833 |
+
.w-\[340px\] {
|
834 |
+
width: 340px;
|
835 |
+
}
|
836 |
+
|
837 |
.w-5 {
|
838 |
width: 1.25rem;
|
839 |
}
|
|
|
846 |
width: 100vw;
|
847 |
}
|
848 |
|
849 |
+
.w-\[0px\] {
|
850 |
+
width: 0px;
|
851 |
+
}
|
852 |
+
|
853 |
+
.w-7 {
|
854 |
+
width: 1.75rem;
|
855 |
+
}
|
856 |
+
|
857 |
.max-w-full {
|
858 |
max-width: 100%;
|
859 |
}
|
|
|
1173 |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1174 |
}
|
1175 |
|
1176 |
+
.from-Peach-Yellow {
|
1177 |
+
--tw-gradient-from: #FFEDBC;
|
1178 |
+
--tw-gradient-to: rgb(255 237 188 / 0);
|
1179 |
+
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1180 |
+
}
|
1181 |
+
|
1182 |
.from-Deep-Space-Black {
|
1183 |
--tw-gradient-from: #000000;
|
1184 |
--tw-gradient-to: rgb(0 0 0 / 0);
|
|
|
1197 |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1198 |
}
|
1199 |
|
|
|
|
|
|
|
|
|
|
|
|
|
1200 |
.via-Vapor-Purple {
|
1201 |
--tw-gradient-to: rgb(148 22 127 / 0);
|
1202 |
--tw-gradient-stops: var(--tw-gradient-from), #94167f, var(--tw-gradient-to);
|
|
|
1272 |
--tw-gradient-to: #6E48AA;
|
1273 |
}
|
1274 |
|
1275 |
+
.to-Peach-Yellow {
|
1276 |
+
--tw-gradient-to: #FFEDBC;
|
1277 |
+
}
|
1278 |
+
|
1279 |
.to-Peach-Red {
|
1280 |
--tw-gradient-to: #ED4264;
|
1281 |
}
|
|
|
1292 |
--tw-gradient-to: #3b82f6;
|
1293 |
}
|
1294 |
|
|
|
|
|
|
|
|
|
1295 |
.p-4 {
|
1296 |
padding: 1rem;
|
1297 |
}
|
|
|
1400 |
padding-bottom: 0px;
|
1401 |
}
|
1402 |
|
1403 |
+
.pt-10 {
|
1404 |
+
padding-top: 2.5rem;
|
1405 |
+
}
|
1406 |
+
|
1407 |
+
.pt-9 {
|
1408 |
+
padding-top: 2.25rem;
|
1409 |
+
}
|
1410 |
+
|
1411 |
.text-center {
|
1412 |
text-align: center;
|
1413 |
}
|
|
|
1451 |
line-height: 2rem;
|
1452 |
}
|
1453 |
|
1454 |
+
.text-6xl {
|
1455 |
+
font-size: 3.75rem;
|
1456 |
+
line-height: 1;
|
1457 |
+
}
|
1458 |
+
|
1459 |
.font-medium {
|
1460 |
font-weight: 500;
|
1461 |
}
|
|
|
1568 |
transition-duration: 500ms;
|
1569 |
}
|
1570 |
|
1571 |
+
.duration-100 {
|
1572 |
+
transition-duration: 100ms;
|
1573 |
+
}
|
1574 |
+
|
1575 |
+
.duration-200 {
|
1576 |
+
transition-duration: 200ms;
|
1577 |
+
}
|
1578 |
+
|
1579 |
.ease-in {
|
1580 |
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
1581 |
}
|