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>
|