darabos commited on
Commit
be095f5
·
1 Parent(s): 2fb9e9d

Missed these files. Workspace in React.

Browse files
web/app/workspace/LynxKiteState.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import { createContext } from "react";
2
+
3
+ export const LynxKiteState = createContext({ workspace: {} as any });
web/app/workspace/Workspace.tsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+ import useSWR from 'swr';
3
+ import { useMemo } from "react";
4
+ import { useSearchParams } from 'next/navigation';
5
+ import {
6
+ ReactFlow,
7
+ useNodesState,
8
+ useEdgesState,
9
+ Controls,
10
+ MiniMap,
11
+ MarkerType,
12
+ useReactFlow,
13
+ type XYPosition,
14
+ type Node,
15
+ type Edge,
16
+ type Connection,
17
+ type NodeTypes,
18
+ } from '@xyflow/react';
19
+ // @ts-ignore
20
+ import ArrowBack from '~icons/tabler/arrow-back.jsx';
21
+ // @ts-ignore
22
+ import Backspace from '~icons/tabler/backspace.jsx';
23
+ // @ts-ignore
24
+ import Atom from '~icons/tabler/atom.jsx';
25
+ import { syncedStore, getYjsDoc } from "@syncedstore/core";
26
+ import { useSyncedStore } from "@syncedstore/react";
27
+ import { WebsocketProvider } from "y-websocket";
28
+ import NodeWithParams from './nodes/NodeWithParams';
29
+ // import NodeWithVisualization from './NodeWithVisualization';
30
+ // import NodeWithImage from './NodeWithImage';
31
+ // import NodeWithTableView from './NodeWithTableView';
32
+ // import NodeWithSubFlow from './NodeWithSubFlow';
33
+ // import NodeWithArea from './NodeWithArea';
34
+ // import NodeSearch from './NodeSearch';
35
+ import EnvironmentSelector from './EnvironmentSelector';
36
+ import { LynxKiteState } from './LynxKiteState';
37
+ import '@xyflow/react/dist/style.css';
38
+
39
+ export default function Workspace() {
40
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
41
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
42
+ const searchParams = useSearchParams();
43
+
44
+ let path = searchParams.get('path');
45
+ const sstore = syncedStore({ workspace: {} });
46
+ const doc = getYjsDoc(sstore);
47
+ const wsProvider = new WebsocketProvider("ws://localhost:8000/ws/crdt", path, doc);
48
+ const state = useSyncedStore(sstore);
49
+
50
+ const fetcher = (resource: string, init?: RequestInit) => fetch(resource, init).then(res => res.json());
51
+ const catalog = useSWR('/api/catalog', fetcher);
52
+
53
+ const nodeTypes = useMemo(() => ({
54
+ basic: NodeWithParams,
55
+ table_view: NodeWithParams,
56
+ }), []);
57
+ return (
58
+ <div className="workspace">
59
+ <div className="top-bar bg-neutral">
60
+ <a className="logo" href=""><img src="/favicon.ico" /></a>
61
+ <div className="ws-name">
62
+ {path}
63
+ </div>
64
+ <EnvironmentSelector
65
+ options={Object.keys(catalog.data || {})}
66
+ value={state.workspace?.env}
67
+ onChange={(env) => state.workspace.env = env}
68
+ />
69
+ <div className="tools text-secondary">
70
+ <a href=""><Atom /></a>
71
+ <a href=""><Backspace /></a>
72
+ <a href="#dir?path={parentDir}"><ArrowBack /></a>
73
+ </div>
74
+ </div>
75
+ <div style={{ height: "100%", width: '100vw' }}>
76
+ <LynxKiteState.Provider value={state}>
77
+ <ReactFlow nodes={state.workspace?.nodes} edges={state.workspace?.edges} nodeTypes={nodeTypes} fitView
78
+ proOptions={{ hideAttribution: true }}
79
+ maxZoom={3}
80
+ minZoom={0.3}
81
+ defaultEdgeOptions={{ markerEnd: { type: MarkerType.Arrow } }}
82
+ >
83
+ <Controls />
84
+ <MiniMap />
85
+ {/* {#if nodeSearchSettings}
86
+ <NodeSearch pos={nodeSearchSettings.pos} boxes={nodeSearchSettings.boxes} on:cancel={closeNodeSearch} on:add={addNode} />
87
+ {/if} */}
88
+ </ReactFlow>
89
+ </LynxKiteState.Provider>
90
+ </div>
91
+ </div>
92
+
93
+ );
94
+ }
web/app/workspace/nodes/LynxKiteNode.tsx ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { LynxKiteState } from '../LynxKiteState';
3
+ import { Handle, NodeResizeControl } from '@xyflow/react';
4
+ // @ts-ignore
5
+ import ChevronDownRight from '~icons/tabler/chevron-down-right.jsx';
6
+
7
+ interface LynxKiteNodeProps {
8
+ width: number;
9
+ height: number;
10
+ nodeStyle: any;
11
+ data: any;
12
+ children: any;
13
+ }
14
+
15
+ function getHandles(inputs, outputs) {
16
+ const handles: {
17
+ position: 'top' | 'bottom' | 'left' | 'right',
18
+ name: string,
19
+ index: number,
20
+ offsetPercentage: number,
21
+ showLabel: boolean,
22
+ }[] = [];
23
+ for (const e of Object.values(inputs)) {
24
+ handles.push({ ...e, type: 'target' });
25
+ }
26
+ for (const e of Object.values(outputs)) {
27
+ handles.push({ ...e, type: 'source' });
28
+ }
29
+ const counts = { top: 0, bottom: 0, left: 0, right: 0 };
30
+ for (const e of handles) {
31
+ e.index = counts[e.position];
32
+ counts[e.position]++;
33
+ }
34
+ for (const e of handles) {
35
+ e.offsetPercentage = 100 * (e.index + 1) / (counts[e.position] + 1);
36
+ const simpleHorizontal = counts.top === 0 && counts.bottom === 0 && handles.length <= 2;
37
+ const simpleVertical = counts.left === 0 && counts.right === 0 && handles.length <= 2;
38
+ e.showLabel = !simpleHorizontal && !simpleVertical;
39
+ }
40
+ return handles;
41
+ }
42
+
43
+ export default function LynxKiteNode(props: LynxKiteNodeProps) {
44
+ const data = props.data;
45
+ const state = useContext(LynxKiteState);
46
+ const expanded = true;
47
+ const handles = getHandles(data.meta?.inputs || {}, data.meta?.outputs || {});
48
+ function asPx(n: number | undefined) {
49
+ return (n ? n + 'px' : undefined) || '200px';
50
+ }
51
+ function titleClicked() { }
52
+ function updateNodeData() { }
53
+ const handleOffsetDirection = { top: 'left', bottom: 'left', left: 'top', right: 'top' };
54
+
55
+ return (
56
+ <div className={'node-container ' + (expanded ? 'expanded' : 'collapsed')}
57
+ style={{ width: asPx(props.width), height: asPx(expanded ? props.height : undefined) }}>
58
+ <div className="lynxkite-node" style={props.nodeStyle}>
59
+ <div className="title bg-primary" onClick={titleClicked}>
60
+ {data.title}
61
+ {data.error && <span className="title-icon">⚠️</span>}
62
+ {expanded || <span className="title-icon">⋯</span>}
63
+ </div>
64
+ {expanded && <>
65
+ {data.error &&
66
+ <div className="error">{data.error}</div>
67
+ }
68
+ {props.children}
69
+ {handles.map(handle => (
70
+ <Handle
71
+ key={handle.name}
72
+ id={handle.name} type={handle.type} position={handle.position}
73
+ style={{ [handleOffsetDirection[handle.position]]: handle.offsetPercentage + '%' }}>
74
+ {handle.showLabel && <span className="handle-name">{handle.name.replace(/_/g, " ")}</span>}
75
+ </Handle>
76
+ ))}
77
+ <NodeResizeControl
78
+ minWidth={100}
79
+ minHeight={50}
80
+ style={{ 'background': 'transparent', 'border': 'none' }}
81
+ onResizeStart={() => updateNodeData(id, { beingResized: true })}
82
+ onResizeEnd={() => updateNodeData(id, { beingResized: false })}
83
+ >
84
+ <ChevronDownRight className="node-resizer" />
85
+ </NodeResizeControl>
86
+ </>}
87
+ </div>
88
+ </div>
89
+ );
90
+ }
web/app/workspace/nodes/NodeParameter.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const BOOLEAN = "<class 'bool'>";
2
+
3
+ function ParamName({ name }) {
4
+ return <span className="param-name bg-base-200">{name.replace(/_/g, ' ')}</span>;
5
+ }
6
+
7
+ export default function NodeParameter({ name, value, meta, onChange }) {
8
+ return (
9
+ <label className="param">
10
+ {meta?.type?.format === 'collapsed' ? <>
11
+ <ParamName name={name} />
12
+ <button className="collapsed-param">
13
+
14
+ </button>
15
+ </> : meta?.type?.format === 'textarea' ? <>
16
+ <ParamName name={name} />
17
+ <textarea className="textarea textarea-bordered w-full max-w-xs"
18
+ rows={6}
19
+ value={value}
20
+ onChange={(evt) => onChange(evt.currentTarget.value)}
21
+ />
22
+ </> : meta?.type?.enum ? <>
23
+ <ParamName name={name} />
24
+ <select className="select select-bordered w-full max-w-xs"
25
+ value={value || meta.type.enum[0]}
26
+ onChange={(evt) => onChange(evt.currentTarget.value)}
27
+ >
28
+ {meta.type.enum.map(option =>
29
+ <option key={option} value={option}>{option}</option>
30
+ )}
31
+ </select>
32
+ </> : meta?.type?.type === BOOLEAN ? <div className="form-control">
33
+ <label className="label cursor-pointer">
34
+ <input className="checkbox"
35
+ type="checkbox"
36
+ checked={value}
37
+ onChange={(evt) => onChange(evt.currentTarget.checked)}
38
+ />
39
+ {name.replace(/_/g, ' ')}
40
+ </label>
41
+ </div> : <>
42
+ <ParamName name={name} />
43
+ <input className="input input-bordered w-full max-w-xs"
44
+ value={value || ""}
45
+ onChange={(evt) => onChange(evt.currentTarget.value)}
46
+ />
47
+ </>
48
+ }
49
+ </label >
50
+ );
51
+ }
web/app/workspace/nodes/NodeWithParams.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { LynxKiteState } from '../LynxKiteState';
3
+ import LynxKiteNode from './LynxKiteNode';
4
+ import { useNodesState } from '@xyflow/react';
5
+ import NodeParameter from './NodeParameter';
6
+
7
+ function NodeWithParams(props) {
8
+ const metaParams = props.data.meta?.params;
9
+ const state = useContext(LynxKiteState);
10
+ function setParam(name, newValue) {
11
+ const i = state.workspace.nodes.findIndex((n) => n.id === props.id);
12
+ state.workspace.nodes[i].data.params[name] = newValue;
13
+ }
14
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
15
+ const params = nodes && props.data?.params ? Object.entries(props.data.params) : [];
16
+
17
+ return (
18
+ <LynxKiteNode {...props}>
19
+ {params.map(([name, value]) =>
20
+ <NodeParameter
21
+ name={name}
22
+ key={name}
23
+ value={value}
24
+ meta={metaParams?.[name]}
25
+ onChange={(value) => setParam(name, value)}
26
+ />
27
+ )}
28
+ </LynxKiteNode >
29
+ );
30
+ }
31
+
32
+ export default NodeWithParams;