Skip to content

Commit a0a2b20

Browse files
authored
Merge pull request #4 from panel-extensions/cleanup_and_tests
Cleanup and tests
2 parents 890b4fe + dbbd152 commit a0a2b20

4 files changed

Lines changed: 256 additions & 39 deletions

File tree

examples/advanced.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ class CustomEditor(ParamNodeEditor):
4343
]
4444

4545
edges = [
46-
{"id": "e1", "source": "start", "target": "process"},
47-
{"id": "e2", "source": "process", "target": "finish"},
46+
{"id": "e1", "source": "start", "target": "process", "label": "0.2"},
47+
{"id": "e2", "source": "process", "target": "finish", "label": "0.5"},
4848
]
4949

5050
status = pn.pane.Markdown("**Last event:** _none_")

src/panel_reactflow/base.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,25 +206,25 @@ def from_dict(cls, payload: dict[str, Any]) -> "EdgeSpec":
206206

207207

208208
class NodeEditor(Viewer):
209-
id = param.String(default="", doc="ID of the node.", constant=True)
210-
data = param.Dict(default={}, doc="Data for the node.")
209+
_id = param.String(default="", doc="ID of the node.", constant=True)
210+
_data = param.Dict(default={}, doc="Data for the node.")
211211

212212

213213
class JsonNodeEditor(NodeEditor):
214214
def __panel__(self):
215-
return JSONEditor.from_param(self.param.data)
215+
return JSONEditor.from_param(self.param._data)
216216

217217

218218
class ParamNodeEditor(NodeEditor):
219219
def __init__(self, **params):
220-
params.update(params.get("data", {}))
220+
params.update({p: v for p, v in params.get("_data", {}).items() if p in type(self).param})
221221
super().__init__(**params)
222222
edit_params = [p for p in self.param if p not in NodeEditor.param]
223223
self.param.watch(self._update_data, edit_params)
224224
self._panel = pn.Param(self, parameters=edit_params, show_name=False, margin=0, default_layout=Paper)
225225

226226
def _update_data(self, *events: tuple[param.parameterized.Event]) -> None:
227-
self.data = dict(self.data, **{event.name: event.new for event in events})
227+
self._data = dict(self._data, **{event.name: event.new for event in events})
228228

229229
def __panel__(self):
230230
return self._panel
@@ -335,8 +335,8 @@ def _update_node_editors(self, *events: tuple[param.parameterized.Event]) -> Non
335335
editors[node_id] = self._node_editors[node_id]
336336
continue
337337
editor_cls = self.node_types.get(node.get("type", "panel"), JsonNodeEditor)
338-
editors[node_id] = editor = editor_cls(id=node_id, data=node.get("data", {}))
339-
self._node_watchers[node_id] = editor.param.watch(self._apply_node_editor_changes, "data")
338+
editors[node_id] = editor = editor_cls(_id=node_id, _data=node.get("data", {}))
339+
self._node_watchers[node_id] = editor.param.watch(self._apply_node_editor_changes, "_data")
340340
self._node_editors = editors
341341
self.param.trigger("_node_editor_views")
342342

src/panel_reactflow/models/reactflow.jsx

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ function FlowInner({
202202
model,
203203
hydratedNodes,
204204
pyNodes,
205-
pyEdges,
205+
hydratedEdges,
206206
selectionSetter,
207207
currentSelection,
208208
views,
@@ -221,7 +221,7 @@ function FlowInner({
221221
viewport,
222222
}) {
223223
const [nodes, setNodes, onNodesChange] = useNodesState(hydratedNodes);
224-
const [edges, setEdges, onEdgesChange] = useEdgesState(pyEdges);
224+
const [edges, setEdges, onEdgesChange] = useEdgesState(hydratedEdges);
225225
const nodesRef = useRef(nodes);
226226
const edgesRef = useRef(edges);
227227
const lastHydrated = useRef({ nodesSig: null, viewsRef: null, edgesSig: null });
@@ -252,7 +252,8 @@ function FlowInner({
252252
return edge;
253253
}
254254
const data = { ...(edge.data || {}), ...(msg.patch || {}) };
255-
return { ...edge, data };
255+
const nextLabel = msg.patch?.label ?? edge.label;
256+
return { ...edge, data, label: nextLabel };
256257
})
257258
);
258259
}
@@ -293,12 +294,12 @@ function FlowInner({
293294
}, [hydratedNodes, pyNodes, setNodes]);
294295

295296
useEffect(() => {
296-
const edgesSig = signature(pyEdges);
297+
const edgesSig = signature(hydratedEdges);
297298
if (edgesSig !== lastHydrated.current.edgesSig) {
298299
lastHydrated.current.edgesSig = edgesSig;
299-
setEdges(pyEdges);
300+
setEdges(hydratedEdges);
300301
}
301-
}, [pyEdges, setEdges]);
302+
}, [hydratedEdges, setEdges]);
302303

303304
useEffect(() => {
304305
if (viewport) {
@@ -336,15 +337,24 @@ function FlowInner({
336337
[enableConnect, sendPatch, setEdges]
337338
);
338339

339-
const onNodeDragStop = useCallback(
340-
(_event, node) => {
341-
schedulePatch({
342-
type: "node_moved",
343-
node_id: node.id,
344-
position: node.position,
340+
const handleNodesChange = useCallback(
341+
(changes) => {
342+
onNodesChange(changes);
343+
const moved = changes.filter(
344+
(change) => change.type === "position" && change.dragging !== true
345+
);
346+
if (!moved.length) {
347+
return;
348+
}
349+
moved.forEach((change) => {
350+
schedulePatch({
351+
type: "node_moved",
352+
node_id: change.id,
353+
position: change.position,
354+
});
345355
});
346356
},
347-
[schedulePatch]
357+
[onNodesChange, schedulePatch]
348358
);
349359

350360
const onSelectionChange = useCallback(
@@ -409,9 +419,8 @@ function FlowInner({
409419
edges={edges}
410420
nodeTypes={nodeTypes}
411421
defaultEdgeOptions={defaultEdgeOptions}
412-
onNodesChange={onNodesChange}
422+
onNodesChange={handleNodesChange}
413423
onEdgesChange={onEdgesChange}
414-
onNodeDragStop={onNodeDragStop}
415424
onSelectionChange={onSelectionChange}
416425
onNodesDelete={onNodesDelete}
417426
onEdgesDelete={onEdgesDelete}
@@ -478,6 +487,17 @@ export function render({ model, view }) {
478487
});
479488
}, [pyNodes, nodeEditors, views, editorMode]);
480489

490+
const hydratedEdges = useMemo(() => {
491+
return (pyEdges || []).map((edge) => {
492+
const data = edge.data || {};
493+
const label = edge.label ?? data.label;
494+
if (label === undefined) {
495+
return edge;
496+
}
497+
return { ...edge, data, label };
498+
});
499+
}, [pyEdges]);
500+
481501
const nodeTypes = useMemo(() => {
482502
const mapping = {};
483503
Object.entries(BUILTIN_NODE_TYPES).forEach(([typeName, spec]) => {
@@ -498,7 +518,7 @@ export function render({ model, view }) {
498518
model={model}
499519
hydratedNodes={hydratedNodes}
500520
pyNodes={pyNodes || []}
501-
pyEdges={pyEdges || []}
521+
hydratedEdges={hydratedEdges}
502522
selectionSetter={setSelection}
503523
currentSelection={selection}
504524
views={views}

0 commit comments

Comments
 (0)