darabos commited on
Commit
7d3eca5
·
1 Parent(s): 6fa3690

Start switching to React.

Browse files
web/.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": ["next/core-web-vitals", "next/typescript"]
3
+ }
web/.gitignore CHANGED
@@ -1,13 +1,40 @@
1
- # Logs
2
- logs
3
- *.log
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  npm-debug.log*
5
  yarn-debug.log*
6
  yarn-error.log*
7
- pnpm-debug.log*
8
- lerna-debug.log*
9
 
10
- node_modules
11
- dist
12
- dist-ssr
13
- *.local
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
  npm-debug.log*
29
  yarn-debug.log*
30
  yarn-error.log*
 
 
31
 
32
+ # env files (can opt-in for committing if needed)
33
+ .env*
34
+
35
+ # vercel
36
+ .vercel
37
+
38
+ # typescript
39
+ *.tsbuildinfo
40
+ next-env.d.ts
web/.vscode/extensions.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "recommendations": ["svelte.svelte-vscode"]
3
- }
 
 
 
 
web/README.md CHANGED
@@ -1,27 +1,36 @@
1
- # Vite Svelte Flow Template
2
 
3
- This template creates a very basic [Svelte Flow](https://svelteflow.dev) app with [Vite](https://vite.dev).
4
 
5
- ## Get it!
6
 
7
- ```sh
8
- npx degit xyflow/vite-svelte-flow-template app-name
 
 
 
 
 
 
9
  ```
10
 
11
- ## Installation
12
 
13
- ```sh
14
- npm install
15
- ```
16
 
17
- ## Development
18
 
19
- ```sh
20
- npm run dev
21
- ```
22
 
23
- ## Build
24
 
25
- ```sh
26
- npm run build
27
- ```
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
 
3
+ ## Getting Started
4
 
5
+ First, run the development server:
6
 
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
  ```
16
 
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
 
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
 
 
20
 
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
 
23
+ ## Learn More
 
 
24
 
25
+ To learn more about Next.js, take a look at the following resources:
26
 
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
web/{public → app}/favicon.ico RENAMED
File without changes
web/app/globals.css ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
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);
20
+ font-family: Arial, Helvetica, sans-serif;
21
+ }
22
+
23
+ .top-bar {
24
+ display: flex;
25
+ justify-content: space-between;
26
+ background: oklch(30% 0.13 230);
27
+ color: white;
28
+ }
29
+ .ws-name {
30
+ font-size: 1.5em;
31
+ }
32
+ .ws-name img {
33
+ height: 1.5em;
34
+ vertical-align: middle;
35
+ margin: 4px;
36
+ }
37
+ .page {
38
+ display: flex;
39
+ flex-direction: column;
40
+ height: 100vh;
41
+ }
42
+
43
+ .tools {
44
+ display: flex;
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
+ }
web/app/layout.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: "Create Next App",
18
+ description: "Generated by create next app",
19
+ };
20
+
21
+ export default function RootLayout({
22
+ children,
23
+ }: Readonly<{
24
+ children: React.ReactNode;
25
+ }>) {
26
+ return (
27
+ <html lang="en">
28
+ <body
29
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
30
+ >
31
+ {children}
32
+ </body>
33
+ </html>
34
+ );
35
+ }
web/app/page.tsx ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+
3
+ export default function Home() {
4
+ return (
5
+ <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
6
+ <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
7
+ <Image
8
+ className="dark:invert"
9
+ src="/next.svg"
10
+ alt="Next.js logo"
11
+ width={180}
12
+ height={38}
13
+ priority
14
+ />
15
+ <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
16
+ <li className="mb-2">
17
+ Get started by editing{" "}
18
+ <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
19
+ app/page.tsx
20
+ </code>
21
+ .
22
+ </li>
23
+ <li>Save and see your changes instantly.</li>
24
+ </ol>
25
+
26
+ <div className="flex gap-4 items-center flex-col sm:flex-row">
27
+ <a
28
+ className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
29
+ href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
+ target="_blank"
31
+ rel="noopener noreferrer"
32
+ >
33
+ <Image
34
+ className="dark:invert"
35
+ src="/vercel.svg"
36
+ alt="Vercel logomark"
37
+ width={20}
38
+ height={20}
39
+ />
40
+ Deploy now
41
+ </a>
42
+ <a
43
+ className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
44
+ href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
45
+ target="_blank"
46
+ rel="noopener noreferrer"
47
+ >
48
+ Read our docs
49
+ </a>
50
+ </div>
51
+ </main>
52
+ <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
53
+ <a
54
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
55
+ href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56
+ target="_blank"
57
+ rel="noopener noreferrer"
58
+ >
59
+ <Image
60
+ aria-hidden
61
+ src="/file.svg"
62
+ alt="File icon"
63
+ width={16}
64
+ height={16}
65
+ />
66
+ Learn
67
+ </a>
68
+ <a
69
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
70
+ href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
71
+ target="_blank"
72
+ rel="noopener noreferrer"
73
+ >
74
+ <Image
75
+ aria-hidden
76
+ src="/window.svg"
77
+ alt="Window icon"
78
+ width={16}
79
+ height={16}
80
+ />
81
+ Examples
82
+ </a>
83
+ <a
84
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
85
+ href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
86
+ target="_blank"
87
+ rel="noopener noreferrer"
88
+ >
89
+ <Image
90
+ aria-hidden
91
+ src="/globe.svg"
92
+ alt="Globe icon"
93
+ width={16}
94
+ height={16}
95
+ />
96
+ Go to nextjs.org →
97
+ </a>
98
+ </footer>
99
+ </div>
100
+ );
101
+ }
web/app/workspace/EnvironmentSelector.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function EnvironmentSelector(props: { options: string[], value: string, onChange: (val: string) => void }) {
2
+ return (
3
+ <select className="form-select form-select-sm"
4
+ value={props.value}
5
+ onChange={(evt) => props.onChange(evt.currentTarget.value)}
6
+ >
7
+ {props.options.map(option =>
8
+ <option key={option} value={option}>{option}</option>
9
+ )}
10
+ </select>
11
+ );
12
+ }
web/app/workspace/page.tsx ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 function Home() {
35
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
36
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
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
+ }
web/index.html DELETED
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/png" href="/favicon.ico" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>LynxKite 2024</title>
8
- </head>
9
- <body>
10
- <div id="app"></div>
11
- <script type="module" src="/src/main.ts"></script>
12
- </body>
13
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/next.config.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+ import Icons from 'unplugin-icons/webpack'
3
+
4
+ const nextConfig: NextConfig = {
5
+ webpack(config) {
6
+ config.plugins.push(
7
+ Icons({
8
+ compiler: 'jsx',
9
+ jsx: 'react',
10
+ })
11
+ );
12
+ return config;
13
+ },
14
+ async rewrites() {
15
+ return [
16
+ {
17
+ source: '/api/:path*',
18
+ destination: 'http://127.0.0.1:8000/api/:path*',
19
+ },
20
+ ]
21
+ },
22
+ };
23
+
24
+ export default nextConfig;
web/package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
web/package.json CHANGED
@@ -1,37 +1,40 @@
1
  {
2
- "name": "vite-svelte-flow-template",
 
3
  "private": true,
4
- "version": "0.0.0",
5
- "type": "module",
6
  "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "preview": "vite preview",
10
- "check": "svelte-check --tsconfig ./tsconfig.json"
11
- },
12
- "devDependencies": {
13
- "@sveltejs/vite-plugin-svelte": "3.1.2",
14
- "@syncedstore/core": "0.6.0",
15
- "@syncedstore/svelte": "0.6.0",
16
- "@tsconfig/svelte": "5.0.4",
17
- "sass": "1.79.5",
18
- "svelte": "4.2.19",
19
- "svelte-check": "4.0.5",
20
- "svelte-markdown": "^0.4.1",
21
- "tslib": "2.7.0",
22
- "typescript": "5.6.3",
23
- "unplugin-icons": "0.19.3",
24
- "vite": "5.4.9",
25
- "y-websocket": "2.0.4"
26
  },
27
  "dependencies": {
28
- "@iconify-json/tabler": "1.2.5",
29
- "@popperjs/core": "2.11.8",
30
- "@sveltestack/svelte-query": "1.6.0",
31
- "@xyflow/svelte": "0.1.21",
32
- "bootstrap": "5.3.3",
33
- "echarts": "5.5.1",
34
- "fuse.js": "7.0.0",
35
- "svelte-echarts": "^1.0.0-rc3"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
37
  }
 
1
  {
2
+ "name": "lynxkite",
3
+ "version": "0.1.0",
4
  "private": true,
 
 
5
  "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  },
11
  "dependencies": {
12
+ "@iconify-json/tabler": "^1.2.8",
13
+ "@syncedstore/core": "^0.6.0",
14
+ "@syncedstore/react": "^0.6.0",
15
+ "@xyflow/react": "^12.3.5",
16
+ "echarts": "^5.5.1",
17
+ "fuse.js": "^7.0.0",
18
+ "next": "15.0.3",
19
+ "react": ">=18",
20
+ "react-dom": ">=18",
21
+ "swr": "^2.2.5",
22
+ "unplugin-icons": "^0.20.1",
23
+ "y-websocket": "2.0.4",
24
+ "yjs": "^13.6.20"
25
+ },
26
+ "devDependencies": {
27
+ "@svgr/core": "^8.1.0",
28
+ "@svgr/plugin-jsx": "^8.1.0",
29
+ "@types/node": "^20",
30
+ "@types/react": "^18",
31
+ "@types/react-dom": "^18",
32
+ "autoprefixer": "^10.4.20",
33
+ "daisyui": "^4.12.14",
34
+ "eslint": "^8",
35
+ "eslint-config-next": "15.0.3",
36
+ "postcss": "^8.4.49",
37
+ "tailwindcss": "^3.4.15",
38
+ "typescript": "^5"
39
  }
40
  }
web/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
web/postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
web/{src/assets → public}/logo.png RENAMED
File without changes
web/src/App.svelte DELETED
@@ -1,22 +0,0 @@
1
- <script lang="ts">
2
- import Directory from './Directory.svelte';
3
- import Workspace from './Workspace.svelte';
4
- let page = '';
5
- let parameters = {};
6
- function onHashChange() {
7
- const parts = location.hash.split('?');
8
- page = parts[0].substring(1);
9
- parameters = {};
10
- if (parts.length > 1) {
11
- parameters = Object.fromEntries(new URLSearchParams(parts[1]));
12
- }
13
- }
14
- onHashChange();
15
- </script>
16
-
17
- <svelte:window on:hashchange={onHashChange} />
18
- {#if page === 'edit'}
19
- <Workspace {...parameters} />
20
- {:else}
21
- <Directory {...parameters} />
22
- {/if}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/Directory.svelte DELETED
@@ -1,177 +0,0 @@
1
- <script lang="ts">
2
- // The directory browser.
3
- import logo from './assets/logo.png';
4
- import Home from 'virtual:icons/tabler/home'
5
- import Folder from 'virtual:icons/tabler/folder'
6
- import FolderPlus from 'virtual:icons/tabler/folder-plus'
7
- import File from 'virtual:icons/tabler/file'
8
- import FilePlus from 'virtual:icons/tabler/file-plus'
9
-
10
- export let path = '';
11
- async function fetchList(path) {
12
- const encodedPath = encodeURIComponent(path || '');
13
- const res = await fetch(`/api/dir/list?path=${encodedPath}`);
14
- const j = await res.json();
15
- return j;
16
- }
17
- $: list = fetchList(path);
18
- function link(item) {
19
- if (item.type === 'directory') {
20
- return `#dir?path=${item.name}`;
21
- } else {
22
- return `#edit?path=${item.name}`;
23
- }
24
- }
25
- function shortName(item) {
26
- return item.name.split('/').pop();
27
- }
28
- function newName(list) {
29
- let i = 0;
30
- while (true) {
31
- const name = `Untitled${i ? ` ${i}` : ''}`;
32
- if (!list.find(item => item.name === name)) {
33
- return name;
34
- }
35
- i++;
36
- }
37
- }
38
- function newWorkspaceIn(path, list) {
39
- const pathSlash = path ? `${path}/` : '';
40
- return `#edit?path=${pathSlash}${newName(list)}`;
41
- }
42
- async function newFolderIn(path, list) {
43
- const pathSlash = path ? `${path}/` : '';
44
- const name = newName(list);
45
- const res = await fetch(`/api/dir/mkdir`, {
46
- method: 'POST',
47
- headers: { 'Content-Type': 'application/json' },
48
- body: JSON.stringify({ path: pathSlash + name }),
49
- });
50
- list = await res.json();
51
- }
52
- </script>
53
-
54
- <div class="directory-page">
55
- <div class="logo">
56
- <a href="https://lynxkite.com/"><img src="{logo}" class="logo-image"></a>
57
- <div class="tagline">The Complete Graph Data Science Platform</div>
58
- </div>
59
- <div class="entry-list">
60
- {#await list}
61
- <div class="loading spinner-border" role="status">
62
- <span class="visually-hidden">Loading...</span>
63
- </div>
64
- {:then list}
65
- <div class="actions">
66
- <a href="{newWorkspaceIn(path, list)}"><FilePlus /> New workspace</a>
67
- <a href on:click="{newFolderIn(path, list)}"><FolderPlus /> New folder</a>
68
- </div>
69
- {#if path} <div class="breadcrumbs"><a href="#dir"><Home /></a> {path} </div> {/if}
70
- {#each list as item}
71
- <a class="entry" href={link(item)}>
72
- {#if item.type === 'directory'}
73
- <Folder />
74
- {:else}
75
- <File />
76
- {/if}
77
- {shortName(item)}
78
- </a>
79
- {/each}
80
- {:catch error}
81
- <p style="color: red">{error.message}</p>
82
- {/await}
83
- </div>
84
- </div>
85
-
86
- <style>
87
- .entry-list {
88
- width: 100%;
89
- margin: 10px auto;
90
- background-color: white;
91
- border-radius: 10px;
92
- box-shadow: 0px 2px 4px;
93
- padding: 0 0 10px 0;
94
- }
95
- @media (min-width: 768px) {
96
- .entry-list {
97
- width: 768px;
98
- }
99
- }
100
- @media (min-width: 960px) {
101
- .entry-list {
102
- width: 80%;
103
- }
104
- }
105
-
106
- .logo {
107
- margin: 0;
108
- padding-top: 50px;
109
- text-align: center;
110
- }
111
- .logo-image {
112
- max-width: 50%;
113
- }
114
- .tagline {
115
- color: #39bcf3;
116
- font-size: 14px;
117
- font-weight: 500;
118
- }
119
- @media (min-width: 1400px) {
120
- .tagline {
121
- font-size: 18px;
122
- }
123
- }
124
-
125
- .actions {
126
- display: flex;
127
- justify-content: space-evenly;
128
- padding: 5px;
129
- }
130
- .actions a {
131
- padding: 2px 10px;
132
- border-radius: 5px;
133
- }
134
- .actions a:hover {
135
- background: #39bcf3;
136
- color: white;
137
- }
138
-
139
- .breadcrumbs {
140
- padding-left: 10px;
141
- font-size: 20px;
142
- background: #002a4c20;
143
- }
144
- .breadcrumbs a:hover {
145
- color: #39bcf3;
146
- }
147
- .entry-list .entry {
148
- display: block;
149
- border-bottom: 1px solid whitesmoke;
150
- padding-left: 10px;
151
- color: #004165;
152
- cursor: pointer;
153
- user-select: none;
154
- text-decoration: none;
155
- }
156
- .entry-list .open .entry,
157
- .entry-list .entry:hover,
158
- .entry-list .entry:focus {
159
- background: #39bcf3;
160
- color: white;
161
- }
162
- .entry-list .entry:last-child {
163
- border-bottom: none;
164
- }
165
- .directory-page {
166
- background: #002a4c;
167
- height: 100vh;
168
- }
169
- a {
170
- color: black;
171
- text-decoration: none;
172
- }
173
- .loading {
174
- color: #39bcf3;
175
- margin: 10px;
176
- }
177
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/EnvironmentSelector.svelte DELETED
@@ -1,14 +0,0 @@
1
- <script lang="ts">
2
- export let options;
3
- export let value;
4
- export let onChange;
5
- </script>
6
-
7
- <select class="form-select form-select-sm"
8
- value={value}
9
- on:change={(evt) => onChange(evt.currentTarget.value)}
10
- >
11
- {#each options as option}
12
- <option value={option}>{option}</option>
13
- {/each}
14
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/LynxKiteFlow.svelte DELETED
@@ -1,230 +0,0 @@
1
- <script lang="ts">
2
- import { setContext } from 'svelte';
3
- import { writable } from 'svelte/store';
4
- import {
5
- SvelteFlow,
6
- Controls,
7
- MiniMap,
8
- MarkerType,
9
- useSvelteFlow,
10
- useUpdateNodeInternals,
11
- type XYPosition,
12
- type Node,
13
- type Edge,
14
- type Connection,
15
- type NodeTypes,
16
- } from '@xyflow/svelte';
17
- import ArrowBack from 'virtual:icons/tabler/arrow-back'
18
- import Backspace from 'virtual:icons/tabler/backspace'
19
- import Atom from 'virtual:icons/tabler/Atom'
20
- import { useQuery } from '@sveltestack/svelte-query';
21
- import NodeWithParams from './NodeWithParams.svelte';
22
- import NodeWithVisualization from './NodeWithVisualization.svelte';
23
- import NodeWithImage from './NodeWithImage.svelte';
24
- import NodeWithTableView from './NodeWithTableView.svelte';
25
- import NodeWithSubFlow from './NodeWithSubFlow.svelte';
26
- import NodeWithArea from './NodeWithArea.svelte';
27
- import NodeSearch from './NodeSearch.svelte';
28
- import EnvironmentSelector from './EnvironmentSelector.svelte';
29
- import '@xyflow/svelte/dist/style.css';
30
- import { syncedStore, getYjsDoc } from "@syncedstore/core";
31
- import { svelteSyncedStore } from "@syncedstore/svelte";
32
- import { WebsocketProvider } from "y-websocket";
33
- const updateNodeInternals = useUpdateNodeInternals();
34
-
35
- function getCRDTStore(path) {
36
- const sstore = syncedStore({ workspace: {} });
37
- const doc = getYjsDoc(sstore);
38
- const wsProvider = new WebsocketProvider("ws://localhost:8000/ws/crdt", path, doc);
39
- return {store: svelteSyncedStore(sstore), sstore, doc};
40
- }
41
- $: connection = getCRDTStore(path);
42
- $: store = connection.store;
43
- $: store.subscribe((value) => {
44
- if (!value?.workspace?.edges) return;
45
- $nodes = [...value.workspace.nodes];
46
- $edges = [...value.workspace.edges];
47
- updateNodeInternals();
48
- });
49
- $: setContext('LynxKite store', store);
50
-
51
- export let path = '';
52
-
53
- const { screenToFlowPosition } = useSvelteFlow();
54
-
55
- const nodeTypes: NodeTypes = {
56
- basic: NodeWithParams,
57
- visualization: NodeWithVisualization,
58
- image: NodeWithImage,
59
- table_view: NodeWithTableView,
60
- sub_flow: NodeWithSubFlow,
61
- area: NodeWithArea,
62
- };
63
-
64
- const nodes = writable<Node[]>([]);
65
- const edges = writable<Edge[]>([]);
66
-
67
- function closeNodeSearch() {
68
- nodeSearchSettings = undefined;
69
- }
70
- function toggleNodeSearch({ detail: { event } }) {
71
- if (nodeSearchSettings) {
72
- closeNodeSearch();
73
- return;
74
- }
75
- event.preventDefault();
76
- nodeSearchSettings = {
77
- pos: { x: event.clientX, y: event.clientY },
78
- boxes: $catalog.data[$store.workspace.env],
79
- };
80
- }
81
- function addNode(e) {
82
- const meta = {...e.detail};
83
- const node = {
84
- type: meta.type,
85
- data: {
86
- meta: meta,
87
- title: meta.name,
88
- params: Object.fromEntries(
89
- Object.values(meta.params).map((p) => [p.name, p.default])),
90
- },
91
- };
92
- node.position = screenToFlowPosition({x: nodeSearchSettings.pos.x, y: nodeSearchSettings.pos.y});
93
- const title = node.data.title;
94
- let i = 1;
95
- node.id = `${title} ${i}`;
96
- const nodes = $store.workspace.nodes;
97
- while (nodes.find((x) => x.id === node.id)) {
98
- i += 1;
99
- node.id = `${title} ${i}`;
100
- }
101
- node.parentId = nodeSearchSettings.parentId;
102
- if (node.parentId) {
103
- node.extent = 'parent';
104
- const parent = nodes.find((x) => x.id === node.parentId);
105
- node.position = { x: node.position.x - parent.position.x, y: node.position.y - parent.position.y };
106
- }
107
- nodes.push(node);
108
- closeNodeSearch();
109
- }
110
- const catalog = useQuery(['catalog'], async () => {
111
- const res = await fetch('/api/catalog');
112
- return res.json();
113
- }, {staleTime: 60000, retry: false});
114
-
115
- let nodeSearchSettings: {
116
- pos: XYPosition,
117
- boxes: any[],
118
- parentId: string,
119
- };
120
-
121
- function nodeClick(e) {
122
- const node = e.detail.node;
123
- const meta = node.data.meta;
124
- if (!meta) return;
125
- const sub_nodes = meta.sub_nodes;
126
- if (!sub_nodes) return;
127
- const event = e.detail.event;
128
- if (event.target.classList.contains('title')) return;
129
- nodeSearchSettings = {
130
- pos: { x: event.clientX, y: event.clientY },
131
- boxes: sub_nodes,
132
- parentId: node.id,
133
- };
134
- }
135
- function onConnect(params: Connection) {
136
- const edge = {
137
- id: `${params.source} ${params.target}`,
138
- source: params.source,
139
- sourceHandle: params.sourceHandle,
140
- target: params.target,
141
- targetHandle: params.targetHandle,
142
- };
143
- $store.workspace.edges.push(edge);
144
- }
145
- function onDelete(params) {
146
- const { nodes, edges } = params;
147
- for (const node of nodes) {
148
- const index = $store.workspace.nodes.findIndex((x) => x.id === node.id);
149
- if (index !== -1) $store.workspace.nodes.splice(index, 1);
150
- }
151
- for (const edge of edges) {
152
- const index = $store.workspace.edges.findIndex((x) => x.id === edge.id);
153
- if (index !== -1) $store.workspace.edges.splice(index, 1);
154
- }
155
- }
156
- $: parentDir = path.split('/').slice(0, -1).join('/');
157
- </script>
158
-
159
- <div class="page">
160
- {#if $store.workspace !== undefined}
161
- <div class="top-bar">
162
- <div class="ws-name">
163
- <a href><img src="/favicon.ico"></a>
164
- {path}
165
- </div>
166
- <div class="tools">
167
- <EnvironmentSelector
168
- options={Object.keys($catalog.data || {})}
169
- value={$store.workspace.env}
170
- onChange={(env) => {
171
- $store.workspace.env = env;
172
- }}
173
- />
174
- <a href><Atom /></a>
175
- <a href><Backspace /></a>
176
- <a href="#dir?path={parentDir}"><ArrowBack /></a>
177
- </div>
178
- </div>
179
- <div style:height="100%">
180
- <SvelteFlow {nodes} {edges} {nodeTypes} fitView
181
- on:paneclick={toggleNodeSearch}
182
- on:nodeclick={nodeClick}
183
- onconnect={onConnect}
184
- ondelete={onDelete}
185
- proOptions={{ hideAttribution: true }}
186
- maxZoom={3}
187
- minZoom={0.3}
188
- defaultEdgeOptions={{ markerEnd: { type: MarkerType.Arrow } }}
189
- >
190
- <Controls />
191
- <MiniMap />
192
- {#if nodeSearchSettings}
193
- <NodeSearch pos={nodeSearchSettings.pos} boxes={nodeSearchSettings.boxes} on:cancel={closeNodeSearch} on:add={addNode} />
194
- {/if}
195
- </SvelteFlow>
196
- </div>
197
- {/if}
198
- </div>
199
-
200
- <style>
201
- .top-bar {
202
- display: flex;
203
- justify-content: space-between;
204
- background: oklch(30% 0.13 230);
205
- color: white;
206
- }
207
- .ws-name {
208
- font-size: 1.5em;
209
- }
210
- .ws-name img {
211
- height: 1.5em;
212
- vertical-align: middle;
213
- margin: 4px;
214
- }
215
- .page {
216
- display: flex;
217
- flex-direction: column;
218
- height: 100vh;
219
- }
220
-
221
- .tools {
222
- display: flex;
223
- align-items: center;
224
- }
225
- .tools a {
226
- color: oklch(75% 0.13 230);
227
- font-size: 1.5em;
228
- padding: 0 10px;
229
- }
230
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/LynxKiteNode.svelte DELETED
@@ -1,174 +0,0 @@
1
- <script lang="ts">
2
- import { getContext } from 'svelte';
3
- import { Handle, useSvelteFlow, useUpdateNodeInternals, type NodeProps, NodeResizeControl } from '@xyflow/svelte';
4
- import ChevronDownRight from 'virtual:icons/tabler/chevron-down-right';
5
-
6
- const { updateNodeData } = useSvelteFlow();
7
- const updateNodeInternals = useUpdateNodeInternals();
8
- type $$Props = NodeProps;
9
-
10
- export let nodeStyle = '';
11
- export let containerStyle = '';
12
- export let id: $$Props['id']; id;
13
- export let data: $$Props['data'];
14
- export let deletable: $$Props['deletable'] = undefined; deletable;
15
- export let draggable: $$Props['draggable'] = undefined; draggable;
16
- export let parentId: $$Props['parentId'] = undefined; parentId;
17
- export let selectable: $$Props['selectable'] = undefined; selectable;
18
- export let dragHandle: $$Props['dragHandle'] = undefined; dragHandle;
19
- export let type: $$Props['type'] = undefined; type;
20
- export let selected: $$Props['selected'] = undefined; selected;
21
- export let isConnectable: $$Props['isConnectable'] = undefined; isConnectable;
22
- export let zIndex: $$Props['zIndex'] = undefined; zIndex;
23
- export let width: $$Props['width'] = undefined; width;
24
- export let height: $$Props['height'] = undefined; height;
25
- export let dragging: $$Props['dragging']; dragging;
26
- export let targetPosition: $$Props['targetPosition'] = undefined; targetPosition;
27
- export let sourcePosition: $$Props['sourcePosition'] = undefined; sourcePosition;
28
- export let positionAbsoluteX: $$Props['positionAbsoluteX'] = undefined; positionAbsoluteX;
29
- export let positionAbsoluteY: $$Props['positionAbsoluteY'] = undefined; positionAbsoluteY;
30
- export let onToggle = () => {};
31
-
32
- $: store = getContext('LynxKite store');
33
- $: expanded = !data.collapsed;
34
- function titleClicked() {
35
- const i = $store.workspace.nodes.findIndex((n) => n.id === id);
36
- $store.workspace.nodes[i].data.collapsed = expanded;
37
- onToggle({ expanded });
38
- // Trigger update.
39
- data = data;
40
- updateNodeInternals();
41
- }
42
- function asPx(n: number | undefined) {
43
- return n ? n + 'px' : undefined;
44
- }
45
- function getHandles(inputs, outputs) {
46
- const handles: {
47
- position: 'top' | 'bottom' | 'left' | 'right',
48
- name: string,
49
- index: number,
50
- offsetPercentage: number,
51
- showLabel: boolean,
52
- }[] = [];
53
- for (const e of Object.values(inputs)) {
54
- handles.push({ ...e, type: 'target' });
55
- }
56
- for (const e of Object.values(outputs)) {
57
- handles.push({ ...e, type: 'source' });
58
- }
59
- const counts = { top: 0, bottom: 0, left: 0, right: 0 };
60
- for (const e of handles) {
61
- e.index = counts[e.position];
62
- counts[e.position]++;
63
- }
64
- for (const e of handles) {
65
- e.offsetPercentage = 100 * (e.index + 1) / (counts[e.position] + 1);
66
- const simpleHorizontal = counts.top === 0 && counts.bottom === 0 && handles.length <= 2;
67
- const simpleVertical = counts.left === 0 && counts.right === 0 && handles.length <= 2;
68
- e.showLabel = !simpleHorizontal && !simpleVertical;
69
- }
70
- return handles;
71
- }
72
- $: handles = getHandles(data.meta?.inputs || {}, data.meta?.outputs || {});
73
- const handleOffsetDirection = { top: 'left', bottom: 'left', left: 'top', right: 'top' };
74
- </script>
75
-
76
- <div class="node-container" class:expanded={expanded}
77
- style:width={asPx(width)} style:height={asPx(expanded ? height : undefined)} style={containerStyle}>
78
- <div class="lynxkite-node" style={nodeStyle}>
79
- <div class="title" on:click={titleClicked}>
80
- {data.title}
81
- {#if data.error}<span class="title-icon">⚠️</span>{/if}
82
- {#if !expanded}<span class="title-icon">⋯</span>{/if}
83
- </div>
84
- {#if expanded}
85
- {#if data.error}
86
- <div class="error">{data.error}</div>
87
- {/if}
88
- <slot />
89
- {/if}
90
- {#each handles as handle}
91
- <Handle
92
- id={handle.name} type={handle.type} position={handle.position}
93
- style="{handleOffsetDirection[handle.position]}: {handle.offsetPercentage}%">
94
- {#if handle.showLabel}<span class="handle-name">{handle.name.replace(/_/g, " ")}</span>{/if}
95
- </Handle>
96
- {/each}
97
- </div>
98
- {#if expanded}
99
- <NodeResizeControl
100
- minWidth={100}
101
- minHeight={50}
102
- style="background: transparent; border: none;"
103
- onResizeStart={() => updateNodeData(id, { beingResized: true })}
104
- onResizeEnd={() => updateNodeData(id, { beingResized: false })}
105
- >
106
- <ChevronDownRight class="node-resizer" />
107
- </NodeResizeControl>
108
- {/if}
109
- </div>
110
-
111
- <style>
112
- .error {
113
- background: #ffdddd;
114
- padding: 8px;
115
- font-size: 12px;
116
- }
117
- .title-icon {
118
- margin-left: 5px;
119
- float: right;
120
- }
121
- .node-container {
122
- padding: 8px;
123
- position: relative;
124
- }
125
- .lynxkite-node {
126
- box-shadow: 0px 5px 50px 0px rgba(0, 0, 0, 0.3);
127
- border-radius: 4px;
128
- background: white;
129
- }
130
- .expanded .lynxkite-node {
131
- overflow-y: auto;
132
- height: 100%;
133
- }
134
- .title {
135
- background: oklch(75% 0.2 55);
136
- font-weight: bold;
137
- padding: 8px;
138
- }
139
- .handle-name {
140
- font-size: 10px;
141
- color: black;
142
- letter-spacing: 0.05em;
143
- text-align: right;
144
- white-space: nowrap;
145
- position: absolute;
146
- top: -5px;
147
- backdrop-filter: blur(10px);
148
- padding: 2px 8px;
149
- border-radius: 4px;
150
- visibility: hidden;
151
- }
152
- :global(.left) .handle-name {
153
- right: 20px;
154
- }
155
- :global(.right) .handle-name {
156
- left: 20px;
157
- }
158
- :global(.top) .handle-name,
159
- :global(.bottom) .handle-name {
160
- top: -5px;
161
- left: 5px;
162
- backdrop-filter: none;
163
- }
164
- .node-container:hover .handle-name {
165
- visibility: visible;
166
- }
167
- :global(.node-resizer) {
168
- position: absolute;
169
- bottom: 8px;
170
- right: 8px;
171
- cursor: nwse-resize;
172
- color: var(--bs-border-color);
173
- }
174
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeParameter.svelte DELETED
@@ -1,69 +0,0 @@
1
- <script lang="ts">
2
- export let name: string;
3
- export let value;
4
- export let meta;
5
- export let onChange;
6
- const BOOLEAN = "<class 'bool'>";
7
- </script>
8
-
9
- <label class="param">
10
- {#if meta?.type?.format === 'collapsed'}
11
- <span class="param-name">{name.replace(/_/g, ' ')}</span>
12
- <button class="collapsed-param form-control form-control-sm">
13
-
14
- </button>
15
- {:else if meta?.type?.format === 'textarea'}
16
- <span class="param-name">{name.replace(/_/g, ' ')}</span>
17
- <textarea class="form-control form-control-sm"
18
- rows="6"
19
- value={value}
20
- on:change={(evt) => onChange(evt.currentTarget.value)}
21
- />
22
- {:else if meta?.type?.enum}
23
- <span class="param-name">{name.replace(/_/g, ' ')}</span>
24
- <select class="form-select form-select-sm"
25
- value={value || meta.type.enum[0]}
26
- on:change={(evt) => onChange(evt.currentTarget.value)}
27
- >
28
- {#each meta.type.enum as option}
29
- <option value={option}>{option}</option>
30
- {/each}
31
- </select>
32
- {:else if meta?.type?.type === BOOLEAN}
33
- <label class="form-check-label">
34
- <input class="form-check-input"
35
- type="checkbox"
36
- checked={value}
37
- on:change={(evt) => onChange(evt.currentTarget.checked)}
38
- />
39
- {name.replace(/_/g, ' ')}
40
- </label>
41
- {:else}
42
- <span class="param-name">{name.replace(/_/g, ' ')}</span>
43
- <input class="form-control form-control-sm"
44
- value={value}
45
- on:change={(evt) => onChange(evt.currentTarget.value)}
46
- />
47
- {/if}
48
- </label>
49
-
50
- <style>
51
- .param {
52
- padding: 4px 8px 4px 8px;
53
- display: block;
54
- }
55
- .param-name {
56
- display: block;
57
- font-size: 10px;
58
- letter-spacing: 0.05em;
59
- margin-left: 10px;
60
- background: var(--bs-border-color);
61
- width: fit-content;
62
- padding: 2px 8px;
63
- border-radius: 4px 4px 0 0;
64
- }
65
- .collapsed-param {
66
- min-height: 20px;
67
- line-height: 10px;
68
- }
69
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeSearch.svelte DELETED
@@ -1,100 +0,0 @@
1
- <script lang="ts">
2
- import { createEventDispatcher, onMount } from 'svelte';
3
- import Fuse from 'fuse.js'
4
- const dispatch = createEventDispatcher();
5
- export let pos;
6
- export let boxes;
7
- let searchBox: HTMLInputElement;
8
- let hits = Object.values(boxes).map(box => ({item: box}));
9
- let selectedIndex = 0;
10
- onMount(() => searchBox.focus());
11
- $: fuse = new Fuse(Object.values(boxes), {
12
- keys: ['name']
13
- })
14
- function onInput() {
15
- hits = fuse.search(searchBox.value);
16
- selectedIndex = Math.max(0, Math.min(selectedIndex, hits.length - 1));
17
- }
18
- function onKeyDown(e) {
19
- if (e.key === 'ArrowDown') {
20
- e.preventDefault();
21
- selectedIndex = Math.min(selectedIndex + 1, hits.length - 1);
22
- } else if (e.key === 'ArrowUp') {
23
- e.preventDefault();
24
- selectedIndex = Math.max(selectedIndex - 1, 0);
25
- } else if (e.key === 'Enter') {
26
- addSelected();
27
- } else if (e.key === 'Escape') {
28
- dispatch('cancel');
29
- }
30
- }
31
- function addSelected() {
32
- const node = {...hits[selectedIndex].item};
33
- delete node.sub_nodes;
34
- node.position = pos;
35
- dispatch('add', node);
36
- }
37
- async function lostFocus(e) {
38
- // If it's a click on a result, let the click handler handle it.
39
- if (e.relatedTarget && e.relatedTarget.closest('.node-search')) return;
40
- dispatch('cancel');
41
- }
42
-
43
- </script>
44
-
45
- <div class="node-search" style="top: {pos.y}px; left: {pos.x}px;">
46
- <input
47
- bind:this={searchBox}
48
- on:input={onInput}
49
- on:keydown={onKeyDown}
50
- on:focusout={lostFocus}
51
- placeholder="Search for box">
52
- <div class="matches">
53
- {#each hits as box, index}
54
- <div
55
- tabindex="0"
56
- on:focus={() => selectedIndex = index}
57
- on:mouseenter={() => selectedIndex = index}
58
- on:click={addSelected}
59
- class="search-result"
60
- class:selected={index == selectedIndex}>
61
- {box.item.name}
62
- </div>
63
- {/each}
64
- </div>
65
- </div>
66
-
67
- <style>
68
- input {
69
- width: calc(100% - 26px);
70
- font-size: 20px;
71
- padding: 8px;
72
- border-radius: 4px;
73
- border: 1px solid #eee;
74
- margin: 4px;
75
- }
76
- .search-result {
77
- padding: 4px;
78
- cursor: pointer;
79
- }
80
- .search-result.selected {
81
- background-color: oklch(75% 0.2 55);
82
- border-radius: 4px;
83
- }
84
- .node-search {
85
- position: fixed;
86
- width: 300px;
87
- z-index: 5;
88
- padding: 4px;
89
- border-radius: 4px;
90
- border: 1px solid #888;
91
- background-color: white;
92
- max-height: -webkit-fill-available;
93
- max-height: -moz-available;
94
- display: flex;
95
- flex-direction: column;
96
- }
97
- .matches {
98
- overflow-y: auto;
99
- }
100
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeWithArea.svelte DELETED
@@ -1,60 +0,0 @@
1
- <script lang="ts">
2
- import { type NodeProps, useSvelteFlow } from '@xyflow/svelte';
3
- import NodeParameter from './NodeParameter.svelte';
4
-
5
- type $$Props = NodeProps;
6
-
7
- export let containerStyle = '';
8
- export let id: $$Props['id']; id;
9
- export let data: $$Props['data'];
10
- export let dragHandle: $$Props['dragHandle'] = undefined; dragHandle;
11
- export let type: $$Props['type'] = undefined; type;
12
- export let selected: $$Props['selected'] = undefined; selected;
13
- export let isConnectable: $$Props['isConnectable'] = undefined; isConnectable;
14
- export let zIndex: $$Props['zIndex'] = undefined; zIndex;
15
- export let width: $$Props['width'] = undefined; width;
16
- export let height: $$Props['height'] = undefined; height;
17
- export let dragging: $$Props['dragging']; dragging;
18
- export let targetPosition: $$Props['targetPosition'] = undefined; targetPosition;
19
- export let sourcePosition: $$Props['sourcePosition'] = undefined; sourcePosition;
20
- export let positionAbsoluteX: $$Props['positionAbsoluteX'] = undefined; positionAbsoluteX;
21
- export let positionAbsoluteY: $$Props['positionAbsoluteY'] = undefined; positionAbsoluteY;
22
-
23
- function asPx(n: number | undefined) {
24
- return n ? n + 'px' : undefined;
25
- }
26
- const { updateNodeData } = useSvelteFlow();
27
- $: metaParams = data.meta?.params;
28
- </script>
29
-
30
- <div class="area" style:width={asPx(width)} style:height={asPx(height)} style={containerStyle}>
31
- <div class="title">
32
- {data.title}
33
- </div>
34
- {#each Object.entries(data.params) as [name, value]}
35
- <NodeParameter
36
- {name}
37
- {value}
38
- meta={metaParams?.[name]}
39
- onChange={(newValue) => updateNodeData(id, { params: { ...data.params, [name]: newValue } })}
40
- />
41
- {/each}
42
- </div>
43
-
44
- <style>
45
- .area {
46
- border-radius: 10px;
47
- border: 3px dashed oklch(75% 0.2 55);
48
- z-index: 0 !important;
49
- }
50
- .title {
51
- color: oklch(75% 0.2 55);
52
- width: 100%;
53
- text-align: center;
54
- top: -1.5em;
55
- position: absolute;
56
- -webkit-text-stroke: 5px white;
57
- paint-order: stroke fill;
58
- font-weight: bold;
59
- }
60
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeWithImage.svelte DELETED
@@ -1,14 +0,0 @@
1
- <script lang="ts">
2
- import { type NodeProps } from '@xyflow/svelte';
3
- import NodeWithParams from './NodeWithParams.svelte';
4
- type $$Props = NodeProps;
5
- export let data: $$Props['data'];
6
- </script>
7
-
8
- <NodeWithParams {...$$props}>
9
- {#if data.display}
10
- <img src={data.display}/>
11
- {/if}
12
- </NodeWithParams>
13
- <style>
14
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeWithParams.svelte DELETED
@@ -1,30 +0,0 @@
1
- <script lang="ts">
2
- import { getContext } from 'svelte';
3
- import { type NodeProps, useNodes } from '@xyflow/svelte';
4
- import LynxKiteNode from './LynxKiteNode.svelte';
5
- import NodeParameter from './NodeParameter.svelte';
6
- type $$Props = NodeProps;
7
- export let id: $$Props['id'];
8
- export let data: $$Props['data'];
9
- $: metaParams = data.meta?.params;
10
- $: store = getContext('LynxKite store');
11
- function setParam(name, newValue) {
12
- const i = $store.workspace.nodes.findIndex((n) => n.id === id);
13
- $store.workspace.nodes[i].data.params[name] = newValue;
14
- }
15
- $: params = $nodes && data?.params ? Object.entries(data.params) : [];
16
- const nodes = useNodes(); // We don't properly get updates to "data". This is a hack.
17
- $: props = $nodes && $$props;
18
- </script>
19
-
20
- <LynxKiteNode {...props}>
21
- {#each params as [name, value]}
22
- <NodeParameter
23
- {name}
24
- {value}
25
- meta={metaParams?.[name]}
26
- onChange={(value) => setParam(name, value)}
27
- />
28
- {/each}
29
- <slot />
30
- </LynxKiteNode>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeWithSubFlow.svelte DELETED
@@ -1,37 +0,0 @@
1
- <script lang="ts">
2
- import { type NodeProps, useNodes } from '@xyflow/svelte';
3
- import LynxKiteNode from './LynxKiteNode.svelte';
4
- type $$Props = NodeProps;
5
- const nodes = useNodes();
6
- export let id: $$Props['id'];
7
- export let data: $$Props['data'];
8
- let isExpanded = true;
9
- function onToggle({ expanded }) {
10
- isExpanded = expanded;
11
- nodes.update((n) =>
12
- n.map((node) =>
13
- node.parentId === id
14
- ? { ...node, hidden: !expanded }
15
- : node));
16
- }
17
- function computeSize(nodes) {
18
- let width = 200;
19
- let height = 200;
20
- for (const node of nodes) {
21
- if (node.parentId === id) {
22
- width = Math.max(width, node.position.x + 300);
23
- height = Math.max(height, node.position.y + 200);
24
- }
25
- }
26
- return { width, height };
27
- }
28
- $: ({ width, height } = computeSize($nodes));
29
- </script>
30
-
31
- <LynxKiteNode
32
- {...$$props}
33
- width={isExpanded && width} height={isExpanded && height}
34
- nodeStyle="background: transparent;" containerStyle="max-width: none; max-height: none;" {onToggle}>
35
- </LynxKiteNode>
36
- <style>
37
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeWithTableView.svelte DELETED
@@ -1,58 +0,0 @@
1
- <script lang="ts">
2
- import { useNodes, type NodeProps } from '@xyflow/svelte';
3
- import LynxKiteNode from './LynxKiteNode.svelte';
4
- import Table from './Table.svelte';
5
- import SvelteMarkdown from 'svelte-markdown'
6
- type $$Props = NodeProps;
7
- export let data: $$Props['data'];
8
- const nodes = useNodes(); // We don't properly get updates to "data". This is a hack.
9
- $: D = $nodes && data;
10
- const open = {};
11
- $: single = D.display?.value?.dataframes && Object.keys(D.display.value.dataframes).length === 1;
12
- function toMD(v) {
13
- if (typeof v === 'string') {
14
- return v;
15
- }
16
- if (Array.isArray(v)) {
17
- return v.map(toMD).join('\n\n');
18
- }
19
- return JSON.stringify(v);
20
- }
21
- </script>
22
-
23
- <LynxKiteNode {...$$props}>
24
- {#if D?.display?.value}
25
- {#each Object.entries(D.display.value.dataframes || {}) as [name, df]}
26
- {#if !single}<div class="df-head" on:click={() => open[name] = !open[name]}>{name}</div>{/if}
27
- {#if single || open[name]}
28
- {#if df.data.length > 1}
29
- <Table columns={df.columns} data={df.data} />
30
- {:else}
31
- <dl>
32
- {#each df.columns as c, i}
33
- <dt>{c}</dt>
34
- <dd><SvelteMarkdown source={toMD(df.data[0][i])} /></dd>
35
- {/each}
36
- </dl>
37
- {/if}
38
- {/if}
39
- {/each}
40
- {#each Object.entries(D.display.value.others || {}) as [name, o]}
41
- <div class="df-head" on:click={() => open[name] = !open[name]}>{name}</div>
42
- {#if open[name]}
43
- <pre>{o}</pre>
44
- {/if}
45
- {/each}
46
- {/if}
47
- </LynxKiteNode>
48
- <style>
49
- .df-head {
50
- font-weight: bold;
51
- padding: 8px;
52
- background: #f0f0f0;
53
- cursor: pointer;
54
- }
55
- dl {
56
- margin: 10px;
57
- }
58
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/NodeWithVisualization.svelte DELETED
@@ -1,17 +0,0 @@
1
- <script lang="ts">
2
- import { useNodes, type NodeProps } from '@xyflow/svelte';
3
- import NodeWithParams from './NodeWithParams.svelte';
4
- import { Chart } from 'svelte-echarts';
5
- import { init } from 'echarts';
6
- type $$Props = NodeProps;
7
- export let data: $$Props['data'];
8
-
9
- const nodes = useNodes(); // We don't properly get updates to "data". This is a hack.
10
- $: D = $nodes && data;
11
- </script>
12
-
13
- <NodeWithParams {...$$props}>
14
- <Chart {init} options={D?.display?.value || {}} initOptions={{renderer: 'canvas', width: 250, height: 250}}/>
15
- </NodeWithParams>
16
- <style>
17
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/Table.svelte DELETED
@@ -1,22 +0,0 @@
1
- <script lang="ts">
2
- export let data, columns;
3
- </script>
4
-
5
- <table>
6
- <thead>
7
- <tr>
8
- {#each columns as column}
9
- <th>{column}</th>
10
- {/each}
11
- </tr>
12
- </thead>
13
- <tbody>
14
- {#each data as row}
15
- <tr>
16
- {#each columns as column}
17
- <td>{row[column]}</td>
18
- {/each}
19
- </tr>
20
- {/each}
21
- </tbody>
22
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/Workspace.svelte DELETED
@@ -1,14 +0,0 @@
1
- <script lang="ts">
2
- // This is the whole LynxKite workspace editor page.
3
- import { QueryClient, QueryClientProvider } from '@sveltestack/svelte-query'
4
- import { SvelteFlowProvider } from '@xyflow/svelte';
5
- import LynxKiteFlow from './LynxKiteFlow.svelte';
6
- export let path = '';
7
- const queryClient = new QueryClient()
8
- </script>
9
-
10
- <QueryClientProvider client={queryClient}>
11
- <SvelteFlowProvider>
12
- <LynxKiteFlow path={path} />
13
- </SvelteFlowProvider>
14
- </QueryClientProvider>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/app.scss DELETED
@@ -1,38 +0,0 @@
1
- // Import all of Bootstrap's CSS
2
- $form-select-indicator-color: oklch(90% 0.01 55);
3
- @import "bootstrap/scss/bootstrap";
4
- :root {
5
- --bs-border-color: oklch(90% 0.01 55);
6
- }
7
-
8
- path.svelte-flow__edge-path {
9
- stroke-width: 2;
10
- stroke: black;
11
- }
12
- .svelte-flow__edge.selected path.svelte-flow__edge-path {
13
- outline: var(--xy-selection-border, var(--xy-selection-border-default));
14
- outline-offset: 10px;
15
- border-radius: 1px;
16
- }
17
- .svelte-flow__handle {
18
- border-color: black;
19
- background: white;
20
- width: 10px;
21
- height: 10px;
22
- }
23
- .svelte-flow__arrowhead * {
24
- stroke: none;
25
- fill: black;
26
- }
27
- // We want the area node to be above the sub-flow node if its inside the sub-flow.
28
- // This will need some more thinking for a general solution.
29
- .svelte-flow__node-sub_flow {
30
- z-index: -20 !important;
31
- }
32
- .svelte-flow__node-area {
33
- z-index: -10 !important;
34
- }
35
- .selected .lynxkite-node {
36
- outline: var(--xy-selection-border, var(--xy-selection-border-default));
37
- outline-offset: 7.5px;
38
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/directory.css DELETED
@@ -1,10 +0,0 @@
1
- @media (min-width: 640px) {
2
- .directory {
3
- width: 100%;
4
- }
5
- }
6
-
7
- .directory {
8
- width: 800px;
9
- background: white;
10
- }
 
 
 
 
 
 
 
 
 
 
 
web/src/main.ts DELETED
@@ -1,10 +0,0 @@
1
- import App from './App.svelte';
2
-
3
- import './app.scss';
4
- import * as bootstrap from 'bootstrap';
5
-
6
- const app = new App({
7
- target: document.getElementById('app')!,
8
- });
9
-
10
- export default app;
 
 
 
 
 
 
 
 
 
 
 
web/src/vite-env.d.ts DELETED
@@ -1,2 +0,0 @@
1
- /// <reference types="svelte" />
2
- /// <reference types="vite/client" />
 
 
 
web/svelte.config.js DELETED
@@ -1,7 +0,0 @@
1
- import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
2
-
3
- export default {
4
- // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
5
- // for more information about preprocessors
6
- preprocess: vitePreprocess(),
7
- }
 
 
 
 
 
 
 
 
web/tailwind.config.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [require('daisyui')],
8
+ };
web/tailwind.config.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from "tailwindcss";
2
+
3
+ export default {
4
+ content: [
5
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
7
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
8
+ ],
9
+ theme: {
10
+ extend: {
11
+ colors: {
12
+ background: "var(--background)",
13
+ foreground: "var(--foreground)",
14
+ },
15
+ },
16
+ },
17
+ plugins: [],
18
+ } satisfies Config;
web/tsconfig.json CHANGED
@@ -1,20 +1,27 @@
1
  {
2
- "extends": "@tsconfig/svelte/tsconfig.json",
3
  "compilerOptions": {
4
- "target": "ESNext",
5
- "useDefineForClassFields": true,
6
- "module": "ESNext",
7
- "resolveJsonModule": true,
8
- /**
9
- * Typecheck JS in `.svelte` and `.js` files by default.
10
- * Disable checkJs if you'd like to use dynamic types in JS.
11
- * Note that setting allowJs false does not prevent the use
12
- * of JS in `.svelte` files.
13
- */
14
  "allowJs": true,
15
- "checkJs": true,
16
- "isolatedModules": true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  },
18
- "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
19
- "references": [{ "path": "./tsconfig.node.json" }]
20
  }
 
1
  {
 
2
  "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
 
 
 
 
 
 
 
 
5
  "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
  },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
  }
web/tsconfig.node.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "composite": true,
4
- "skipLibCheck": true,
5
- "module": "ESNext",
6
- "moduleResolution": "bundler"
7
- },
8
- "include": ["vite.config.ts"]
9
- }