Spaces:
Running
Running
Start switching to React.
Browse files- web/.eslintrc.json +3 -0
- web/.gitignore +36 -9
- web/.vscode/extensions.json +0 -3
- web/README.md +26 -17
- web/{public → app}/favicon.ico +0 -0
- web/app/globals.css +51 -0
- web/app/layout.tsx +35 -0
- web/app/page.tsx +101 -0
- web/app/workspace/EnvironmentSelector.tsx +12 -0
- web/app/workspace/page.tsx +224 -0
- web/index.html +0 -13
- web/next.config.ts +24 -0
- web/package-lock.json +0 -0
- web/package.json +33 -30
- web/postcss.config.js +6 -0
- web/postcss.config.mjs +8 -0
- web/{src/assets → public}/logo.png +0 -0
- web/src/App.svelte +0 -22
- web/src/Directory.svelte +0 -177
- web/src/EnvironmentSelector.svelte +0 -14
- web/src/LynxKiteFlow.svelte +0 -230
- web/src/LynxKiteNode.svelte +0 -174
- web/src/NodeParameter.svelte +0 -69
- web/src/NodeSearch.svelte +0 -100
- web/src/NodeWithArea.svelte +0 -60
- web/src/NodeWithImage.svelte +0 -14
- web/src/NodeWithParams.svelte +0 -30
- web/src/NodeWithSubFlow.svelte +0 -37
- web/src/NodeWithTableView.svelte +0 -58
- web/src/NodeWithVisualization.svelte +0 -17
- web/src/Table.svelte +0 -22
- web/src/Workspace.svelte +0 -14
- web/src/app.scss +0 -38
- web/src/directory.css +0 -10
- web/src/main.ts +0 -10
- web/src/vite-env.d.ts +0 -2
- web/svelte.config.js +0 -7
- web/tailwind.config.js +8 -0
- web/tailwind.config.ts +18 -0
- web/tsconfig.json +22 -15
- web/tsconfig.node.json +0 -9
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 |
-
#
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
npm-debug.log*
|
5 |
yarn-debug.log*
|
6 |
yarn-error.log*
|
7 |
-
pnpm-debug.log*
|
8 |
-
lerna-debug.log*
|
9 |
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
|
3 |
-
|
4 |
|
5 |
-
|
6 |
|
7 |
-
```
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
```
|
10 |
|
11 |
-
|
12 |
|
13 |
-
|
14 |
-
npm install
|
15 |
-
```
|
16 |
|
17 |
-
|
18 |
|
19 |
-
|
20 |
-
npm run dev
|
21 |
-
```
|
22 |
|
23 |
-
|
24 |
|
25 |
-
|
26 |
-
|
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": "
|
|
|
3 |
"private": true,
|
4 |
-
"version": "0.0.0",
|
5 |
-
"type": "module",
|
6 |
"scripts": {
|
7 |
-
"dev": "
|
8 |
-
"build": "
|
9 |
-
"
|
10 |
-
"
|
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.
|
29 |
-
"@
|
30 |
-
"@
|
31 |
-
"@xyflow/
|
32 |
-
"
|
33 |
-
"
|
34 |
-
"
|
35 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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": "
|
5 |
-
"
|
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 |
-
"
|
16 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
},
|
18 |
-
"include": ["
|
19 |
-
"
|
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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|