Spaces:
Running
Running
Resizing possibly works.
Browse files- server/crdt.py +5 -1
- web/src/index.css +33 -0
- web/src/workspace/Workspace.tsx +8 -7
- web/src/workspace/nodes/LynxKiteNode.tsx +2 -4
server/crdt.py
CHANGED
@@ -130,17 +130,21 @@ async def workspace_changed(name, changes, ws_crdt):
|
|
130 |
from . import workspace
|
131 |
|
132 |
ws_pyd = workspace.Workspace.model_validate(ws_crdt.to_py())
|
|
|
|
|
133 |
clean_input(ws_pyd)
|
134 |
if ws_pyd == last_known_versions.get(name):
|
135 |
return
|
136 |
last_known_versions[name] = ws_pyd.model_copy(deep=True)
|
|
|
|
|
137 |
if name in delayed_executions:
|
138 |
delayed_executions[name].cancel()
|
139 |
delay = min(
|
140 |
change.keys.get("__execution_delay", {}).get("newValue", 0)
|
141 |
for change in changes
|
142 |
)
|
143 |
-
if delay
|
144 |
task = asyncio.create_task(execute(ws_crdt, ws_pyd, delay))
|
145 |
delayed_executions[name] = task
|
146 |
else:
|
|
|
130 |
from . import workspace
|
131 |
|
132 |
ws_pyd = workspace.Workspace.model_validate(ws_crdt.to_py())
|
133 |
+
# Do not trigger execution for superficial changes.
|
134 |
+
# This is a quick solution until we build proper caching.
|
135 |
clean_input(ws_pyd)
|
136 |
if ws_pyd == last_known_versions.get(name):
|
137 |
return
|
138 |
last_known_versions[name] = ws_pyd.model_copy(deep=True)
|
139 |
+
# Frontend changes that result from typing are delayed to avoid
|
140 |
+
# rerunning the workspace for every keystroke.
|
141 |
if name in delayed_executions:
|
142 |
delayed_executions[name].cancel()
|
143 |
delay = min(
|
144 |
change.keys.get("__execution_delay", {}).get("newValue", 0)
|
145 |
for change in changes
|
146 |
)
|
147 |
+
if delay:
|
148 |
task = asyncio.create_task(execute(ws_crdt, ws_pyd, delay))
|
149 |
delayed_executions[name] = task
|
150 |
else:
|
web/src/index.css
CHANGED
@@ -259,3 +259,36 @@ body {
|
|
259 |
margin: 10px;
|
260 |
}
|
261 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
margin: 10px;
|
260 |
}
|
261 |
}
|
262 |
+
|
263 |
+
|
264 |
+
path.react-flow__edge-path {
|
265 |
+
stroke-width: 2;
|
266 |
+
stroke: black;
|
267 |
+
}
|
268 |
+
.react-flow__edge.selected path.react-flow__edge-path {
|
269 |
+
outline: var(--xy-selection-border, var(--xy-selection-border-default));
|
270 |
+
outline-offset: 10px;
|
271 |
+
border-radius: 1px;
|
272 |
+
}
|
273 |
+
.react-flow__handle {
|
274 |
+
border-color: black;
|
275 |
+
background: white;
|
276 |
+
width: 10px;
|
277 |
+
height: 10px;
|
278 |
+
}
|
279 |
+
.react-flow__arrowhead * {
|
280 |
+
stroke: none;
|
281 |
+
fill: black;
|
282 |
+
}
|
283 |
+
// We want the area node to be above the sub-flow node if its inside the sub-flow.
|
284 |
+
// This will need some more thinking for a general solution.
|
285 |
+
.react-flow__node-sub_flow {
|
286 |
+
z-index: -20 !important;
|
287 |
+
}
|
288 |
+
.react-flow__node-area {
|
289 |
+
z-index: -10 !important;
|
290 |
+
}
|
291 |
+
.selected .lynxkite-node {
|
292 |
+
outline: var(--xy-selection-border, var(--xy-selection-border-default));
|
293 |
+
outline-offset: 7.5px;
|
294 |
+
}
|
web/src/workspace/Workspace.tsx
CHANGED
@@ -97,15 +97,16 @@ function LynxKiteFlow() {
|
|
97 |
});
|
98 |
} else if (ch.type === 'select') {
|
99 |
} else if (ch.type === 'dimensions') {
|
|
|
100 |
} else if (ch.type === 'replace') {
|
101 |
// Ideally we would only update the parameter that changed. But ReactFlow does not give us that detail.
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
} else {
|
110 |
console.log('Unknown node change', ch);
|
111 |
}
|
|
|
97 |
});
|
98 |
} else if (ch.type === 'select') {
|
99 |
} else if (ch.type === 'dimensions') {
|
100 |
+
getYjsDoc(state).transact(() => Object.assign(node, ch.dimensions));
|
101 |
} else if (ch.type === 'replace') {
|
102 |
// Ideally we would only update the parameter that changed. But ReactFlow does not give us that detail.
|
103 |
+
const u = {
|
104 |
+
collapsed: ch.item.data.collapsed,
|
105 |
+
// The "..." expansion on a Y.map returns an empty object. Copying with fromEntries/entries instead.
|
106 |
+
params: { ...Object.fromEntries(Object.entries(ch.item.data.params)) },
|
107 |
+
__execution_delay: ch.item.data.__execution_delay,
|
108 |
+
};
|
109 |
+
getYjsDoc(state).transact(() => Object.assign(node.data, u));
|
110 |
} else {
|
111 |
console.log('Unknown node change', ch);
|
112 |
}
|
web/src/workspace/nodes/LynxKiteNode.tsx
CHANGED
@@ -73,17 +73,15 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
|
|
73 |
{handles.map(handle => (
|
74 |
<Handle
|
75 |
key={handle.name}
|
76 |
-
id={handle.name} type={handle.type} position={
|
77 |
style={{ [handleOffsetDirection[handle.position]]: handle.offsetPercentage + '%' }}>
|
78 |
{handle.showLabel && <span className="handle-name">{handle.name.replace(/_/g, " ")}</span>}
|
79 |
-
</Handle>
|
80 |
))}
|
81 |
<NodeResizeControl
|
82 |
minWidth={100}
|
83 |
minHeight={50}
|
84 |
style={{ 'background': 'transparent', 'border': 'none' }}
|
85 |
-
onResizeStart={() => reactFlow.updateNodeData(props.id, { beingResized: true })}
|
86 |
-
onResizeEnd={() => reactFlow.updateNodeData(props.id, { beingResized: false })}
|
87 |
>
|
88 |
<ChevronDownRight className="node-resizer" />
|
89 |
</NodeResizeControl>
|
|
|
73 |
{handles.map(handle => (
|
74 |
<Handle
|
75 |
key={handle.name}
|
76 |
+
id={handle.name} type={handle.type} position={handle.position as Position}
|
77 |
style={{ [handleOffsetDirection[handle.position]]: handle.offsetPercentage + '%' }}>
|
78 |
{handle.showLabel && <span className="handle-name">{handle.name.replace(/_/g, " ")}</span>}
|
79 |
+
</Handle >
|
80 |
))}
|
81 |
<NodeResizeControl
|
82 |
minWidth={100}
|
83 |
minHeight={50}
|
84 |
style={{ 'background': 'transparent', 'border': 'none' }}
|
|
|
|
|
85 |
>
|
86 |
<ChevronDownRight className="node-resizer" />
|
87 |
</NodeResizeControl>
|