Spaces:
Configuration error
Configuration error
Commit
Β·
7f30a93
1
Parent(s):
6e075ce
refactor frontend/backend π¨βπ»
Browse files- backend/src/resources/module.py +94 -22
- frontend/src/App.js +1 -1
- frontend/src/{Components β components}/Navagation/navbar.js +111 -41
- frontend/src/{Components β components}/Nodes/Custom.js +21 -12
- frontend/src/{Components β components}/ReactFlow/ReactFlowEnv.js +3 -5
- frontend/src/css/CustomNode.css +0 -46
- frontend/src/css/dist/output.css +73 -15
- frontend/src/css/iframe.css +0 -5
- frontend/src/helper/visual.js +31 -25
- frontend/tailwind.config.js +8 -0
- images/application_dark.png +2 -2
- images/application_light.png +2 -2
backend/src/resources/module.py
CHANGED
@@ -1,41 +1,77 @@
|
|
|
|
1 |
import gradio as gr
|
2 |
from inspect import getfile
|
3 |
-
|
4 |
import socket
|
5 |
import requests
|
6 |
-
|
7 |
class Dock:
|
8 |
|
9 |
def __init__(self) -> None:
|
10 |
self.port_map = dict()
|
11 |
for p in range(7860, 7880):
|
12 |
-
if not self.
|
13 |
self.port_map[p] = True
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
def
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
21 |
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
for port, available in self.port_map.items():
|
24 |
if available == True:
|
|
|
|
|
25 |
return port
|
|
|
26 |
raise Exception(f'β π {bcolor.BOLD}{bcolor.UNDERLINE}{bcolor.FAIL}All visable ports are used up...Try close some ports {bcolor.ENDC}')
|
27 |
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
30 |
|
31 |
def InterLauncher(name, interface, listen=2000, **kwargs):
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
try:
|
|
|
34 |
requests.post(f"http://{DOCKER_LOCAL_HOST}:{listen}/api/append/port", json={"port" : port, "host" : f'http://localhost:{port}', "file" : "Not Applicable", "name" : name, "kwargs" : kwargs})
|
35 |
-
except Exception as e:
|
|
|
36 |
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}")
|
37 |
return
|
38 |
|
|
|
39 |
interface.launch(server_port=port,
|
40 |
server_name=f"{DOCKER_LOCAL_HOST}",
|
41 |
inline= kwargs['inline'] if "inline" in kwargs else False,
|
@@ -58,6 +94,9 @@ def InterLauncher(name, interface, listen=2000, **kwargs):
|
|
58 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
59 |
|
60 |
try:
|
|
|
|
|
|
|
61 |
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ listen }/api/remove/port", json={"port" : port, "host" : f'http://localhost:{port}', "file" : 'Not Applicable', "name" : name, "kwargs" : kwargs})
|
62 |
except Exception as e:
|
63 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** πThe api either lost connection or was turned off...π \n {e}")
|
@@ -77,6 +116,7 @@ def tabularGradio(funcs, names=[], name="Tabular Temp Name", **kwargs):
|
|
77 |
# send this to the backend api for it to be read by the react frontend
|
78 |
if 'listen' in kwargs:
|
79 |
try:
|
|
|
80 |
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ kwargs[ 'listen' ] }/api/append/port", json={"port" : port, "host" : f'http://localhost:{port}', "file" : 'Not Applicable', "name" : name, "kwargs" : kwargs})
|
81 |
except Exception as e:
|
82 |
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}")
|
@@ -116,14 +156,21 @@ def tabularGradio(funcs, names=[], name="Tabular Temp Name", **kwargs):
|
|
116 |
|
117 |
|
118 |
def register(inputs, outputs, examples=None, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
119 |
def register_gradio(func):
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
if type(outputs) is list:
|
124 |
assert len(outputs) >= 1, f"β {bcolor.BOLD}{bcolor.FAIL}you have no outputs π€¨... {str(type(outputs))} {bcolor.ENDC}"
|
125 |
|
126 |
-
fn_name = func.__name__
|
|
|
|
|
127 |
if 'self' in func.__code__.co_varnames and func.__code__.co_varnames[0] == 'self' and fn_name in dir(args[0]):
|
128 |
"""
|
129 |
given the decorator is on a class then
|
@@ -131,6 +178,7 @@ def register(inputs, outputs, examples=None, **kwargs):
|
|
131 |
if not already initialize.
|
132 |
"""
|
133 |
|
|
|
134 |
if type(inputs) is list:
|
135 |
assert len(inputs) == func.__code__.co_argcount - 1, f"β {bcolor.BOLD}{bcolor.FAIL}inputs should have the same length as arguments{bcolor.ENDC}"
|
136 |
|
@@ -138,8 +186,9 @@ def register(inputs, outputs, examples=None, **kwargs):
|
|
138 |
self = args[0]
|
139 |
self.registered_gradio_functons
|
140 |
except AttributeError:
|
141 |
-
self.registered_gradio_functons = dict()
|
142 |
|
|
|
143 |
if not fn_name in self.registered_gradio_functons:
|
144 |
self.registered_gradio_functons[fn_name] = dict(inputs=inputs,
|
145 |
outputs=outputs,
|
@@ -156,13 +205,19 @@ def register(inputs, outputs, examples=None, **kwargs):
|
|
156 |
allow_flagging=kwargs['allow_flagging'] if "allow_flagging" in kwargs else None,
|
157 |
theme=kwargs['theme'] if "theme" in kwargs else 'default', )
|
158 |
|
|
|
|
|
159 |
if len(args[1:]) == (func.__code__.co_argcount - 1):
|
160 |
return func(*args, **wargs)
|
|
|
|
|
161 |
return None
|
162 |
else :
|
163 |
"""
|
164 |
the function is not a class function
|
165 |
"""
|
|
|
|
|
166 |
if type(inputs) is list:
|
167 |
assert len(inputs) == func.__code__.co_argcount, f"β {bcolor.BOLD}{bcolor.FAIL}inputs should have the same length as arguments{bcolor.ENDC}"
|
168 |
|
@@ -187,7 +242,7 @@ def register(inputs, outputs, examples=None, **kwargs):
|
|
187 |
allow_flagging=kwargs['allow_flagging'] if "allow_flagging" in kwargs else None,
|
188 |
theme='default',
|
189 |
)
|
190 |
-
decorator.__decorator__ = "__gradio__"
|
191 |
return decorator
|
192 |
return register_gradio
|
193 |
|
@@ -200,14 +255,23 @@ def GradioModule(cls):
|
|
200 |
self.interface = self.__compile()
|
201 |
|
202 |
def get_funcs_names(self):
|
|
|
|
|
|
|
203 |
assert self.get_registered_map() != None, "this is not possible..."
|
204 |
return [ name for name in self.get_registered_map().keys()]
|
205 |
|
206 |
def get_registered_map(self):
|
|
|
|
|
|
|
207 |
assert self.__cls__.registered_gradio_functons != None, "what happen!!!!"
|
208 |
return self.__cls__.registered_gradio_functons
|
209 |
|
210 |
def __get_funcs_attr(self):
|
|
|
|
|
|
|
211 |
for func in dir(self.__cls__):
|
212 |
fn = getattr(self.__cls__, func, None)
|
213 |
|
@@ -220,8 +284,8 @@ def GradioModule(cls):
|
|
220 |
within the class that are registeed
|
221 |
"""
|
222 |
demos, names = [], []
|
223 |
-
for func, param in self.get_registered_map().items():
|
224 |
-
names.append(func)
|
225 |
try:
|
226 |
demos.append(gr.Interface(fn=getattr(self.__cls__, func, None), **param))
|
227 |
except Exception as e :
|
@@ -232,6 +296,13 @@ def GradioModule(cls):
|
|
232 |
return gr.TabbedInterface(demos, names)
|
233 |
|
234 |
def launch(self, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
port= kwargs["port"] if "port" in kwargs else DOCKER_PORT.determinePort()
|
236 |
if 'listen' in kwargs:
|
237 |
try:
|
@@ -269,6 +340,7 @@ def GradioModule(cls):
|
|
269 |
|
270 |
return Decorator
|
271 |
|
|
|
272 |
class bcolor:
|
273 |
HEADER = '\033[95m'
|
274 |
OKBLUE = '\033[94m'
|
|
|
1 |
+
from signal import signal, SIGKILL
|
2 |
import gradio as gr
|
3 |
from inspect import getfile
|
|
|
4 |
import socket
|
5 |
import requests
|
6 |
+
import os
|
7 |
class Dock:
|
8 |
|
9 |
def __init__(self) -> None:
|
10 |
self.port_map = dict()
|
11 |
for p in range(7860, 7880):
|
12 |
+
if not self.port_is_connected(p):
|
13 |
self.port_map[p] = True
|
14 |
+
else:
|
15 |
+
self.port_map[p] = False
|
16 |
+
|
17 |
+
|
18 |
|
19 |
+
def port_is_connected(self, port : int) -> bool:
|
20 |
+
"""
|
21 |
+
@params:
|
22 |
+
- port : int
|
23 |
+
@return:
|
24 |
+
- boolean
|
25 |
+
check if the port is open with in our localhost
|
26 |
+
"""
|
27 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
28 |
+
return s.connect_ex(("localhost", port)) == 0
|
29 |
|
30 |
+
|
31 |
+
def determinePort(self) -> any:
|
32 |
+
"""
|
33 |
+
Take the port_map that was instantiate
|
34 |
+
in the __init__ and loop though all ports
|
35 |
+
and check if it the port is available
|
36 |
+
"""
|
37 |
for port, available in self.port_map.items():
|
38 |
if available == True:
|
39 |
+
if self.port_is_connected(port): # check if port is in use if so then go to the next one
|
40 |
+
continue
|
41 |
return port
|
42 |
+
|
43 |
raise Exception(f'β π {bcolor.BOLD}{bcolor.UNDERLINE}{bcolor.FAIL}All visable ports are used up...Try close some ports {bcolor.ENDC}')
|
44 |
|
45 |
+
|
46 |
+
|
47 |
+
|
48 |
+
|
49 |
+
DOCKER_LOCAL_HOST = '0.0.0.0'
|
50 |
+
DOCKER_PORT = Dock() # Determine the best possible port
|
51 |
|
52 |
def InterLauncher(name, interface, listen=2000, **kwargs):
|
53 |
+
"""
|
54 |
+
@params:
|
55 |
+
- name : string
|
56 |
+
- interface : gradio.Interface(...)
|
57 |
+
- listen : int
|
58 |
+
- **kwargs
|
59 |
+
|
60 |
+
Take any gradio interface object
|
61 |
+
that is created by the gradio
|
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={"port" : port, "host" : f'http://localhost:{port}', "file" : "Not Applicable", "name" : name, "kwargs" : kwargs})
|
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}")
|
72 |
return
|
73 |
|
74 |
+
# Launch the gradio application
|
75 |
interface.launch(server_port=port,
|
76 |
server_name=f"{DOCKER_LOCAL_HOST}",
|
77 |
inline= kwargs['inline'] if "inline" in kwargs else False,
|
|
|
94 |
quiet=kwargs['quiet'] if "quiet" in kwargs else False)
|
95 |
|
96 |
try:
|
97 |
+
# (POST) stop the interface if user hits ctrl+c
|
98 |
+
# send the information of the gradio to the flask
|
99 |
+
# api to remove it from the list within the flask api
|
100 |
requests.post(f"http://{DOCKER_LOCAL_HOST}:{ listen }/api/remove/port", json={"port" : port, "host" : f'http://localhost:{port}', "file" : 'Not Applicable', "name" : name, "kwargs" : kwargs})
|
101 |
except Exception as e:
|
102 |
print(f"**{bcolor.BOLD}{bcolor.FAIL}CONNECTION ERROR{bcolor.ENDC}** πThe api either lost connection or was turned off...π \n {e}")
|
|
|
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={"port" : port, "host" : f'http://localhost:{port}', "file" : 'Not Applicable', "name" : name, "kwargs" : kwargs})
|
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}")
|
|
|
156 |
|
157 |
|
158 |
def register(inputs, outputs, examples=None, **kwargs):
|
159 |
+
"""
|
160 |
+
Decorator that is appended to a function either within a class or not
|
161 |
+
and output either an interface or inputs and outputs for later processing
|
162 |
+
to launch either to Gradio-Flow or just Gradio
|
163 |
+
"""
|
164 |
def register_gradio(func):
|
165 |
+
def decorator(*args, **wargs):
|
166 |
+
|
167 |
+
# if the output is a list then there should be equal or more then 1
|
168 |
if type(outputs) is list:
|
169 |
assert len(outputs) >= 1, f"β {bcolor.BOLD}{bcolor.FAIL}you have no outputs π€¨... {str(type(outputs))} {bcolor.ENDC}"
|
170 |
|
171 |
+
fn_name = func.__name__ # name of the function
|
172 |
+
|
173 |
+
# if there exist the self within the arguments and thats the first argument then this must be a class function
|
174 |
if 'self' in func.__code__.co_varnames and func.__code__.co_varnames[0] == 'self' and fn_name in dir(args[0]):
|
175 |
"""
|
176 |
given the decorator is on a class then
|
|
|
178 |
if not already initialize.
|
179 |
"""
|
180 |
|
181 |
+
# if the inputs is a list then inputs list should equal the arugments list
|
182 |
if type(inputs) is list:
|
183 |
assert len(inputs) == func.__code__.co_argcount - 1, f"β {bcolor.BOLD}{bcolor.FAIL}inputs should have the same length as arguments{bcolor.ENDC}"
|
184 |
|
|
|
186 |
self = args[0]
|
187 |
self.registered_gradio_functons
|
188 |
except AttributeError:
|
189 |
+
self.registered_gradio_functons = dict() # if registered_gradio_functons does not exist then create it
|
190 |
|
191 |
+
# if the function name is not within the registered_gradio_functons then register it within the registered_gradio_functons
|
192 |
if not fn_name in self.registered_gradio_functons:
|
193 |
self.registered_gradio_functons[fn_name] = dict(inputs=inputs,
|
194 |
outputs=outputs,
|
|
|
205 |
allow_flagging=kwargs['allow_flagging'] if "allow_flagging" in kwargs else None,
|
206 |
theme=kwargs['theme'] if "theme" in kwargs else 'default', )
|
207 |
|
208 |
+
# if the argument are within the function when it's called then give me the output of the function
|
209 |
+
# giving the user the ability to use the function if necessary
|
210 |
if len(args[1:]) == (func.__code__.co_argcount - 1):
|
211 |
return func(*args, **wargs)
|
212 |
+
|
213 |
+
# return nothing if the arguments are not within it cause if the arguments do not exist it will give a error
|
214 |
return None
|
215 |
else :
|
216 |
"""
|
217 |
the function is not a class function
|
218 |
"""
|
219 |
+
|
220 |
+
# if the inputs is a list then inputs list should equal the arugments list
|
221 |
if type(inputs) is list:
|
222 |
assert len(inputs) == func.__code__.co_argcount, f"β {bcolor.BOLD}{bcolor.FAIL}inputs should have the same length as arguments{bcolor.ENDC}"
|
223 |
|
|
|
242 |
allow_flagging=kwargs['allow_flagging'] if "allow_flagging" in kwargs else None,
|
243 |
theme='default',
|
244 |
)
|
245 |
+
decorator.__decorator__ = "__gradio__" # siginture to tell any function that need to know that function is a registed gradio application
|
246 |
return decorator
|
247 |
return register_gradio
|
248 |
|
|
|
255 |
self.interface = self.__compile()
|
256 |
|
257 |
def get_funcs_names(self):
|
258 |
+
"""
|
259 |
+
Get all name for each function
|
260 |
+
"""
|
261 |
assert self.get_registered_map() != None, "this is not possible..."
|
262 |
return [ name for name in self.get_registered_map().keys()]
|
263 |
|
264 |
def get_registered_map(self):
|
265 |
+
"""
|
266 |
+
Get all registered functions
|
267 |
+
"""
|
268 |
assert self.__cls__.registered_gradio_functons != None, "what happen!!!!"
|
269 |
return self.__cls__.registered_gradio_functons
|
270 |
|
271 |
def __get_funcs_attr(self):
|
272 |
+
"""
|
273 |
+
Get all the function that are registered
|
274 |
+
"""
|
275 |
for func in dir(self.__cls__):
|
276 |
fn = getattr(self.__cls__, func, None)
|
277 |
|
|
|
284 |
within the class that are registeed
|
285 |
"""
|
286 |
demos, names = [], []
|
287 |
+
for func, param in self.get_registered_map().items(): # loop though the registered function and append it to the TabularInterface
|
288 |
+
names.append(func)
|
289 |
try:
|
290 |
demos.append(gr.Interface(fn=getattr(self.__cls__, func, None), **param))
|
291 |
except Exception as e :
|
|
|
296 |
return gr.TabbedInterface(demos, names)
|
297 |
|
298 |
def launch(self, **kwargs):
|
299 |
+
"""
|
300 |
+
@params:
|
301 |
+
**kwargs
|
302 |
+
Take the tabular interface and send it to the api if
|
303 |
+
'listen' is within the kwargs and launch the gradio interface
|
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:
|
|
|
340 |
|
341 |
return Decorator
|
342 |
|
343 |
+
# console colour changer
|
344 |
class bcolor:
|
345 |
HEADER = '\033[95m'
|
346 |
OKBLUE = '\033[94m'
|
frontend/src/App.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import ReactEnviorment from './
|
2 |
|
3 |
export default function App() {
|
4 |
return(
|
|
|
1 |
+
import ReactEnviorment from './components/ReactFlow/ReactFlowEnv'
|
2 |
|
3 |
export default function App() {
|
4 |
return(
|
frontend/src/{Components β components}/Navagation/navbar.js
RENAMED
@@ -6,10 +6,11 @@ import { random_colour, random_emoji } from "../../helper/visual";
|
|
6 |
import { Message, Header, Modal, Button, Icon } from 'semantic-ui-react'
|
7 |
|
8 |
export default class Navbar extends Component{
|
9 |
-
constructor(){
|
10 |
-
super()
|
11 |
this.fetch_classes()
|
12 |
this.temp_host = 0
|
|
|
13 |
this.state = {open : true,
|
14 |
menu : [],
|
15 |
colour : [],
|
@@ -19,11 +20,15 @@ export default class Navbar extends Component{
|
|
19 |
mode : false,
|
20 |
modal : false,
|
21 |
error : false
|
|
|
22 |
}
|
23 |
}
|
24 |
|
25 |
|
26 |
-
|
|
|
|
|
|
|
27 |
async fetch_classes(){
|
28 |
try {
|
29 |
setInterval( async () => {
|
@@ -47,35 +52,39 @@ export default class Navbar extends Component{
|
|
47 |
}
|
48 |
}
|
49 |
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
// emoji : this.state.emoji,
|
60 |
-
// error : true,
|
61 |
-
// modal : this.state.modal })
|
62 |
-
// return
|
63 |
-
// }
|
64 |
-
|
65 |
-
// if((this.state.text.includes("localhost") || this.state.text.includes("127.0.0.1"))
|
66 |
-
// && this.state.menu.some(e => e.host === this.state.text)){
|
67 |
-
// this.setState({open : this.state.open,
|
68 |
-
// menu : this.state.menu,
|
69 |
-
// text: this.state.text,
|
70 |
-
// name : this.state.name,
|
71 |
-
// colour : this.state.colour,
|
72 |
-
// emoji : this.state.emoji,
|
73 |
-
// error : true,
|
74 |
-
// modal : this.state.modal })
|
75 |
|
76 |
-
|
|
|
|
|
|
|
|
|
77 |
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
fetch(this.state.text, {method : "GET", mode: 'no-cors'}).then((re) => {
|
80 |
console.log(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 => {
|
@@ -88,16 +97,31 @@ export default class Navbar extends Component{
|
|
88 |
error : false,
|
89 |
modal : false })
|
90 |
|
91 |
-
}).catch(() => this.setState({open : this.state.open,
|
92 |
menu : this.state.menu,
|
93 |
-
text:
|
|
|
94 |
colour : this.state.colour,
|
95 |
emoji : this.state.emoji,
|
96 |
error : true,
|
97 |
modal : this.state.modal }))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
})
|
99 |
}
|
100 |
|
|
|
|
|
|
|
|
|
101 |
handelModal = (bool) => {
|
102 |
this.setState({open : this.state.open,
|
103 |
menu : this.state.menu,
|
@@ -109,25 +133,40 @@ export default class Navbar extends Component{
|
|
109 |
modal : bool})
|
110 |
}
|
111 |
|
112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
onDragStart = (event, nodeType, item, index) => {
|
114 |
-
console.log(item)
|
115 |
event.dataTransfer.setData('application/reactflow', nodeType);
|
116 |
-
event.dataTransfer.setData('application/host', item.host)
|
117 |
-
event.dataTransfer.setData('application/name', item.name)
|
118 |
event.dataTransfer.setData('application/colour', this.state.colour[index])
|
119 |
event.dataTransfer.setData('application/item', JSON.stringify(item))
|
120 |
-
|
121 |
event.dataTransfer.effectAllowed = 'move';
|
122 |
};
|
123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
onDragDrop = (e) => {
|
125 |
e.preventDefault();
|
126 |
var item = JSON.parse(e.dataTransfer.getData('application/item'));
|
127 |
fetch("http://localhost:2000/api/remove/port", {method : "POST", mode: 'cors', headers : { 'Content-Type' : 'application/json' }, body: JSON.stringify(item) }).then((re)=>{
|
|
|
128 |
})
|
|
|
129 |
}
|
130 |
|
|
|
|
|
|
|
|
|
|
|
131 |
hanelTabs = (e, d) => {
|
132 |
|
133 |
// if less then 0 we must remove colour's and emoji's
|
@@ -150,22 +189,39 @@ export default class Navbar extends Component{
|
|
150 |
c.push(random_colour());
|
151 |
j.push(random_emoji());
|
152 |
}
|
153 |
-
this.setState({open : this.state.open, menu : e, text: this.state.text, name: this.state.name, colour : this.state.colour
|
154 |
}
|
155 |
}
|
156 |
|
|
|
|
|
|
|
157 |
appendTabs = () => {
|
158 |
this.setState({open : this.state.open, menu : this.state.menu, text: this.state.text, name: this.state.name, colour : [...this.state.colour, random_colour] , emoji : [...this.state.emoji, random_emoji], error : this.state.error, modal : this.state.modal })
|
159 |
}
|
160 |
|
|
|
|
|
|
|
161 |
handelNavbar = () => {
|
162 |
this.setState({open : !this.state.open, menu : this.state.menu, text: this.state.text, name: this.state.name, colour : this.state.colour, emoji : this.state.emoji, error : this.state.error, modal : this.state.modal })
|
163 |
}
|
164 |
|
|
|
|
|
|
|
|
|
|
|
165 |
updateText(e, type){
|
166 |
this.setState({open : this.state.open, menu : this.state.menu, text: type === "text" ? e.target.value : this.state.text, name: type === "name" ? e.target.value : this.state.name, colour : this.state.colour, emoji : this.state.emoji, error : this.state.error, modal : this.state.modal })
|
167 |
}
|
168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
subComponents(item, index){
|
170 |
|
171 |
return(<>
|
@@ -213,7 +269,7 @@ export default class Navbar extends Component{
|
|
213 |
<span className={`absolute inset-y-0 left-0 flex items-center pl-3`}>
|
214 |
<BsSearch className="block float-left cursor-pointer mr-2"/>
|
215 |
</span>
|
216 |
-
<input className={`placeholder:italic placeholder:text-slate-400 block bg-transparent w-full border border-slate-300 border-dashed rounded-md py-2 pl-9 ${this.state.open ? "pr-3" : "hidden"} shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1
|
217 |
placeholder={`stream link...`}
|
218 |
type="text" name="search"
|
219 |
onChange={(e) => {
|
@@ -240,8 +296,22 @@ export default class Navbar extends Component{
|
|
240 |
|
241 |
{ this.state.error &&
|
242 |
<Message negative>
|
243 |
-
<Message.Header>π«
|
244 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
</Message>
|
246 |
}
|
247 |
|
|
|
6 |
import { Message, Header, Modal, Button, Icon } from 'semantic-ui-react'
|
7 |
|
8 |
export default class Navbar extends Component{
|
9 |
+
constructor(props){
|
10 |
+
super(props)
|
11 |
this.fetch_classes()
|
12 |
this.temp_host = 0
|
13 |
+
this.deleteNode = props.onDelete
|
14 |
this.state = {open : true,
|
15 |
menu : [],
|
16 |
colour : [],
|
|
|
20 |
mode : false,
|
21 |
modal : false,
|
22 |
error : false
|
23 |
+
|
24 |
}
|
25 |
}
|
26 |
|
27 |
|
28 |
+
/**
|
29 |
+
* Asynchronously call the Flask api server every second to check if there exist a gradio application info
|
30 |
+
* @return null
|
31 |
+
*/
|
32 |
async fetch_classes(){
|
33 |
try {
|
34 |
setInterval( async () => {
|
|
|
52 |
}
|
53 |
}
|
54 |
|
55 |
+
/**
|
56 |
+
* Append new node from the user
|
57 |
+
*/
|
58 |
+
append_gradio = async () => {
|
59 |
+
const pattern = {
|
60 |
+
local : /^https?:\/\/(localhost)*(:[0-9]+)?(\/)?$/,
|
61 |
+
share : /^https?:\/\/*([0-9]{5})*(-gradio)*(.app)?(\/)?$/,
|
62 |
+
hugginFace : /^https?:\/\/*(hf.space)\/*(embed)\/*([a-zA-Z0-9+_-]+)\/*([a-zA-Z0-9+_-]+)\/*([+])?(\/)?$/
|
63 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
|
65 |
+
if (this.state.menu.findIndex(element => {return element.name.toLowerCase() === this.state.name.toLowerCase() || element.host.includes(this.state.host) }) !== -1 ||
|
66 |
+
this.state.text.includes(" ") ||
|
67 |
+
(!pattern.local.test(this.state.text) &&
|
68 |
+
!pattern.share.test(this.state.text) &&
|
69 |
+
!pattern.hugginFace.test(this.state.text))){
|
70 |
|
71 |
+
console.log(this.state.menu.findIndex(element => {return element.name.toLowerCase() === this.state.name.toLowerCase() || element.host.includes(this.state.host) }) !== -1)
|
72 |
+
console.log(this.state.text.includes(" ") )
|
73 |
+
console.log(this.state.text.includes("") )
|
74 |
+
console.log(!pattern.local.test(this.state.text) &&
|
75 |
+
!pattern.share.test(this.state.text) &&
|
76 |
+
!pattern.hugginFace.test(this.state.text))
|
77 |
+
this.setState({open : this.state.open,
|
78 |
+
menu : this.state.menu,
|
79 |
+
text: '',
|
80 |
+
name : '',
|
81 |
+
colour : this.state.colour,
|
82 |
+
emoji : this.state.emoji,
|
83 |
+
error : true,
|
84 |
+
modal : this.state.modal })
|
85 |
+
return
|
86 |
+
}
|
87 |
+
|
88 |
fetch(this.state.text, {method : "GET", mode: 'no-cors'}).then((re) => {
|
89 |
console.log(re)
|
90 |
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 => {
|
|
|
97 |
error : false,
|
98 |
modal : false })
|
99 |
|
100 |
+
}).catch((err) => this.setState({open : this.state.open,
|
101 |
menu : this.state.menu,
|
102 |
+
text: '',
|
103 |
+
name : '',
|
104 |
colour : this.state.colour,
|
105 |
emoji : this.state.emoji,
|
106 |
error : true,
|
107 |
modal : this.state.modal }))
|
108 |
+
}).catch((err)=>{
|
109 |
+
console.log(err)
|
110 |
+
this.setState({open : this.state.open,
|
111 |
+
menu : this.state.menu,
|
112 |
+
text: '',
|
113 |
+
name : '',
|
114 |
+
colour : this.state.colour,
|
115 |
+
emoji : this.state.emoji,
|
116 |
+
error : true,
|
117 |
+
modal : this.state.modal })
|
118 |
})
|
119 |
}
|
120 |
|
121 |
+
/**
|
122 |
+
* error check the user input
|
123 |
+
* @param {*} bool boolean of the current state of the modal
|
124 |
+
*/
|
125 |
handelModal = (bool) => {
|
126 |
this.setState({open : this.state.open,
|
127 |
menu : this.state.menu,
|
|
|
133 |
modal : bool})
|
134 |
}
|
135 |
|
136 |
+
/**
|
137 |
+
* when dragged get all the information needed
|
138 |
+
* @param {*} event
|
139 |
+
* @param {*} nodeType string 'custom' node type
|
140 |
+
* @param {*} item object information returned from the api
|
141 |
+
* @param {*} index current index
|
142 |
+
*/
|
143 |
onDragStart = (event, nodeType, item, index) => {
|
|
|
144 |
event.dataTransfer.setData('application/reactflow', nodeType);
|
|
|
|
|
145 |
event.dataTransfer.setData('application/colour', this.state.colour[index])
|
146 |
event.dataTransfer.setData('application/item', JSON.stringify(item))
|
|
|
147 |
event.dataTransfer.effectAllowed = 'move';
|
148 |
};
|
149 |
|
150 |
+
/**
|
151 |
+
* droped event that occurs when the user drops the Tab within the tash div.
|
152 |
+
* The function just deletes all nodes within React-Flow enviorment related,
|
153 |
+
* and remove it from the api.
|
154 |
+
* @param {*} e drop event
|
155 |
+
*/
|
156 |
onDragDrop = (e) => {
|
157 |
e.preventDefault();
|
158 |
var item = JSON.parse(e.dataTransfer.getData('application/item'));
|
159 |
fetch("http://localhost:2000/api/remove/port", {method : "POST", mode: 'cors', headers : { 'Content-Type' : 'application/json' }, body: JSON.stringify(item) }).then((re)=>{
|
160 |
+
this.deleteNode(item.name)
|
161 |
})
|
162 |
+
|
163 |
}
|
164 |
|
165 |
+
/**
|
166 |
+
* update the tabs within the navbar
|
167 |
+
* @param {*} e current menu
|
168 |
+
* @param {*} d integer variable of the diffence between the current menu and new menu updated ment
|
169 |
+
*/
|
170 |
hanelTabs = (e, d) => {
|
171 |
|
172 |
// if less then 0 we must remove colour's and emoji's
|
|
|
189 |
c.push(random_colour());
|
190 |
j.push(random_emoji());
|
191 |
}
|
192 |
+
this.setState({open : this.state.open, menu : e, text: this.state.text, name: this.state.name, colour : [...this.state.colour, ...c], emoji : [...this.state.emoji, ...j], error : this.state.error, modal : this.state.modal })
|
193 |
}
|
194 |
}
|
195 |
|
196 |
+
/**
|
197 |
+
* Append a new colour, and emoji to the colour and emoji list with in the state of the component
|
198 |
+
*/
|
199 |
appendTabs = () => {
|
200 |
this.setState({open : this.state.open, menu : this.state.menu, text: this.state.text, name: this.state.name, colour : [...this.state.colour, random_colour] , emoji : [...this.state.emoji, random_emoji], error : this.state.error, modal : this.state.modal })
|
201 |
}
|
202 |
|
203 |
+
/**
|
204 |
+
* handel navagation open and close function
|
205 |
+
*/
|
206 |
handelNavbar = () => {
|
207 |
this.setState({open : !this.state.open, menu : this.state.menu, text: this.state.text, name: this.state.name, colour : this.state.colour, emoji : this.state.emoji, error : this.state.error, modal : this.state.modal })
|
208 |
}
|
209 |
|
210 |
+
/**
|
211 |
+
*
|
212 |
+
* @param {*} e : event type to get the target value of the current input
|
213 |
+
* @param {*} type : text | name string that set the changed value of the input to the current value
|
214 |
+
*/
|
215 |
updateText(e, type){
|
216 |
this.setState({open : this.state.open, menu : this.state.menu, text: type === "text" ? e.target.value : this.state.text, name: type === "name" ? e.target.value : this.state.name, colour : this.state.colour, emoji : this.state.emoji, error : this.state.error, modal : this.state.modal })
|
217 |
}
|
218 |
|
219 |
+
/**
|
220 |
+
*
|
221 |
+
* @param {*} item : object infomation from the flask api
|
222 |
+
* @param {*} index : current index with in the list
|
223 |
+
* @returns div component that contians infomation of gradio
|
224 |
+
*/
|
225 |
subComponents(item, index){
|
226 |
|
227 |
return(<>
|
|
|
269 |
<span className={`absolute inset-y-0 left-0 flex items-center pl-3`}>
|
270 |
<BsSearch className="block float-left cursor-pointer mr-2"/>
|
271 |
</span>
|
272 |
+
<input className={`placeholder:italic placeholder:text-slate-400 block bg-transparent w-full border border-slate-300 border-dashed rounded-md py-2 pl-9 ${this.state.open ? "pr-3" : "hidden"} shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm bg-transparent`}
|
273 |
placeholder={`stream link...`}
|
274 |
type="text" name="search"
|
275 |
onChange={(e) => {
|
|
|
296 |
|
297 |
{ this.state.error &&
|
298 |
<Message negative>
|
299 |
+
<Message.Header className=" text-lg text-center">π« Something went wrong...</Message.Header>
|
300 |
+
<br/>
|
301 |
+
<h1 className=" underline pb-3 font-bold text-lg">π€ Possible Things That could of happen <br/></h1>
|
302 |
+
<ul className="font-bold">
|
303 |
+
<li>- The input was empty</li>
|
304 |
+
<li>- The connection was forbidden</li>
|
305 |
+
<li>- The name was already taken</li>
|
306 |
+
<li>- The link you gave did not pass the regex</li>
|
307 |
+
<ul className="px-6">
|
308 |
+
<li>- http://localhost:xxxx</li>
|
309 |
+
<li>- http://xxxxx.gradio.app</li>
|
310 |
+
<li>- https://hf.space/embed/$user/$space_name/+</li>
|
311 |
+
</ul>
|
312 |
+
<li>- link already exist within the menu</li>
|
313 |
+
</ul>
|
314 |
+
|
315 |
</Message>
|
316 |
}
|
317 |
|
frontend/src/{Components β components}/Nodes/Custom.js
RENAMED
@@ -1,6 +1,6 @@
|
|
1 |
import React from "react"
|
2 |
import {TbResize} from 'react-icons/tb'
|
3 |
-
import {BiCube} from 'react-icons/bi'
|
4 |
import {BsTrash} from 'react-icons/bs'
|
5 |
import {CgLayoutGridSmall} from 'react-icons/cg'
|
6 |
import '../../css/counter.css'
|
@@ -14,19 +14,20 @@ export default class CustomNodeIframe extends React.Component {
|
|
14 |
reachable : this.isFetchable(data.host),
|
15 |
selected : true,
|
16 |
data : data,
|
17 |
-
width :
|
18 |
-
height :
|
19 |
-
size : false
|
|
|
20 |
}
|
21 |
|
22 |
}
|
23 |
|
24 |
handelSelected = () => {
|
25 |
-
this.setState({id : this.state.id, reachable : this.state.reachable, selected : !this.state.selected, data : this.state.data, width : this.state.width, height : this.state.height, size : this.state.size})
|
26 |
}
|
27 |
|
28 |
handelSizeState = () => {
|
29 |
-
this.setState({id : this.state.id, reachable : this.state.reachable, selected : this.state.selected, data : this.state.data, width : this.state.width, height : this.state.height, size : !this.state.size})
|
30 |
}
|
31 |
|
32 |
isFetchable = async (host) => {
|
@@ -45,14 +46,21 @@ export default class CustomNodeIframe extends React.Component {
|
|
45 |
this.state.data.delete(id)
|
46 |
}
|
47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
handelOnChange(evt, type){
|
49 |
-
this.setState({id : this.state.id, reachable : this.state.reachable, selected : this.state.selected, data : this.state.data, width : type === "width" ? parseInt(evt.target.value) : this.state.width, height : type === "height" ? parseInt(evt.target.value) : this.state.height, size : this.state.size})
|
50 |
type === "width" ? this.myRef.current.style.width = `${parseInt(evt.target.value)}px` : this.myRef.current.style.height = `${parseInt(evt.target.value)}px`
|
51 |
}
|
52 |
|
53 |
handelSize(evt, increment, change){
|
54 |
if (evt === "increment") {
|
55 |
-
this.setState({id : this.state.id, reachable : this.state.reachable, selected : this.state.selected, data : this.state.data, width : change === "width" ? this.state.width + increment : this.state.width, height : change === "height" ? this.state.height + increment : this.state.height, size : this.state.size})
|
56 |
change === "width" ? this.myRef.current.style.width = `${this.state.width + increment}px` : this.myRef.current.style.height = `${this.state.height + increment}px`
|
57 |
}
|
58 |
|
@@ -77,15 +85,16 @@ export default class CustomNodeIframe extends React.Component {
|
|
77 |
return (<>
|
78 |
<>
|
79 |
<div className=" flex w-full h-10 top-0 cursor-pointer" onClick={this.handelEvent}>
|
80 |
-
<div title="Collaspse Node" className=" duration-300 cursor-pointer shadow-xl border-2
|
81 |
|
82 |
|
83 |
<div className={` flex ${this.state.selected ? '' : 'w-0 hidden'}`}>
|
84 |
<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={this.handelSizeState}><TbResize className="h-full w-full text-white p-1"/></div>
|
85 |
<a href={this.state.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>
|
86 |
<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={() => this.onNodeClick(this.state.id)}><BsTrash className="h-full w-full text-white p-1"/></div>
|
87 |
-
|
88 |
-
|
|
|
89 |
{this.Counter("width", this.state.width)}
|
90 |
{this.Counter("height", this.state.height)}
|
91 |
</div>}
|
@@ -97,11 +106,11 @@ export default class CustomNodeIframe extends React.Component {
|
|
97 |
<div className={`absolute h-full w-full ${this.state.data.colour} border-1shadow-2xl shadow-black rounded-xl -z-20`}></div>
|
98 |
<iframe
|
99 |
id="iframe"
|
|
|
100 |
src={this.state.data.host}
|
101 |
title={this.state.data.label}
|
102 |
frameBorder="0"
|
103 |
className=" -z-10 container h-full p-2 flex-grow space-iframe overflow-scroll "
|
104 |
-
allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking"
|
105 |
sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
|
106 |
</div>
|
107 |
</>
|
|
|
1 |
import React from "react"
|
2 |
import {TbResize} from 'react-icons/tb'
|
3 |
+
import {BiCube, BiRefresh} from 'react-icons/bi'
|
4 |
import {BsTrash} from 'react-icons/bs'
|
5 |
import {CgLayoutGridSmall} from 'react-icons/cg'
|
6 |
import '../../css/counter.css'
|
|
|
14 |
reachable : this.isFetchable(data.host),
|
15 |
selected : true,
|
16 |
data : data,
|
17 |
+
width : 540,
|
18 |
+
height : 600,
|
19 |
+
size : false,
|
20 |
+
iframe : 0
|
21 |
}
|
22 |
|
23 |
}
|
24 |
|
25 |
handelSelected = () => {
|
26 |
+
this.setState({id : this.state.id, reachable : this.state.reachable, selected : !this.state.selected, data : this.state.data, width : this.state.width, height : this.state.height, size : this.state.size, iframe : this.state.iframe})
|
27 |
}
|
28 |
|
29 |
handelSizeState = () => {
|
30 |
+
this.setState({id : this.state.id, reachable : this.state.reachable, selected : this.state.selected, data : this.state.data, width : this.state.width, height : this.state.height, size : !this.state.size, iframe : this.state.iframe})
|
31 |
}
|
32 |
|
33 |
isFetchable = async (host) => {
|
|
|
46 |
this.state.data.delete(id)
|
47 |
}
|
48 |
|
49 |
+
onRefresh(){
|
50 |
+
if(!this.isFetchable) this.onNodeClick(this.state.id)
|
51 |
+
else{
|
52 |
+
this.setState({id : this.state.id, reachable : this.state.reachable, selected : this.state.selected, data : this.state.data, width : this.state.width, height : this.state.height, size : this.state.size, iframe : this.state.iframe + 1})
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
handelOnChange(evt, type){
|
57 |
+
this.setState({id : this.state.id, reachable : this.state.reachable, selected : this.state.selected, data : this.state.data, width : type === "width" ? parseInt(evt.target.value) : this.state.width, height : type === "height" ? parseInt(evt.target.value) : this.state.height, size : this.state.size, iframe : this.state.iframe})
|
58 |
type === "width" ? this.myRef.current.style.width = `${parseInt(evt.target.value)}px` : this.myRef.current.style.height = `${parseInt(evt.target.value)}px`
|
59 |
}
|
60 |
|
61 |
handelSize(evt, increment, change){
|
62 |
if (evt === "increment") {
|
63 |
+
this.setState({id : this.state.id, reachable : this.state.reachable, selected : this.state.selected, data : this.state.data, width : change === "width" ? this.state.width + increment : this.state.width, height : change === "height" ? this.state.height + increment : this.state.height, size : this.state.size, iframe : this.state.iframe})
|
64 |
change === "width" ? this.myRef.current.style.width = `${this.state.width + increment}px` : this.myRef.current.style.height = `${this.state.height + increment}px`
|
65 |
}
|
66 |
|
|
|
85 |
return (<>
|
86 |
<>
|
87 |
<div className=" flex w-full h-10 top-0 cursor-pointer" onClick={this.handelEvent}>
|
88 |
+
<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>
|
89 |
|
90 |
|
91 |
<div className={` flex ${this.state.selected ? '' : 'w-0 hidden'}`}>
|
92 |
<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={this.handelSizeState}><TbResize className="h-full w-full text-white p-1"/></div>
|
93 |
<a href={this.state.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>
|
94 |
<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={() => this.onNodeClick(this.state.id)}><BsTrash className="h-full w-full text-white p-1"/></div>
|
95 |
+
<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={() => this.onRefresh()}><BiRefresh className="h-full w-full text-white p-1"/></div>
|
96 |
+
|
97 |
+
{ this.state.size && <div className="duration-300 flex w-auto h-full mr-2 -mt-3 space-x-4">
|
98 |
{this.Counter("width", this.state.width)}
|
99 |
{this.Counter("height", this.state.height)}
|
100 |
</div>}
|
|
|
106 |
<div className={`absolute h-full w-full ${this.state.data.colour} border-1shadow-2xl shadow-black rounded-xl -z-20`}></div>
|
107 |
<iframe
|
108 |
id="iframe"
|
109 |
+
key={this.state.iframe}
|
110 |
src={this.state.data.host}
|
111 |
title={this.state.data.label}
|
112 |
frameBorder="0"
|
113 |
className=" -z-10 container h-full p-2 flex-grow space-iframe overflow-scroll "
|
|
|
114 |
sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
|
115 |
</div>
|
116 |
</>
|
frontend/src/{Components β components}/ReactFlow/ReactFlowEnv.js
RENAMED
@@ -1,6 +1,5 @@
|
|
1 |
import CustomNodeIframe from "../Nodes/Custom.js";
|
2 |
import '../../css/dist/output.css'
|
3 |
-
import '../../css/CustomNode.css'
|
4 |
import ReactFlow, { Background,
|
5 |
applyNodeChanges,
|
6 |
applyEdgeChanges,
|
@@ -16,14 +15,13 @@ const types = {
|
|
16 |
|
17 |
export default function ReactEnviorment() {
|
18 |
|
19 |
-
const [theme, setTheme] = useState(useThemeDetector
|
20 |
const [nodes, setNodes] = useState([]);
|
21 |
const [edges, setEdges] = useState([]);
|
22 |
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
23 |
const reactFlowWrapper = useRef(null);
|
24 |
|
25 |
|
26 |
-
|
27 |
const onNodesChange = useCallback(
|
28 |
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
29 |
[setNodes]
|
@@ -40,7 +38,7 @@ export default function ReactEnviorment() {
|
|
40 |
}, []);
|
41 |
|
42 |
const deleteNode = (id) =>{setNodes((nds) => nds.filter(n => n.id !== id ))}
|
43 |
-
|
44 |
const onDrop = useCallback(
|
45 |
(event) => {
|
46 |
event.preventDefault();
|
@@ -74,7 +72,7 @@ export default function ReactEnviorment() {
|
|
74 |
<h1 className='text-4xl select-none' >{theme ? 'π' : 'βοΈ'}</h1>
|
75 |
</div>
|
76 |
<div className={`flex h-screen w-screen ${theme ? "dark" : ""} transition-all`}>
|
77 |
-
<Navbar/>
|
78 |
<ReactFlowProvider>
|
79 |
<div className="h-screen w-screen" ref={reactFlowWrapper}>
|
80 |
<ReactFlow nodes={nodes} edges={edges} nodeTypes={types} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onNodesDelete={deleteNode} onDragOver={onDragOver} onDrop={onDrop} onInit={setReactFlowInstance} fitView>
|
|
|
1 |
import CustomNodeIframe from "../Nodes/Custom.js";
|
2 |
import '../../css/dist/output.css'
|
|
|
3 |
import ReactFlow, { Background,
|
4 |
applyNodeChanges,
|
5 |
applyEdgeChanges,
|
|
|
15 |
|
16 |
export default function ReactEnviorment() {
|
17 |
|
18 |
+
const [theme, setTheme] = useState(useThemeDetector)
|
19 |
const [nodes, setNodes] = useState([]);
|
20 |
const [edges, setEdges] = useState([]);
|
21 |
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
22 |
const reactFlowWrapper = useRef(null);
|
23 |
|
24 |
|
|
|
25 |
const onNodesChange = useCallback(
|
26 |
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
27 |
[setNodes]
|
|
|
38 |
}, []);
|
39 |
|
40 |
const deleteNode = (id) =>{setNodes((nds) => nds.filter(n => n.id !== id ))}
|
41 |
+
const deleteNodeContains = (id) =>{setNodes((nds) => nds.filter(n => !n.id.includes(`${id}-`) ))}
|
42 |
const onDrop = useCallback(
|
43 |
(event) => {
|
44 |
event.preventDefault();
|
|
|
72 |
<h1 className='text-4xl select-none' >{theme ? 'π' : 'βοΈ'}</h1>
|
73 |
</div>
|
74 |
<div className={`flex h-screen w-screen ${theme ? "dark" : ""} transition-all`}>
|
75 |
+
<Navbar onDelete={deleteNodeContains}/>
|
76 |
<ReactFlowProvider>
|
77 |
<div className="h-screen w-screen" ref={reactFlowWrapper}>
|
78 |
<ReactFlow nodes={nodes} edges={edges} nodeTypes={types} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onNodesDelete={deleteNode} onDragOver={onDragOver} onDrop={onDrop} onInit={setReactFlowInstance} fitView>
|
frontend/src/css/CustomNode.css
DELETED
@@ -1,46 +0,0 @@
|
|
1 |
-
.hexagon {
|
2 |
-
position: relative;
|
3 |
-
width: 200px;
|
4 |
-
height: 115.47px;
|
5 |
-
background-color: #ffffff;
|
6 |
-
margin: 57.74px 0;
|
7 |
-
border-left: solid 3px #000000;
|
8 |
-
border-right: solid 3px #000000;
|
9 |
-
margin-left: auto;
|
10 |
-
margin-right: auto;
|
11 |
-
border-image-slice: 1;
|
12 |
-
border-image-source: linear-gradient(to left, #2de2e6, #f6019d);
|
13 |
-
}
|
14 |
-
|
15 |
-
|
16 |
-
.hexagon:before,
|
17 |
-
.hexagon:after {
|
18 |
-
content: "";
|
19 |
-
position: absolute;
|
20 |
-
z-index: -1;
|
21 |
-
width: 141.42px;
|
22 |
-
height: 141.42px;
|
23 |
-
-webkit-transform: scaleY(0.5774) rotate(-30deg);
|
24 |
-
-ms-transform: scaleY(0.5774) rotate(-30deg);
|
25 |
-
transform: scaleY(0.5774) rotate(-45deg);
|
26 |
-
background-color: inherit;
|
27 |
-
left: 27.2893px;
|
28 |
-
}
|
29 |
-
|
30 |
-
.hexagon:before {
|
31 |
-
top: -70.7107px;
|
32 |
-
border-top: solid 4.8284px #000000;
|
33 |
-
border-right: solid 4.8284px #000000;
|
34 |
-
border-image-slice: 1;
|
35 |
-
border-image-source: linear-gradient(to left, #2de2e6, #f6019d);
|
36 |
-
|
37 |
-
}
|
38 |
-
|
39 |
-
.hexagon:after {
|
40 |
-
bottom: -70.7107px;
|
41 |
-
border-bottom: solid 4.8284px #000000;
|
42 |
-
border-left: solid 4.8284px;
|
43 |
-
border-image-slice: 1;
|
44 |
-
border-image-source: linear-gradient(to left, #2de2e6, #f6019d);
|
45 |
-
|
46 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/css/dist/output.css
CHANGED
@@ -744,22 +744,18 @@ video {
|
|
744 |
width: 0px;
|
745 |
}
|
746 |
|
747 |
-
.w
|
748 |
-
width:
|
749 |
-
}
|
750 |
-
|
751 |
-
.w-screen {
|
752 |
-
width: 100vw;
|
753 |
-
}
|
754 |
-
|
755 |
-
.w-\[560px\] {
|
756 |
-
width: 560px;
|
757 |
}
|
758 |
|
759 |
.w-\[540px\] {
|
760 |
width: 540px;
|
761 |
}
|
762 |
|
|
|
|
|
|
|
|
|
763 |
.flex-1 {
|
764 |
flex: 1 1 0%;
|
765 |
}
|
@@ -928,6 +924,11 @@ video {
|
|
928 |
background-color: rgb(255 96 93 / var(--tw-bg-opacity));
|
929 |
}
|
930 |
|
|
|
|
|
|
|
|
|
|
|
931 |
.bg-gradient-to-bl {
|
932 |
background-image: linear-gradient(to bottom left, var(--tw-gradient-stops));
|
933 |
}
|
@@ -992,6 +993,30 @@ video {
|
|
992 |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
993 |
}
|
994 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
995 |
.via-purple-500 {
|
996 |
--tw-gradient-to: rgb(168 85 247 / 0);
|
997 |
--tw-gradient-stops: var(--tw-gradient-from), #a855f7, var(--tw-gradient-to);
|
@@ -1017,11 +1042,6 @@ video {
|
|
1017 |
--tw-gradient-stops: var(--tw-gradient-from), #F1A089, var(--tw-gradient-to);
|
1018 |
}
|
1019 |
|
1020 |
-
.via-Happy-Light-Magenta {
|
1021 |
-
--tw-gradient-to: rgb(207 123 198 / 0);
|
1022 |
-
--tw-gradient-stops: var(--tw-gradient-from), #CF7BC6, var(--tw-gradient-to);
|
1023 |
-
}
|
1024 |
-
|
1025 |
.via-Happy-Indego-Purple {
|
1026 |
--tw-gradient-to: rgb(148 98 233 / 0);
|
1027 |
--tw-gradient-stops: var(--tw-gradient-from), #9462E9, var(--tw-gradient-to);
|
@@ -1073,6 +1093,22 @@ video {
|
|
1073 |
--tw-gradient-to: #319B72;
|
1074 |
}
|
1075 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1076 |
.p-5 {
|
1077 |
padding: 1.25rem;
|
1078 |
}
|
@@ -1113,6 +1149,11 @@ video {
|
|
1113 |
padding-bottom: 0.5rem;
|
1114 |
}
|
1115 |
|
|
|
|
|
|
|
|
|
|
|
1116 |
.pt-8 {
|
1117 |
padding-top: 2rem;
|
1118 |
}
|
@@ -1141,6 +1182,10 @@ video {
|
|
1141 |
padding-top: 0.5rem;
|
1142 |
}
|
1143 |
|
|
|
|
|
|
|
|
|
1144 |
.text-left {
|
1145 |
text-align: left;
|
1146 |
}
|
@@ -1200,6 +1245,11 @@ video {
|
|
1200 |
color: rgb(0 0 0 / var(--tw-text-opacity));
|
1201 |
}
|
1202 |
|
|
|
|
|
|
|
|
|
|
|
1203 |
.shadow-lg {
|
1204 |
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
1205 |
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
@@ -1271,6 +1321,14 @@ video {
|
|
1271 |
color: rgb(148 163 184 / var(--tw-text-opacity));
|
1272 |
}
|
1273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1274 |
@-webkit-keyframes pulse {
|
1275 |
50% {
|
1276 |
opacity: .5;
|
|
|
744 |
width: 0px;
|
745 |
}
|
746 |
|
747 |
+
.w-auto {
|
748 |
+
width: auto;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
749 |
}
|
750 |
|
751 |
.w-\[540px\] {
|
752 |
width: 540px;
|
753 |
}
|
754 |
|
755 |
+
.w-screen {
|
756 |
+
width: 100vw;
|
757 |
+
}
|
758 |
+
|
759 |
.flex-1 {
|
760 |
flex: 1 1 0%;
|
761 |
}
|
|
|
924 |
background-color: rgb(255 96 93 / var(--tw-bg-opacity));
|
925 |
}
|
926 |
|
927 |
+
.bg-Warm-Orange {
|
928 |
+
--tw-bg-opacity: 1;
|
929 |
+
background-color: rgb(254 169 89 / var(--tw-bg-opacity));
|
930 |
+
}
|
931 |
+
|
932 |
.bg-gradient-to-bl {
|
933 |
background-image: linear-gradient(to bottom left, var(--tw-gradient-stops));
|
934 |
}
|
|
|
993 |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
994 |
}
|
995 |
|
996 |
+
.from-Amethyst-Light {
|
997 |
+
--tw-gradient-from: #9D50BB;
|
998 |
+
--tw-gradient-to: rgb(157 80 187 / 0);
|
999 |
+
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1000 |
+
}
|
1001 |
+
|
1002 |
+
.from-Peach-Red {
|
1003 |
+
--tw-gradient-from: #ED4264;
|
1004 |
+
--tw-gradient-to: rgb(237 66 100 / 0);
|
1005 |
+
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1006 |
+
}
|
1007 |
+
|
1008 |
+
.from-Deep-Space-Black {
|
1009 |
+
--tw-gradient-from: #000000;
|
1010 |
+
--tw-gradient-to: rgb(0 0 0 / 0);
|
1011 |
+
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1012 |
+
}
|
1013 |
+
|
1014 |
+
.from-Sunshine-Red {
|
1015 |
+
--tw-gradient-from: #b92b27;
|
1016 |
+
--tw-gradient-to: rgb(185 43 39 / 0);
|
1017 |
+
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
1018 |
+
}
|
1019 |
+
|
1020 |
.via-purple-500 {
|
1021 |
--tw-gradient-to: rgb(168 85 247 / 0);
|
1022 |
--tw-gradient-stops: var(--tw-gradient-from), #a855f7, var(--tw-gradient-to);
|
|
|
1042 |
--tw-gradient-stops: var(--tw-gradient-from), #F1A089, var(--tw-gradient-to);
|
1043 |
}
|
1044 |
|
|
|
|
|
|
|
|
|
|
|
1045 |
.via-Happy-Indego-Purple {
|
1046 |
--tw-gradient-to: rgb(148 98 233 / 0);
|
1047 |
--tw-gradient-stops: var(--tw-gradient-from), #9462E9, var(--tw-gradient-to);
|
|
|
1093 |
--tw-gradient-to: #319B72;
|
1094 |
}
|
1095 |
|
1096 |
+
.to-Amethyst-Dark {
|
1097 |
+
--tw-gradient-to: #6E48AA;
|
1098 |
+
}
|
1099 |
+
|
1100 |
+
.to-Peach-Yello {
|
1101 |
+
--tw-gradient-to: #FFEDBC;
|
1102 |
+
}
|
1103 |
+
|
1104 |
+
.to-Deep-Space-Gray {
|
1105 |
+
--tw-gradient-to: #434343;
|
1106 |
+
}
|
1107 |
+
|
1108 |
+
.to-Sunshine-Blue {
|
1109 |
+
--tw-gradient-to: #1565C0;
|
1110 |
+
}
|
1111 |
+
|
1112 |
.p-5 {
|
1113 |
padding: 1.25rem;
|
1114 |
}
|
|
|
1149 |
padding-bottom: 0.5rem;
|
1150 |
}
|
1151 |
|
1152 |
+
.px-6 {
|
1153 |
+
padding-left: 1.5rem;
|
1154 |
+
padding-right: 1.5rem;
|
1155 |
+
}
|
1156 |
+
|
1157 |
.pt-8 {
|
1158 |
padding-top: 2rem;
|
1159 |
}
|
|
|
1182 |
padding-top: 0.5rem;
|
1183 |
}
|
1184 |
|
1185 |
+
.pb-3 {
|
1186 |
+
padding-bottom: 0.75rem;
|
1187 |
+
}
|
1188 |
+
|
1189 |
.text-left {
|
1190 |
text-align: left;
|
1191 |
}
|
|
|
1245 |
color: rgb(0 0 0 / var(--tw-text-opacity));
|
1246 |
}
|
1247 |
|
1248 |
+
.underline {
|
1249 |
+
-webkit-text-decoration-line: underline;
|
1250 |
+
text-decoration-line: underline;
|
1251 |
+
}
|
1252 |
+
|
1253 |
.shadow-lg {
|
1254 |
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
1255 |
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
|
|
1321 |
color: rgb(148 163 184 / var(--tw-text-opacity));
|
1322 |
}
|
1323 |
|
1324 |
+
.placeholder\:text-transparent::-webkit-input-placeholder {
|
1325 |
+
color: transparent;
|
1326 |
+
}
|
1327 |
+
|
1328 |
+
.placeholder\:text-transparent::placeholder {
|
1329 |
+
color: transparent;
|
1330 |
+
}
|
1331 |
+
|
1332 |
@-webkit-keyframes pulse {
|
1333 |
50% {
|
1334 |
opacity: .5;
|
frontend/src/css/iframe.css
DELETED
@@ -1,5 +0,0 @@
|
|
1 |
-
.Iframe_class{
|
2 |
-
position: absolute;
|
3 |
-
width: 100%!important;
|
4 |
-
height: 100%!important;
|
5 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/helper/visual.js
CHANGED
@@ -1,38 +1,41 @@
|
|
1 |
import '../css/dist/output.css'
|
2 |
-
const emote = ['πΊ',
|
3 |
|
4 |
-
const colour_map =
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
17 |
|
18 |
/**
|
19 |
-
*
|
20 |
-
*
|
21 |
-
"Warm-Violet" : "#8D379E",
|
22 |
-
"Warm-Pink" : "#F13484",
|
23 |
-
"Warm-Red" : "#FF605D",
|
24 |
-
"Warm-Orange" : "#FEA959",
|
25 |
-
"Warm-Yellow" : "#FEE27A",
|
26 |
*/
|
27 |
-
|
28 |
export const random_emoji = () =>{
|
29 |
return emote[Math.floor(Math.random() * emote.length)]
|
30 |
}
|
31 |
|
|
|
|
|
|
|
|
|
32 |
export const random_colour = () => {
|
33 |
-
return colour_map[Math.floor(Math.random() *
|
34 |
}
|
35 |
|
|
|
36 |
export const list_of_null = (idx) => {
|
37 |
var list = []
|
38 |
for(var i = 0; i < idx; i++ ) {
|
@@ -41,8 +44,11 @@ export const list_of_null = (idx) => {
|
|
41 |
return list
|
42 |
}
|
43 |
|
44 |
-
|
|
|
|
|
|
|
45 |
export const useThemeDetector = () => {
|
46 |
const getCurrentTheme = () => window.matchMedia("(prefers-color-scheme: dark)").matches;
|
47 |
-
return getCurrentTheme;
|
48 |
}
|
|
|
1 |
import '../css/dist/output.css'
|
2 |
+
const emote = ['πΊ','π','π','πΎ','π€','π₯','π§ ','πΏ','π¦Ύ','π¦','β¨','π‘','π΅','π¦','π','π','π₯','π','π','π§¬','π','π','π','π','π±','π']
|
3 |
|
4 |
+
const colour_map = [
|
5 |
+
'bg-gradient-to-bl from-Retro-light-blue to-Retro-light-pink',
|
6 |
+
'bg-gradient-to-bl from-Vapor-Violet to-Vapor-Orange',
|
7 |
+
'bg-gradient-to-bl from-Retro-purple to-Vapor-Pink',
|
8 |
+
'bg-gradient-to-bl from-Retro-purple to-Vapor-Blue',
|
9 |
+
'bg-gradient-to-bl from-Retro-light-pink to-Vapor-Blue',
|
10 |
+
'bg-gradient-to-bl from-indigo-500 via-purple-500 to-pink-500',
|
11 |
+
'bg-gradient-to-bl from-Vapor-Rose to-Vapor-Blue',
|
12 |
+
'bg-gradient-to-bl from-Warm-Blue via-Warm-Pink via-Warm-Red via-Warm-Orange to-Warm-Yellow',
|
13 |
+
'bg-gradient-to-bl from-Happy-Yellow via-Happy-Tangerine via-Happy-Indego-Purple via-Cool-Blue to-Happy-Sea-Blue',
|
14 |
+
'bg-gradient-to-bl from-Blue-Turquoise via-Blue-Midtone to-Blue-Royal',
|
15 |
+
'bg-gradient-to-bl from-Green-Black via-Green-Forest to-Green-Emerald',
|
16 |
+
'bg-gradient-to-bl from-Amethyst-Light to-Amethyst-Dark',
|
17 |
+
'bg-gradient-to-bl from-Peach-Red to-Peach-Yello',
|
18 |
+
'bg-gradient-to-bl from-Deep-Space-Black to-Deep-Space-Gray',
|
19 |
+
'bg-gradient-to-bl from-Sunshine-Red to-Sunshine-Blue'
|
20 |
+
]
|
21 |
|
22 |
/**
|
23 |
+
* Get a random emoji from emote array
|
24 |
+
* @returns random emoji from emote array
|
|
|
|
|
|
|
|
|
|
|
25 |
*/
|
|
|
26 |
export const random_emoji = () =>{
|
27 |
return emote[Math.floor(Math.random() * emote.length)]
|
28 |
}
|
29 |
|
30 |
+
/**
|
31 |
+
* Get a random color string from colour_map array
|
32 |
+
* @returns random color css string
|
33 |
+
*/
|
34 |
export const random_colour = () => {
|
35 |
+
return colour_map[Math.floor(Math.random() * colour_map.length)]
|
36 |
}
|
37 |
|
38 |
+
|
39 |
export const list_of_null = (idx) => {
|
40 |
var list = []
|
41 |
for(var i = 0; i < idx; i++ ) {
|
|
|
44 |
return list
|
45 |
}
|
46 |
|
47 |
+
/**
|
48 |
+
*
|
49 |
+
* @returns
|
50 |
+
*/
|
51 |
export const useThemeDetector = () => {
|
52 |
const getCurrentTheme = () => window.matchMedia("(prefers-color-scheme: dark)").matches;
|
53 |
+
return getCurrentTheme();
|
54 |
}
|
frontend/tailwind.config.js
CHANGED
@@ -7,6 +7,14 @@ module.exports = {
|
|
7 |
extend: {
|
8 |
|
9 |
colors : {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
"Retro-light-blue" : "#2de2e6",
|
11 |
"Retro-dark-blue" : "#035ee8",
|
12 |
"Retro-light-pink" : "#f6019d",
|
|
|
7 |
extend: {
|
8 |
|
9 |
colors : {
|
10 |
+
"Amethyst-Light" : "#9D50BB",
|
11 |
+
"Amethyst-Dark" : "#6E48AA",
|
12 |
+
"Peach-Red" : "#ED4264",
|
13 |
+
"Peach-Yello" : "#FFEDBC",
|
14 |
+
"Deep-Space-Black" : "#000000",
|
15 |
+
"Deep-Space-Gray" : "#434343",
|
16 |
+
"Sunshine-Red" : "#b92b27",
|
17 |
+
"Sunshine-Blue" : "#1565C0",
|
18 |
"Retro-light-blue" : "#2de2e6",
|
19 |
"Retro-dark-blue" : "#035ee8",
|
20 |
"Retro-light-pink" : "#f6019d",
|
images/application_dark.png
CHANGED
![]() |
Git LFS Details
|
![]() |
Git LFS Details
|
images/application_light.png
CHANGED
![]() |
Git LFS Details
|
![]() |
Git LFS Details
|