Spaces:
Running
Running
Some nodes are now visible.
Browse files- server/crdt.py +44 -21
- web/app/globals.css +86 -17
- web/app/layout.tsx +3 -15
- web/app/workspace/EnvironmentSelector.tsx +11 -8
- web/app/workspace/page.tsx +4 -221
- web/package-lock.json +105 -0
- web/tailwind.config.js +15 -1
- web/tsconfig.json +1 -0
server/crdt.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
-
|
|
|
|
| 2 |
import asyncio
|
| 3 |
import contextlib
|
|
|
|
| 4 |
import fastapi
|
| 5 |
import os.path
|
| 6 |
import pycrdt
|
|
@@ -10,33 +12,39 @@ import pycrdt_websocket.ystore
|
|
| 10 |
|
| 11 |
router = fastapi.APIRouter()
|
| 12 |
|
|
|
|
| 13 |
def ws_exception_handler(exception, log):
|
| 14 |
-
print(
|
| 15 |
log.exception(exception)
|
| 16 |
return True
|
| 17 |
|
|
|
|
| 18 |
class WebsocketServer(pycrdt_websocket.WebsocketServer):
|
| 19 |
async def init_room(self, name):
|
| 20 |
-
ystore = pycrdt_websocket.ystore.FileYStore(f
|
| 21 |
ydoc = pycrdt.Doc()
|
| 22 |
-
ydoc[
|
| 23 |
# Replay updates from the store.
|
| 24 |
try:
|
| 25 |
-
for update, timestamp in [
|
|
|
|
|
|
|
| 26 |
ydoc.apply_update(update)
|
| 27 |
except pycrdt_websocket.ystore.YDocNotFound:
|
| 28 |
pass
|
| 29 |
-
if
|
| 30 |
-
ws[
|
| 31 |
-
if
|
| 32 |
-
ws[
|
| 33 |
-
if
|
| 34 |
-
ws[
|
| 35 |
try_to_load_workspace(ws, name)
|
| 36 |
room = pycrdt_websocket.YRoom(ystore=ystore, ydoc=ydoc)
|
| 37 |
room.ws = ws
|
|
|
|
| 38 |
def on_change(changes):
|
| 39 |
asyncio.create_task(workspace_changed(changes, ws))
|
|
|
|
| 40 |
ws.observe_deep(on_change)
|
| 41 |
return room
|
| 42 |
|
|
@@ -47,10 +55,16 @@ class WebsocketServer(pycrdt_websocket.WebsocketServer):
|
|
| 47 |
await self.start_room(room)
|
| 48 |
return room
|
| 49 |
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
asgi_server = pycrdt_websocket.ASGIServer(websocket_server)
|
| 52 |
|
| 53 |
last_ws_input = None
|
|
|
|
|
|
|
| 54 |
def clean_input(ws_pyd):
|
| 55 |
for node in ws_pyd.nodes:
|
| 56 |
node.data.display = None
|
|
@@ -60,6 +74,7 @@ def clean_input(ws_pyd):
|
|
| 60 |
for key in list(node.model_extra.keys()):
|
| 61 |
delattr(node, key)
|
| 62 |
|
|
|
|
| 63 |
def crdt_update(crdt_obj, python_obj, boxes=set()):
|
| 64 |
if isinstance(python_obj, dict):
|
| 65 |
for key, value in python_obj.items():
|
|
@@ -73,6 +88,8 @@ def crdt_update(crdt_obj, python_obj, boxes=set()):
|
|
| 73 |
if crdt_obj.get(key) is None:
|
| 74 |
crdt_obj[key] = pycrdt.Array()
|
| 75 |
crdt_update(crdt_obj[key], value, boxes)
|
|
|
|
|
|
|
| 76 |
else:
|
| 77 |
crdt_obj[key] = value
|
| 78 |
elif isinstance(python_obj, list):
|
|
@@ -91,41 +108,47 @@ def crdt_update(crdt_obj, python_obj, boxes=set()):
|
|
| 91 |
else:
|
| 92 |
crdt_obj[i] = value
|
| 93 |
else:
|
| 94 |
-
raise ValueError(
|
| 95 |
|
| 96 |
|
| 97 |
def try_to_load_workspace(ws, name):
|
| 98 |
from . import workspace
|
| 99 |
-
|
|
|
|
| 100 |
if os.path.exists(json_path):
|
| 101 |
ws_pyd = workspace.load(json_path)
|
| 102 |
-
crdt_update(ws, ws_pyd.model_dump(), boxes={
|
|
|
|
| 103 |
|
| 104 |
async def workspace_changed(e, ws_crdt):
|
| 105 |
global last_ws_input
|
| 106 |
from . import workspace
|
|
|
|
| 107 |
ws_pyd = workspace.Workspace.model_validate(ws_crdt.to_py())
|
| 108 |
clean_input(ws_pyd)
|
| 109 |
if ws_pyd == last_ws_input:
|
| 110 |
return
|
| 111 |
last_ws_input = ws_pyd.model_copy(deep=True)
|
| 112 |
await workspace.execute(ws_pyd)
|
| 113 |
-
for nc, np in zip(ws_crdt[
|
| 114 |
-
if
|
| 115 |
-
nc[
|
| 116 |
# Display is added as an opaque Box.
|
| 117 |
-
nc[
|
| 118 |
-
nc[
|
|
|
|
| 119 |
|
| 120 |
@contextlib.asynccontextmanager
|
| 121 |
async def lifespan(app):
|
| 122 |
async with websocket_server:
|
| 123 |
yield
|
| 124 |
|
|
|
|
| 125 |
def sanitize_path(path):
|
| 126 |
return os.path.relpath(os.path.normpath(os.path.join("/", path)), "/")
|
| 127 |
|
|
|
|
| 128 |
@router.websocket("/ws/crdt/{room_name}")
|
| 129 |
async def crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
|
| 130 |
room_name = sanitize_path(room_name)
|
| 131 |
-
await asgi_server({
|
|
|
|
| 1 |
+
"""CRDT is used to synchronize workspace state for backend and frontend(s)."""
|
| 2 |
+
|
| 3 |
import asyncio
|
| 4 |
import contextlib
|
| 5 |
+
import enum
|
| 6 |
import fastapi
|
| 7 |
import os.path
|
| 8 |
import pycrdt
|
|
|
|
| 12 |
|
| 13 |
router = fastapi.APIRouter()
|
| 14 |
|
| 15 |
+
|
| 16 |
def ws_exception_handler(exception, log):
|
| 17 |
+
print("exception", exception)
|
| 18 |
log.exception(exception)
|
| 19 |
return True
|
| 20 |
|
| 21 |
+
|
| 22 |
class WebsocketServer(pycrdt_websocket.WebsocketServer):
|
| 23 |
async def init_room(self, name):
|
| 24 |
+
ystore = pycrdt_websocket.ystore.FileYStore(f"crdt_data/{name}.crdt")
|
| 25 |
ydoc = pycrdt.Doc()
|
| 26 |
+
ydoc["workspace"] = ws = pycrdt.Map()
|
| 27 |
# Replay updates from the store.
|
| 28 |
try:
|
| 29 |
+
for update, timestamp in [
|
| 30 |
+
(item[0], item[-1]) async for item in ystore.read()
|
| 31 |
+
]:
|
| 32 |
ydoc.apply_update(update)
|
| 33 |
except pycrdt_websocket.ystore.YDocNotFound:
|
| 34 |
pass
|
| 35 |
+
if "nodes" not in ws:
|
| 36 |
+
ws["nodes"] = pycrdt.Array()
|
| 37 |
+
if "edges" not in ws:
|
| 38 |
+
ws["edges"] = pycrdt.Array()
|
| 39 |
+
if "env" not in ws:
|
| 40 |
+
ws["env"] = "unset"
|
| 41 |
try_to_load_workspace(ws, name)
|
| 42 |
room = pycrdt_websocket.YRoom(ystore=ystore, ydoc=ydoc)
|
| 43 |
room.ws = ws
|
| 44 |
+
|
| 45 |
def on_change(changes):
|
| 46 |
asyncio.create_task(workspace_changed(changes, ws))
|
| 47 |
+
|
| 48 |
ws.observe_deep(on_change)
|
| 49 |
return room
|
| 50 |
|
|
|
|
| 55 |
await self.start_room(room)
|
| 56 |
return room
|
| 57 |
|
| 58 |
+
|
| 59 |
+
websocket_server = WebsocketServer(
|
| 60 |
+
# exception_handler=ws_exception_handler,
|
| 61 |
+
auto_clean_rooms=False,
|
| 62 |
+
)
|
| 63 |
asgi_server = pycrdt_websocket.ASGIServer(websocket_server)
|
| 64 |
|
| 65 |
last_ws_input = None
|
| 66 |
+
|
| 67 |
+
|
| 68 |
def clean_input(ws_pyd):
|
| 69 |
for node in ws_pyd.nodes:
|
| 70 |
node.data.display = None
|
|
|
|
| 74 |
for key in list(node.model_extra.keys()):
|
| 75 |
delattr(node, key)
|
| 76 |
|
| 77 |
+
|
| 78 |
def crdt_update(crdt_obj, python_obj, boxes=set()):
|
| 79 |
if isinstance(python_obj, dict):
|
| 80 |
for key, value in python_obj.items():
|
|
|
|
| 88 |
if crdt_obj.get(key) is None:
|
| 89 |
crdt_obj[key] = pycrdt.Array()
|
| 90 |
crdt_update(crdt_obj[key], value, boxes)
|
| 91 |
+
elif isinstance(value, enum.Enum):
|
| 92 |
+
crdt_obj[key] = str(value)
|
| 93 |
else:
|
| 94 |
crdt_obj[key] = value
|
| 95 |
elif isinstance(python_obj, list):
|
|
|
|
| 108 |
else:
|
| 109 |
crdt_obj[i] = value
|
| 110 |
else:
|
| 111 |
+
raise ValueError("Invalid type:", python_obj)
|
| 112 |
|
| 113 |
|
| 114 |
def try_to_load_workspace(ws, name):
|
| 115 |
from . import workspace
|
| 116 |
+
|
| 117 |
+
json_path = f"data/{name}"
|
| 118 |
if os.path.exists(json_path):
|
| 119 |
ws_pyd = workspace.load(json_path)
|
| 120 |
+
crdt_update(ws, ws_pyd.model_dump(), boxes={"display"})
|
| 121 |
+
|
| 122 |
|
| 123 |
async def workspace_changed(e, ws_crdt):
|
| 124 |
global last_ws_input
|
| 125 |
from . import workspace
|
| 126 |
+
|
| 127 |
ws_pyd = workspace.Workspace.model_validate(ws_crdt.to_py())
|
| 128 |
clean_input(ws_pyd)
|
| 129 |
if ws_pyd == last_ws_input:
|
| 130 |
return
|
| 131 |
last_ws_input = ws_pyd.model_copy(deep=True)
|
| 132 |
await workspace.execute(ws_pyd)
|
| 133 |
+
for nc, np in zip(ws_crdt["nodes"], ws_pyd.nodes):
|
| 134 |
+
if "data" not in nc:
|
| 135 |
+
nc["data"] = pycrdt.Map()
|
| 136 |
# Display is added as an opaque Box.
|
| 137 |
+
nc["data"]["display"] = np.data.display
|
| 138 |
+
nc["data"]["error"] = np.data.error
|
| 139 |
+
|
| 140 |
|
| 141 |
@contextlib.asynccontextmanager
|
| 142 |
async def lifespan(app):
|
| 143 |
async with websocket_server:
|
| 144 |
yield
|
| 145 |
|
| 146 |
+
|
| 147 |
def sanitize_path(path):
|
| 148 |
return os.path.relpath(os.path.normpath(os.path.join("/", path)), "/")
|
| 149 |
|
| 150 |
+
|
| 151 |
@router.websocket("/ws/crdt/{room_name}")
|
| 152 |
async def crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
|
| 153 |
room_name = sanitize_path(room_name)
|
| 154 |
+
await asgi_server({"path": room_name}, websocket._receive, websocket._send)
|
web/app/globals.css
CHANGED
|
@@ -2,18 +2,6 @@
|
|
| 2 |
@tailwind components;
|
| 3 |
@tailwind utilities;
|
| 4 |
|
| 5 |
-
:root {
|
| 6 |
-
--background: #ffffff;
|
| 7 |
-
--foreground: #171717;
|
| 8 |
-
}
|
| 9 |
-
|
| 10 |
-
/* @media (prefers-color-scheme: dark) {
|
| 11 |
-
:root {
|
| 12 |
-
--background: #0a0a0a;
|
| 13 |
-
--foreground: #ededed;
|
| 14 |
-
}
|
| 15 |
-
} */
|
| 16 |
-
|
| 17 |
body {
|
| 18 |
color: var(--foreground);
|
| 19 |
background: var(--background);
|
|
@@ -23,14 +11,15 @@ body {
|
|
| 23 |
.top-bar {
|
| 24 |
display: flex;
|
| 25 |
justify-content: space-between;
|
| 26 |
-
|
| 27 |
-
color: white;
|
| 28 |
}
|
| 29 |
.ws-name {
|
| 30 |
font-size: 1.5em;
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
-
.
|
| 33 |
-
height:
|
| 34 |
vertical-align: middle;
|
| 35 |
margin: 4px;
|
| 36 |
}
|
|
@@ -45,7 +34,87 @@ body {
|
|
| 45 |
align-items: center;
|
| 46 |
}
|
| 47 |
.tools a {
|
| 48 |
-
color: oklch(75% 0.13 230);
|
| 49 |
font-size: 1.5em;
|
| 50 |
padding: 0 10px;
|
| 51 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
@tailwind components;
|
| 3 |
@tailwind utilities;
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
body {
|
| 6 |
color: var(--foreground);
|
| 7 |
background: var(--background);
|
|
|
|
| 11 |
.top-bar {
|
| 12 |
display: flex;
|
| 13 |
justify-content: space-between;
|
| 14 |
+
align-items: center;
|
|
|
|
| 15 |
}
|
| 16 |
.ws-name {
|
| 17 |
font-size: 1.5em;
|
| 18 |
+
flex: 1;
|
| 19 |
+
color: white;
|
| 20 |
}
|
| 21 |
+
.top-bar .logo img {
|
| 22 |
+
height: 2em;
|
| 23 |
vertical-align: middle;
|
| 24 |
margin: 4px;
|
| 25 |
}
|
|
|
|
| 34 |
align-items: center;
|
| 35 |
}
|
| 36 |
.tools a {
|
|
|
|
| 37 |
font-size: 1.5em;
|
| 38 |
padding: 0 10px;
|
| 39 |
}
|
| 40 |
+
.error {
|
| 41 |
+
background: #ffdddd;
|
| 42 |
+
padding: 8px;
|
| 43 |
+
font-size: 12px;
|
| 44 |
+
}
|
| 45 |
+
.title-icon {
|
| 46 |
+
margin-left: 5px;
|
| 47 |
+
float: right;
|
| 48 |
+
}
|
| 49 |
+
.node-container {
|
| 50 |
+
padding: 8px;
|
| 51 |
+
position: relative;
|
| 52 |
+
}
|
| 53 |
+
.lynxkite-node {
|
| 54 |
+
box-shadow: 0px 5px 50px 0px rgba(0, 0, 0, 0.3);
|
| 55 |
+
border-radius: 4px;
|
| 56 |
+
background: white;
|
| 57 |
+
}
|
| 58 |
+
.expanded .lynxkite-node {
|
| 59 |
+
overflow-y: auto;
|
| 60 |
+
height: 100%;
|
| 61 |
+
}
|
| 62 |
+
.lynxkite-node .title {
|
| 63 |
+
/* background: oklch(75% 0.2 55); */
|
| 64 |
+
font-weight: bold;
|
| 65 |
+
padding: 8px;
|
| 66 |
+
}
|
| 67 |
+
.handle-name {
|
| 68 |
+
font-size: 10px;
|
| 69 |
+
color: black;
|
| 70 |
+
letter-spacing: 0.05em;
|
| 71 |
+
text-align: right;
|
| 72 |
+
white-space: nowrap;
|
| 73 |
+
position: absolute;
|
| 74 |
+
top: -5px;
|
| 75 |
+
backdrop-filter: blur(10px);
|
| 76 |
+
padding: 2px 8px;
|
| 77 |
+
border-radius: 4px;
|
| 78 |
+
visibility: hidden;
|
| 79 |
+
}
|
| 80 |
+
.left .handle-name {
|
| 81 |
+
right: 20px;
|
| 82 |
+
}
|
| 83 |
+
.right .handle-name {
|
| 84 |
+
left: 20px;
|
| 85 |
+
}
|
| 86 |
+
.top .handle-name,
|
| 87 |
+
.bottom .handle-name {
|
| 88 |
+
top: -5px;
|
| 89 |
+
left: 5px;
|
| 90 |
+
backdrop-filter: none;
|
| 91 |
+
}
|
| 92 |
+
.node-container:hover .handle-name {
|
| 93 |
+
visibility: visible;
|
| 94 |
+
}
|
| 95 |
+
.node-resizer {
|
| 96 |
+
position: absolute;
|
| 97 |
+
bottom: 8px;
|
| 98 |
+
right: 8px;
|
| 99 |
+
cursor: nwse-resize;
|
| 100 |
+
}
|
| 101 |
+
.lynxkite-node {
|
| 102 |
+
.param {
|
| 103 |
+
padding: 4px 8px 4px 8px;
|
| 104 |
+
display: block;
|
| 105 |
+
}
|
| 106 |
+
.param-name {
|
| 107 |
+
display: block;
|
| 108 |
+
font-size: 10px;
|
| 109 |
+
letter-spacing: 0.05em;
|
| 110 |
+
margin-left: 10px;
|
| 111 |
+
width: fit-content;
|
| 112 |
+
padding: 2px 8px;
|
| 113 |
+
border-radius: 4px 4px 0 0;
|
| 114 |
+
;
|
| 115 |
+
}
|
| 116 |
+
.collapsed-param {
|
| 117 |
+
min-height: 20px;
|
| 118 |
+
line-height: 10px;
|
| 119 |
+
}
|
| 120 |
+
}
|
web/app/layout.tsx
CHANGED
|
@@ -1,21 +1,9 @@
|
|
| 1 |
import type { Metadata } from "next";
|
| 2 |
-
import localFont from "next/font/local";
|
| 3 |
import "./globals.css";
|
| 4 |
|
| 5 |
-
const geistSans = localFont({
|
| 6 |
-
src: "./fonts/GeistVF.woff",
|
| 7 |
-
variable: "--font-geist-sans",
|
| 8 |
-
weight: "100 900",
|
| 9 |
-
});
|
| 10 |
-
const geistMono = localFont({
|
| 11 |
-
src: "./fonts/GeistMonoVF.woff",
|
| 12 |
-
variable: "--font-geist-mono",
|
| 13 |
-
weight: "100 900",
|
| 14 |
-
});
|
| 15 |
-
|
| 16 |
export const metadata: Metadata = {
|
| 17 |
-
title: "
|
| 18 |
-
description: "
|
| 19 |
};
|
| 20 |
|
| 21 |
export default function RootLayout({
|
|
@@ -26,7 +14,7 @@ export default function RootLayout({
|
|
| 26 |
return (
|
| 27 |
<html lang="en">
|
| 28 |
<body
|
| 29 |
-
className={
|
| 30 |
>
|
| 31 |
{children}
|
| 32 |
</body>
|
|
|
|
| 1 |
import type { Metadata } from "next";
|
|
|
|
| 2 |
import "./globals.css";
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
export const metadata: Metadata = {
|
| 5 |
+
title: "LynxKite MM",
|
| 6 |
+
description: "From Lynx Analytics",
|
| 7 |
};
|
| 8 |
|
| 9 |
export default function RootLayout({
|
|
|
|
| 14 |
return (
|
| 15 |
<html lang="en">
|
| 16 |
<body
|
| 17 |
+
className={`antialiased`}
|
| 18 |
>
|
| 19 |
{children}
|
| 20 |
</body>
|
web/app/workspace/EnvironmentSelector.tsx
CHANGED
|
@@ -1,12 +1,15 @@
|
|
| 1 |
export default function EnvironmentSelector(props: { options: string[], value: string, onChange: (val: string) => void }) {
|
| 2 |
return (
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
| 11 |
);
|
| 12 |
}
|
|
|
|
| 1 |
export default function EnvironmentSelector(props: { options: string[], value: string, onChange: (val: string) => void }) {
|
| 2 |
return (
|
| 3 |
+
<>
|
| 4 |
+
<select className="select w-full max-w-xs"
|
| 5 |
+
name="workspace-env"
|
| 6 |
+
value={props.value}
|
| 7 |
+
onChange={(evt) => props.onChange(evt.currentTarget.value)}
|
| 8 |
+
>
|
| 9 |
+
{props.options.map(option =>
|
| 10 |
+
<option key={option} value={option}>{option}</option>
|
| 11 |
+
)}
|
| 12 |
+
</select>
|
| 13 |
+
</>
|
| 14 |
);
|
| 15 |
}
|
web/app/workspace/page.tsx
CHANGED
|
@@ -1,224 +1,7 @@
|
|
| 1 |
'use client';
|
| 2 |
-
import useSWR from 'swr';
|
| 3 |
import { useMemo } from "react";
|
| 4 |
-
import
|
| 5 |
-
ReactFlow,
|
| 6 |
-
useNodesState,
|
| 7 |
-
useEdgesState,
|
| 8 |
-
Controls,
|
| 9 |
-
MiniMap,
|
| 10 |
-
MarkerType,
|
| 11 |
-
useReactFlow,
|
| 12 |
-
type XYPosition,
|
| 13 |
-
type Node,
|
| 14 |
-
type Edge,
|
| 15 |
-
type Connection,
|
| 16 |
-
type NodeTypes,
|
| 17 |
-
} from '@xyflow/React';
|
| 18 |
-
// @ts-ignore
|
| 19 |
-
import ArrowBack from '~icons/tabler/arrow-back.jsx';
|
| 20 |
-
// @ts-ignore
|
| 21 |
-
import Backspace from '~icons/tabler/backspace.jsx';
|
| 22 |
-
// @ts-ignore
|
| 23 |
-
import Atom from '~icons/tabler/atom.jsx';
|
| 24 |
-
// import NodeWithParams from './NodeWithParams';
|
| 25 |
-
// import NodeWithVisualization from './NodeWithVisualization';
|
| 26 |
-
// import NodeWithImage from './NodeWithImage';
|
| 27 |
-
// import NodeWithTableView from './NodeWithTableView';
|
| 28 |
-
// import NodeWithSubFlow from './NodeWithSubFlow';
|
| 29 |
-
// import NodeWithArea from './NodeWithArea';
|
| 30 |
-
// import NodeSearch from './NodeSearch';
|
| 31 |
-
import EnvironmentSelector from './EnvironmentSelector';
|
| 32 |
-
import '@xyflow/react/dist/style.css';
|
| 33 |
|
| 34 |
-
export default
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
let path = '';
|
| 39 |
-
|
| 40 |
-
// const { screenToFlowPosition } = useReactFlow();
|
| 41 |
-
// const queryClient = useQueryClient();
|
| 42 |
-
// const backendWorkspace = useQuery(['workspace', path], async () => {
|
| 43 |
-
// const res = await fetch(`/api/load?path=${path}`);
|
| 44 |
-
// return res.json();
|
| 45 |
-
// }, { staleTime: 10000, retry: false });
|
| 46 |
-
// const mutation = useMutation(async (update) => {
|
| 47 |
-
// const res = await fetch('/api/save', {
|
| 48 |
-
// method: 'POST',
|
| 49 |
-
// headers: {
|
| 50 |
-
// 'Content-Type': 'application/json',
|
| 51 |
-
// },
|
| 52 |
-
// body: JSON.stringify(update),
|
| 53 |
-
// });
|
| 54 |
-
// return await res.json();
|
| 55 |
-
// }, {
|
| 56 |
-
// onSuccess: data => queryClient.setQueryData(['workspace', path], data),
|
| 57 |
-
// });
|
| 58 |
-
|
| 59 |
-
// const nodeTypes: NodeTypes = {
|
| 60 |
-
// basic: NodeWithParams,
|
| 61 |
-
// visualization: NodeWithVisualization,
|
| 62 |
-
// image: NodeWithImage,
|
| 63 |
-
// table_view: NodeWithTableView,
|
| 64 |
-
// sub_flow: NodeWithSubFlow,
|
| 65 |
-
// area: NodeWithArea,
|
| 66 |
-
// };
|
| 67 |
-
|
| 68 |
-
// const nodes = writable<Node[]>([]);
|
| 69 |
-
// const edges = writable<Edge[]>([]);
|
| 70 |
-
// let doNotSave = true;
|
| 71 |
-
// $: if ($backendWorkspace.isSuccess) {
|
| 72 |
-
// doNotSave = true; // Change is coming from the backend.
|
| 73 |
-
// nodes.set(JSON.parse(JSON.stringify($backendWorkspace.data?.nodes || [])));
|
| 74 |
-
// edges.set(JSON.parse(JSON.stringify($backendWorkspace.data?.edges || [])));
|
| 75 |
-
// doNotSave = false;
|
| 76 |
-
// }
|
| 77 |
-
|
| 78 |
-
// function closeNodeSearch() {
|
| 79 |
-
// nodeSearchSettings = undefined;
|
| 80 |
-
// }
|
| 81 |
-
// function toggleNodeSearch({ detail: { event } }) {
|
| 82 |
-
// if (nodeSearchSettings) {
|
| 83 |
-
// closeNodeSearch();
|
| 84 |
-
// return;
|
| 85 |
-
// }
|
| 86 |
-
// event.preventDefault();
|
| 87 |
-
// nodeSearchSettings = {
|
| 88 |
-
// pos: { x: event.clientX, y: event.clientY },
|
| 89 |
-
// boxes: $catalog.data[$backendWorkspace.data?.env],
|
| 90 |
-
// };
|
| 91 |
-
// }
|
| 92 |
-
// function addNode(e) {
|
| 93 |
-
// const meta = { ...e.detail };
|
| 94 |
-
// nodes.update((n) => {
|
| 95 |
-
// const node = {
|
| 96 |
-
// type: meta.type,
|
| 97 |
-
// data: {
|
| 98 |
-
// meta: meta,
|
| 99 |
-
// title: meta.name,
|
| 100 |
-
// params: Object.fromEntries(
|
| 101 |
-
// Object.values(meta.params).map((p) => [p.name, p.default])),
|
| 102 |
-
// },
|
| 103 |
-
// };
|
| 104 |
-
// node.position = screenToFlowPosition({ x: nodeSearchSettings.pos.x, y: nodeSearchSettings.pos.y });
|
| 105 |
-
// const title = node.data.title;
|
| 106 |
-
// let i = 1;
|
| 107 |
-
// node.id = `${title} ${i}`;
|
| 108 |
-
// while (n.find((x) => x.id === node.id)) {
|
| 109 |
-
// i += 1;
|
| 110 |
-
// node.id = `${title} ${i}`;
|
| 111 |
-
// }
|
| 112 |
-
// node.parentId = nodeSearchSettings.parentId;
|
| 113 |
-
// if (node.parentId) {
|
| 114 |
-
// node.extent = 'parent';
|
| 115 |
-
// const parent = n.find((x) => x.id === node.parentId);
|
| 116 |
-
// node.position = { x: node.position.x - parent.position.x, y: node.position.y - parent.position.y };
|
| 117 |
-
// }
|
| 118 |
-
// return [...n, node]
|
| 119 |
-
// });
|
| 120 |
-
// closeNodeSearch();
|
| 121 |
-
// }
|
| 122 |
-
const fetcher = (resource: string, init?: RequestInit) => fetch(resource, init).then(res => res.json());
|
| 123 |
-
const catalog = useSWR('/api/catalog', fetcher);
|
| 124 |
-
|
| 125 |
-
// let nodeSearchSettings: {
|
| 126 |
-
// pos: XYPosition,
|
| 127 |
-
// boxes: any[],
|
| 128 |
-
// parentId: string,
|
| 129 |
-
// };
|
| 130 |
-
|
| 131 |
-
// const graph = derived([nodes, edges], ([nodes, edges]) => ({ nodes, edges }));
|
| 132 |
-
// // Like JSON.stringify, but with keys sorted.
|
| 133 |
-
// function orderedJSON(obj: any) {
|
| 134 |
-
// const allKeys = new Set();
|
| 135 |
-
// JSON.stringify(obj, (key, value) => (allKeys.add(key), value));
|
| 136 |
-
// return JSON.stringify(obj, Array.from(allKeys).sort());
|
| 137 |
-
// }
|
| 138 |
-
// graph.subscribe(async (g) => {
|
| 139 |
-
// if (doNotSave) return;
|
| 140 |
-
// const dragging = g.nodes.find((n) => n.dragging);
|
| 141 |
-
// if (dragging) return;
|
| 142 |
-
// const resizing = g.nodes.find((n) => n.data?.beingResized);
|
| 143 |
-
// if (resizing) return;
|
| 144 |
-
// scheduleSave(g);
|
| 145 |
-
// });
|
| 146 |
-
// let saveTimeout;
|
| 147 |
-
// function scheduleSave(g) {
|
| 148 |
-
// // A slight delay, so we don't send a million requests when a node is resized, for example.
|
| 149 |
-
// clearTimeout(saveTimeout);
|
| 150 |
-
// saveTimeout = setTimeout(() => save(g), 500);
|
| 151 |
-
// }
|
| 152 |
-
// function save(g) {
|
| 153 |
-
// g = JSON.parse(JSON.stringify(g));
|
| 154 |
-
// for (const node of g.nodes) {
|
| 155 |
-
// delete node.measured;
|
| 156 |
-
// delete node.selected;
|
| 157 |
-
// delete node.dragging;
|
| 158 |
-
// delete node.beingResized;
|
| 159 |
-
// }
|
| 160 |
-
// for (const node of g.edges) {
|
| 161 |
-
// delete node.markerEnd;
|
| 162 |
-
// delete node.selected;
|
| 163 |
-
// }
|
| 164 |
-
// g.env = $backendWorkspace.data?.env;
|
| 165 |
-
// const ws = orderedJSON(g);
|
| 166 |
-
// const bd = orderedJSON($backendWorkspace.data);
|
| 167 |
-
// if (ws === bd) return;
|
| 168 |
-
// console.log('changed', JSON.stringify(diff(g, $backendWorkspace.data), null, 2));
|
| 169 |
-
// $mutation.mutate({ path, ws: g });
|
| 170 |
-
// }
|
| 171 |
-
// function nodeClick(e) {
|
| 172 |
-
// const node = e.detail.node;
|
| 173 |
-
// const meta = node.data.meta;
|
| 174 |
-
// if (!meta) return;
|
| 175 |
-
// const sub_nodes = meta.sub_nodes;
|
| 176 |
-
// if (!sub_nodes) return;
|
| 177 |
-
// const event = e.detail.event;
|
| 178 |
-
// if (event.target.classList.contains('title')) return;
|
| 179 |
-
// nodeSearchSettings = {
|
| 180 |
-
// pos: { x: event.clientX, y: event.clientY },
|
| 181 |
-
// boxes: sub_nodes,
|
| 182 |
-
// parentId: node.id,
|
| 183 |
-
// };
|
| 184 |
-
// }
|
| 185 |
-
// $: parentDir = path.split('/').slice(0, -1).join('/');
|
| 186 |
-
|
| 187 |
-
const nodeTypes = useMemo(() => ({}), []);
|
| 188 |
-
return (
|
| 189 |
-
|
| 190 |
-
<div className="page">
|
| 191 |
-
<div className="top-bar">
|
| 192 |
-
<div className="ws-name">
|
| 193 |
-
<a href=""><img src="/favicon.ico" /></a>
|
| 194 |
-
{path}
|
| 195 |
-
</div>
|
| 196 |
-
<div className="tools">
|
| 197 |
-
<EnvironmentSelector
|
| 198 |
-
options={Object.keys(catalog.data || {})}
|
| 199 |
-
value={'asd'}
|
| 200 |
-
onChange={(env) => 1}
|
| 201 |
-
/>
|
| 202 |
-
<a href=""><Atom /></a>
|
| 203 |
-
<a href=""><Backspace /></a>
|
| 204 |
-
<a href="#dir?path={parentDir}"><ArrowBack /></a>
|
| 205 |
-
</div>
|
| 206 |
-
</div>
|
| 207 |
-
<div style={{ height: "100%", width: '100vw' }}>
|
| 208 |
-
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} fitView
|
| 209 |
-
proOptions={{ hideAttribution: true }}
|
| 210 |
-
maxZoom={3}
|
| 211 |
-
minZoom={0.3}
|
| 212 |
-
defaultEdgeOptions={{ markerEnd: { type: MarkerType.Arrow } }}
|
| 213 |
-
>
|
| 214 |
-
<Controls />
|
| 215 |
-
<MiniMap />
|
| 216 |
-
{/* {#if nodeSearchSettings}
|
| 217 |
-
<NodeSearch pos={nodeSearchSettings.pos} boxes={nodeSearchSettings.boxes} on:cancel={closeNodeSearch} on:add={addNode} />
|
| 218 |
-
{/if} */}
|
| 219 |
-
</ReactFlow>
|
| 220 |
-
</div>
|
| 221 |
-
</div>
|
| 222 |
-
|
| 223 |
-
);
|
| 224 |
-
}
|
|
|
|
| 1 |
'use client';
|
|
|
|
| 2 |
import { useMemo } from "react";
|
| 3 |
+
import dynamic from 'next/dynamic';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
export default dynamic(() => import('./Workspace'), {
|
| 6 |
+
ssr: false,
|
| 7 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/package-lock.json
CHANGED
|
@@ -6932,6 +6932,111 @@
|
|
| 6932 |
"version": "2.3.0",
|
| 6933 |
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
| 6934 |
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6935 |
}
|
| 6936 |
}
|
| 6937 |
}
|
|
|
|
| 6932 |
"version": "2.3.0",
|
| 6933 |
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
| 6934 |
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
| 6935 |
+
},
|
| 6936 |
+
"node_modules/@next/swc-darwin-x64": {
|
| 6937 |
+
"version": "15.0.3",
|
| 6938 |
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.3.tgz",
|
| 6939 |
+
"integrity": "sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==",
|
| 6940 |
+
"cpu": [
|
| 6941 |
+
"x64"
|
| 6942 |
+
],
|
| 6943 |
+
"optional": true,
|
| 6944 |
+
"os": [
|
| 6945 |
+
"darwin"
|
| 6946 |
+
],
|
| 6947 |
+
"engines": {
|
| 6948 |
+
"node": ">= 10"
|
| 6949 |
+
}
|
| 6950 |
+
},
|
| 6951 |
+
"node_modules/@next/swc-linux-arm64-gnu": {
|
| 6952 |
+
"version": "15.0.3",
|
| 6953 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.3.tgz",
|
| 6954 |
+
"integrity": "sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==",
|
| 6955 |
+
"cpu": [
|
| 6956 |
+
"arm64"
|
| 6957 |
+
],
|
| 6958 |
+
"optional": true,
|
| 6959 |
+
"os": [
|
| 6960 |
+
"linux"
|
| 6961 |
+
],
|
| 6962 |
+
"engines": {
|
| 6963 |
+
"node": ">= 10"
|
| 6964 |
+
}
|
| 6965 |
+
},
|
| 6966 |
+
"node_modules/@next/swc-linux-arm64-musl": {
|
| 6967 |
+
"version": "15.0.3",
|
| 6968 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.3.tgz",
|
| 6969 |
+
"integrity": "sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==",
|
| 6970 |
+
"cpu": [
|
| 6971 |
+
"arm64"
|
| 6972 |
+
],
|
| 6973 |
+
"optional": true,
|
| 6974 |
+
"os": [
|
| 6975 |
+
"linux"
|
| 6976 |
+
],
|
| 6977 |
+
"engines": {
|
| 6978 |
+
"node": ">= 10"
|
| 6979 |
+
}
|
| 6980 |
+
},
|
| 6981 |
+
"node_modules/@next/swc-linux-x64-gnu": {
|
| 6982 |
+
"version": "15.0.3",
|
| 6983 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.3.tgz",
|
| 6984 |
+
"integrity": "sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==",
|
| 6985 |
+
"cpu": [
|
| 6986 |
+
"x64"
|
| 6987 |
+
],
|
| 6988 |
+
"optional": true,
|
| 6989 |
+
"os": [
|
| 6990 |
+
"linux"
|
| 6991 |
+
],
|
| 6992 |
+
"engines": {
|
| 6993 |
+
"node": ">= 10"
|
| 6994 |
+
}
|
| 6995 |
+
},
|
| 6996 |
+
"node_modules/@next/swc-linux-x64-musl": {
|
| 6997 |
+
"version": "15.0.3",
|
| 6998 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.3.tgz",
|
| 6999 |
+
"integrity": "sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==",
|
| 7000 |
+
"cpu": [
|
| 7001 |
+
"x64"
|
| 7002 |
+
],
|
| 7003 |
+
"optional": true,
|
| 7004 |
+
"os": [
|
| 7005 |
+
"linux"
|
| 7006 |
+
],
|
| 7007 |
+
"engines": {
|
| 7008 |
+
"node": ">= 10"
|
| 7009 |
+
}
|
| 7010 |
+
},
|
| 7011 |
+
"node_modules/@next/swc-win32-arm64-msvc": {
|
| 7012 |
+
"version": "15.0.3",
|
| 7013 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.3.tgz",
|
| 7014 |
+
"integrity": "sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==",
|
| 7015 |
+
"cpu": [
|
| 7016 |
+
"arm64"
|
| 7017 |
+
],
|
| 7018 |
+
"optional": true,
|
| 7019 |
+
"os": [
|
| 7020 |
+
"win32"
|
| 7021 |
+
],
|
| 7022 |
+
"engines": {
|
| 7023 |
+
"node": ">= 10"
|
| 7024 |
+
}
|
| 7025 |
+
},
|
| 7026 |
+
"node_modules/@next/swc-win32-x64-msvc": {
|
| 7027 |
+
"version": "15.0.3",
|
| 7028 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.3.tgz",
|
| 7029 |
+
"integrity": "sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==",
|
| 7030 |
+
"cpu": [
|
| 7031 |
+
"x64"
|
| 7032 |
+
],
|
| 7033 |
+
"optional": true,
|
| 7034 |
+
"os": [
|
| 7035 |
+
"win32"
|
| 7036 |
+
],
|
| 7037 |
+
"engines": {
|
| 7038 |
+
"node": ">= 10"
|
| 7039 |
+
}
|
| 7040 |
}
|
| 7041 |
}
|
| 7042 |
}
|
web/tailwind.config.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
| 1 |
/** @type {import('tailwindcss').Config} */
|
| 2 |
module.exports = {
|
| 3 |
-
|
|
|
|
| 4 |
theme: {
|
| 5 |
extend: {},
|
| 6 |
},
|
| 7 |
plugins: [require('daisyui')],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
};
|
|
|
|
| 1 |
/** @type {import('tailwindcss').Config} */
|
| 2 |
module.exports = {
|
| 3 |
+
darkMode: 'selector',
|
| 4 |
+
content: ['./app/**/*.{js,ts,jsx,tsx,mdx}'],
|
| 5 |
theme: {
|
| 6 |
extend: {},
|
| 7 |
},
|
| 8 |
plugins: [require('daisyui')],
|
| 9 |
+
daisyui: {
|
| 10 |
+
themes: [
|
| 11 |
+
{
|
| 12 |
+
lynxkite: {
|
| 13 |
+
primary: 'oklch(75% 0.2 55)',
|
| 14 |
+
secondary: 'oklch(75% 0.13 230)',
|
| 15 |
+
accent: 'oklch(55% 0.25 320)',
|
| 16 |
+
neutral: 'oklch(35% 0.1 240)',
|
| 17 |
+
'base-100': '#ffffff',
|
| 18 |
+
},
|
| 19 |
+
},
|
| 20 |
+
],
|
| 21 |
+
},
|
| 22 |
};
|
web/tsconfig.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
| 13 |
"isolatedModules": true,
|
| 14 |
"jsx": "preserve",
|
| 15 |
"incremental": true,
|
|
|
|
| 16 |
"plugins": [
|
| 17 |
{
|
| 18 |
"name": "next"
|
|
|
|
| 13 |
"isolatedModules": true,
|
| 14 |
"jsx": "preserve",
|
| 15 |
"incremental": true,
|
| 16 |
+
"noImplicitAny": false,
|
| 17 |
"plugins": [
|
| 18 |
{
|
| 19 |
"name": "next"
|