darabos commited on
Commit
aef8814
·
unverified ·
2 Parent(s): 41cc084 bcfe577

Merge pull request #146 from biggraph/darabos-folders

Browse files
README.md CHANGED
@@ -41,7 +41,7 @@ uv pip install -e lynxkite-core/[dev] -e lynxkite-app/[dev] -e lynxkite-graph-an
41
  This also builds the frontend, hopefully very quickly. To run it:
42
 
43
  ```bash
44
- cd examples && LYNXKITE_RELOAD=1 lynxkite
45
  ```
46
 
47
  If you also want to make changes to the frontend with hot reloading:
 
41
  This also builds the frontend, hopefully very quickly. To run it:
42
 
43
  ```bash
44
+ cd examples && lynxkite
45
  ```
46
 
47
  If you also want to make changes to the frontend with hot reloading:
lynxkite-app/src/lynxkite_app/crdt.py CHANGED
@@ -301,14 +301,14 @@ def sanitize_path(path):
301
  return os.path.relpath(os.path.normpath(os.path.join("/", path)), "/")
302
 
303
 
304
- @router.websocket("/ws/crdt/{room_name}")
305
  async def crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
306
  room_name = sanitize_path(room_name)
307
  server = pycrdt_websocket.ASGIServer(ws_websocket_server)
308
  await server({"path": room_name}, websocket._receive, websocket._send)
309
 
310
 
311
- @router.websocket("/ws/code/crdt/{room_name}")
312
  async def code_crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
313
  room_name = sanitize_path(room_name)
314
  server = pycrdt_websocket.ASGIServer(code_websocket_server)
 
301
  return os.path.relpath(os.path.normpath(os.path.join("/", path)), "/")
302
 
303
 
304
+ @router.websocket("/ws/crdt/{room_name:path}")
305
  async def crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
306
  room_name = sanitize_path(room_name)
307
  server = pycrdt_websocket.ASGIServer(ws_websocket_server)
308
  await server({"path": room_name}, websocket._receive, websocket._send)
309
 
310
 
311
+ @router.websocket("/ws/code/crdt/{room_name:path}")
312
  async def code_crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
313
  room_name = sanitize_path(room_name)
314
  server = pycrdt_websocket.ASGIServer(code_websocket_server)
lynxkite-app/web/playwright.config.ts CHANGED
@@ -2,19 +2,18 @@ import { defineConfig, devices } from "@playwright/test";
2
 
3
  export default defineConfig({
4
  testDir: "./tests",
5
- timeout: 30000,
6
  fullyParallel: false,
7
  /* Fail the build on CI if you accidentally left test.only in the source code. */
8
  forbidOnly: !!process.env.CI,
9
- retries: process.env.CI ? 1 : 0,
10
- maxFailures: 5,
11
  workers: 1,
12
  reporter: process.env.CI ? [["github"], ["html"]] : "html",
13
  use: {
14
  /* Base URL to use in actions like `await page.goto('/')`. */
15
  baseURL: "http://127.0.0.1:8000",
16
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
17
- trace: "on-first-retry",
18
  testIdAttribute: "data-nodeid", // Useful for easily selecting nodes using getByTestId
19
  },
20
  projects: [
@@ -24,7 +23,7 @@ export default defineConfig({
24
  },
25
  ],
26
  webServer: {
27
- command: "cd ../../examples && lynxkite",
28
  port: 8000,
29
  reuseExistingServer: false,
30
  },
 
2
 
3
  export default defineConfig({
4
  testDir: "./tests",
5
+ timeout: process.env.CI ? 20000 : 10000,
6
  fullyParallel: false,
7
  /* Fail the build on CI if you accidentally left test.only in the source code. */
8
  forbidOnly: !!process.env.CI,
9
+ retries: 0,
10
+ maxFailures: 3,
11
  workers: 1,
12
  reporter: process.env.CI ? [["github"], ["html"]] : "html",
13
  use: {
14
  /* Base URL to use in actions like `await page.goto('/')`. */
15
  baseURL: "http://127.0.0.1:8000",
16
+ trace: "on",
 
17
  testIdAttribute: "data-nodeid", // Useful for easily selecting nodes using getByTestId
18
  },
19
  projects: [
 
23
  },
24
  ],
25
  webServer: {
26
+ command: "cd ../../examples && LYNXKITE_SUPPRESS_OP_ERRORS=1 lynxkite",
27
  port: 8000,
28
  reuseExistingServer: false,
29
  },
lynxkite-app/web/src/Code.tsx CHANGED
@@ -3,7 +3,7 @@
3
  import Editor, { type Monaco } from "@monaco-editor/react";
4
  import type { editor } from "monaco-editor";
5
  import { useEffect, useRef } from "react";
6
- import { Link, useParams } from "react-router";
7
  import { WebsocketProvider } from "y-websocket";
8
  import * as Y from "yjs";
9
  // @ts-ignore
@@ -14,9 +14,10 @@ import Backspace from "~icons/tabler/backspace.jsx";
14
  import Close from "~icons/tabler/x.jsx";
15
  import favicon from "./assets/favicon.ico";
16
  import theme from "./code-theme.ts";
 
17
 
18
  export default function Code() {
19
- const { path } = useParams();
20
  const parentDir = path!.split("/").slice(0, -1).join("/");
21
  const yDocRef = useRef<any>();
22
  const wsProviderRef = useRef<any>();
 
3
  import Editor, { type Monaco } from "@monaco-editor/react";
4
  import type { editor } from "monaco-editor";
5
  import { useEffect, useRef } from "react";
6
+ import { Link } from "react-router";
7
  import { WebsocketProvider } from "y-websocket";
8
  import * as Y from "yjs";
9
  // @ts-ignore
 
14
  import Close from "~icons/tabler/x.jsx";
15
  import favicon from "./assets/favicon.ico";
16
  import theme from "./code-theme.ts";
17
+ import { usePath } from "./common.ts";
18
 
19
  export default function Code() {
20
+ const path = usePath().replace(/^[/]code[/]/, "");
21
  const parentDir = path!.split("/").slice(0, -1).join("/");
22
  const yDocRef = useRef<any>();
23
  const wsProviderRef = useRef<any>();
lynxkite-app/web/src/Directory.tsx CHANGED
@@ -1,8 +1,9 @@
1
  import { useState } from "react";
2
  // The directory browser.
3
- import { Link, useNavigate, useParams } from "react-router";
4
  import useSWR from "swr";
5
  import type { DirectoryEntry } from "./apiTypes.ts";
 
6
 
7
  // @ts-ignore
8
  import File from "~icons/tabler/file";
@@ -58,7 +59,7 @@ function EntryCreator(props: {
58
  const fetcher = (url: string) => fetch(url).then((res) => res.json());
59
 
60
  export default function () {
61
- const { path } = useParams();
62
  const encodedPath = encodeURIComponent(path || "");
63
  const list = useSWR(`/api/dir/list?path=${encodedPath}`, fetcher, {
64
  dedupingInterval: 0,
@@ -160,7 +161,7 @@ export default function () {
160
 
161
  {path ? (
162
  <div className="breadcrumbs">
163
- <Link to="/dir/">
164
  <Home />
165
  </Link>{" "}
166
  <span className="current-folder">{path}</span>
 
1
  import { useState } from "react";
2
  // The directory browser.
3
+ import { Link, useNavigate } from "react-router";
4
  import useSWR from "swr";
5
  import type { DirectoryEntry } from "./apiTypes.ts";
6
+ import { usePath } from "./common.ts";
7
 
8
  // @ts-ignore
9
  import File from "~icons/tabler/file";
 
59
  const fetcher = (url: string) => fetch(url).then((res) => res.json());
60
 
61
  export default function () {
62
+ const path = usePath().replace(/^[/]$|^[/]dir$|^[/]dir[/]/, "");
63
  const encodedPath = encodeURIComponent(path || "");
64
  const list = useSWR(`/api/dir/list?path=${encodedPath}`, fetcher, {
65
  dedupingInterval: 0,
 
161
 
162
  {path ? (
163
  <div className="breadcrumbs">
164
+ <Link to="/dir/" aria-label="home">
165
  <Home />
166
  </Link>{" "}
167
  <span className="current-folder">{path}</span>
lynxkite-app/web/src/common.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { useLocation } from "react-router";
2
+
3
+ export function usePath() {
4
+ // Decode special characters. Drop trailing slash. (Some clients add it, e.g. Playwright.)
5
+ const path = decodeURIComponent(useLocation().pathname).replace(/[/]$/, "");
6
+ return path;
7
+ }
lynxkite-app/web/src/main.tsx CHANGED
@@ -13,9 +13,9 @@ createRoot(document.getElementById("root")!).render(
13
  <Routes>
14
  <Route path="/" element={<Directory />} />
15
  <Route path="/dir" element={<Directory />} />
16
- <Route path="/dir/:path" element={<Directory />} />
17
- <Route path="/edit/:path" element={<Workspace />} />
18
- <Route path="/code/:path" element={<Code />} />
19
  </Routes>
20
  </BrowserRouter>
21
  </StrictMode>,
 
13
  <Routes>
14
  <Route path="/" element={<Directory />} />
15
  <Route path="/dir" element={<Directory />} />
16
+ <Route path="/dir/*" element={<Directory />} />
17
+ <Route path="/edit/*" element={<Workspace />} />
18
+ <Route path="/code/*" element={<Code />} />
19
  </Routes>
20
  </BrowserRouter>
21
  </StrictMode>,
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -18,7 +18,7 @@ import {
18
  } from "@xyflow/react";
19
  import axios from "axios";
20
  import { type MouseEvent, useCallback, useEffect, useMemo, useState } from "react";
21
- import { useParams } from "react-router";
22
  import useSWR, { type Fetcher } from "swr";
23
  import { WebsocketProvider } from "y-websocket";
24
  // @ts-ignore
@@ -31,6 +31,7 @@ import Restart from "~icons/tabler/rotate-clockwise.jsx";
31
  import Close from "~icons/tabler/x.jsx";
32
  import type { Workspace, WorkspaceNode } from "../apiTypes.ts";
33
  import favicon from "../assets/favicon.ico";
 
34
  // import NodeWithTableView from './NodeWithTableView';
35
  import EnvironmentSelector from "./EnvironmentSelector";
36
  import { LynxKiteState } from "./LynxKiteState";
@@ -55,7 +56,7 @@ function LynxKiteFlow() {
55
  const reactFlow = useReactFlow();
56
  const [nodes, setNodes] = useState([] as Node[]);
57
  const [edges, setEdges] = useState([] as Edge[]);
58
- const { path } = useParams();
59
  const shortPath = path!
60
  .split("/")
61
  .pop()!
@@ -335,9 +336,9 @@ function LynxKiteFlow() {
335
  return (
336
  <div className="workspace">
337
  <div className="top-bar bg-neutral">
338
- <a className="logo" href="">
339
  <img alt="" src={favicon} />
340
- </a>
341
  <div className="ws-name">{shortPath}</div>
342
  <title>{shortPath}</title>
343
  <EnvironmentSelector
@@ -357,9 +358,9 @@ function LynxKiteFlow() {
357
  <button className="btn btn-link" onClick={executeWorkspace}>
358
  <Restart />
359
  </button>
360
- <a className="btn btn-link" href={`/dir/${parentDir}`}>
361
  <Close />
362
- </a>
363
  </div>
364
  </div>
365
  <div style={{ height: "100%", width: "100vw" }} onDragOver={onDragOver} onDrop={onDrop}>
 
18
  } from "@xyflow/react";
19
  import axios from "axios";
20
  import { type MouseEvent, useCallback, useEffect, useMemo, useState } from "react";
21
+ import { Link } from "react-router";
22
  import useSWR, { type Fetcher } from "swr";
23
  import { WebsocketProvider } from "y-websocket";
24
  // @ts-ignore
 
31
  import Close from "~icons/tabler/x.jsx";
32
  import type { Workspace, WorkspaceNode } from "../apiTypes.ts";
33
  import favicon from "../assets/favicon.ico";
34
+ import { usePath } from "../common.ts";
35
  // import NodeWithTableView from './NodeWithTableView';
36
  import EnvironmentSelector from "./EnvironmentSelector";
37
  import { LynxKiteState } from "./LynxKiteState";
 
56
  const reactFlow = useReactFlow();
57
  const [nodes, setNodes] = useState([] as Node[]);
58
  const [edges, setEdges] = useState([] as Edge[]);
59
+ const path = usePath().replace(/^[/]edit[/]/, "");
60
  const shortPath = path!
61
  .split("/")
62
  .pop()!
 
336
  return (
337
  <div className="workspace">
338
  <div className="top-bar bg-neutral">
339
+ <Link className="logo" to="/">
340
  <img alt="" src={favicon} />
341
+ </Link>
342
  <div className="ws-name">{shortPath}</div>
343
  <title>{shortPath}</title>
344
  <EnvironmentSelector
 
358
  <button className="btn btn-link" onClick={executeWorkspace}>
359
  <Restart />
360
  </button>
361
+ <Link className="btn btn-link" to={`/dir/${parentDir}`} aria-label="close">
362
  <Close />
363
+ </Link>
364
  </div>
365
  </div>
366
  <div style={{ height: "100%", width: "100vw" }} onDragOver={onDragOver} onDrop={onDrop}>
lynxkite-app/web/tests/lynxkite.ts CHANGED
@@ -155,7 +155,7 @@ export class Workspace {
155
  }
156
 
157
  async close() {
158
- await this.page.locator('a[href="/dir/"]').click();
159
  }
160
  }
161
 
@@ -220,6 +220,6 @@ export class Splash {
220
  }
221
 
222
  async goHome() {
223
- await this.page.locator('a[href="/dir/"]').click();
224
  }
225
  }
 
155
  }
156
 
157
  async close() {
158
+ await this.page.getByRole("link", { name: "close" }).click();
159
  }
160
  }
161
 
 
220
  }
221
 
222
  async goHome() {
223
+ await this.page.getByRole("link", { name: "home" }).click();
224
  }
225
  }
lynxkite-core/src/lynxkite/core/workspace.py CHANGED
@@ -102,12 +102,14 @@ class Workspace(BaseConfig):
102
  return self
103
  catalog = ops.CATALOGS[self.env]
104
  _ops = {n.id: catalog[n.data.title] for n in self.nodes if n.data.title in catalog}
105
- valid_targets = set(
106
- (n.id, h) for n in self.nodes for h in _ops[n.id].inputs if n.id in _ops
107
- )
108
- valid_sources = set(
109
- (n.id, h) for n in self.nodes for h in _ops[n.id].outputs if n.id in _ops
110
- )
 
 
111
  edges = [
112
  edge
113
  for edge in self.edges
 
102
  return self
103
  catalog = ops.CATALOGS[self.env]
104
  _ops = {n.id: catalog[n.data.title] for n in self.nodes if n.data.title in catalog}
105
+ valid_targets = set()
106
+ valid_sources = set()
107
+ for n in self.nodes:
108
+ if n.id in _ops:
109
+ for h in _ops[n.id].inputs:
110
+ valid_targets.add((n.id, h))
111
+ for h in _ops[n.id].outputs:
112
+ valid_sources.add((n.id, h))
113
  edges = [
114
  edge
115
  for edge in self.edges