From 90254d2f3b398940e590bba006573d8ac7e2dee9 Mon Sep 17 00:00:00 2001 From: Corentin MUNOZ Date: Wed, 14 Jan 2026 11:09:44 +0100 Subject: [PATCH 1/3] feat(CustomNode): add Handle component --- examples/nodes.py | 9 ++++++++- src/trame_flow/widgets/flow.py | 27 ++++++++++++++++++++++++++ vue-components/src/components/index.js | 2 ++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/examples/nodes.py b/examples/nodes.py index d6e684b..6edbf54 100644 --- a/examples/nodes.py +++ b/examples/nodes.py @@ -6,7 +6,13 @@ VIcon, VSelect, ) -from trame_flow.widgets.flow import CustomNode, Node, NodeEditor, create_node +from trame_flow.widgets.flow import ( + CustomNode, + Handle, + Node, + NodeEditor, + create_node, +) class Example(TrameApp): @@ -63,6 +69,7 @@ def build_ui(self): with NodeEditor() as self.vueflow: with CustomNode("title"): + Handle(type="target", position="top") H4("{{props.data.label}}") P("{{props.data.subtitle}}") with CustomNode("text", var_name="nodeProps"): diff --git a/src/trame_flow/widgets/flow.py b/src/trame_flow/widgets/flow.py index c73b6c2..8604109 100644 --- a/src/trame_flow/widgets/flow.py +++ b/src/trame_flow/widgets/flow.py @@ -23,6 +23,7 @@ def __init__(self, _elem_name, children=None, **kwargs): "EdgeType", "Extent", "Graph", + "Handle", "HandlePosition", "Node", "NodeEditor", @@ -214,6 +215,32 @@ def __init__(self, type: str, var_name: str = "props", **kwargs): super().__init__(raw_attrs=[f"v-slot:node-{type}={var_name}"], **kwargs) +class Handle(HtmlElement): + def __init__( + self, + position: HandlePosition, + type: Literal["source", "target"], + **kwargs, + ): + """Handle used to connect a node to other nodes. Use it inside a `CustomNode`. + + :param position: Position of the handle. + :param type: Type of the handle. + :param connectable: (Optional) Boolean, integer for maximum connection number or function that returns a boolean. + :param id: (Optional) ID of the handle. Useful when using multiple source handles or target handles. + :param connection_mode: (Optional) Defines if a handle can be connected to another handle with the same type. "loose" (default) or "strict". + """ + super().__init__("Handle", position=position, type=type, **kwargs) + + self._attr_names += [ + "connectable", + ("connection_mode", "connection-mode"), + "id", + "position", + "type", + ] + + class NodeEditor(HtmlElement): """ Node Editor based on VueFlow. diff --git a/vue-components/src/components/index.js b/vue-components/src/components/index.js index e7d87b8..29905be 100644 --- a/vue-components/src/components/index.js +++ b/vue-components/src/components/index.js @@ -1,5 +1,7 @@ import NodeEditor from "./NodeEditor"; +import { Handle } from "@vue-flow/core"; export default { NodeEditor, + Handle, }; From 0ce0e286400eb128d0f3c2f258eaddc5dfc1be5e Mon Sep 17 00:00:00 2001 From: Corentin MUNOZ Date: Wed, 14 Jan 2026 14:45:52 +0100 Subject: [PATCH 2/3] refactor!: separate core classes and widgets --- examples/nodes.py | 3 +- examples/serialization.py | 3 +- examples/style.py | 3 +- src/trame_flow/module/core.py | 187 +++++++++++++++ src/trame_flow/widgets/flow/__init__.py | 7 + .../widgets/{flow.py => flow/node_editor.py} | 222 +----------------- 6 files changed, 209 insertions(+), 216 deletions(-) create mode 100644 src/trame_flow/module/core.py create mode 100644 src/trame_flow/widgets/flow/__init__.py rename src/trame_flow/widgets/{flow.py => flow/node_editor.py} (64%) diff --git a/examples/nodes.py b/examples/nodes.py index 6edbf54..d68397d 100644 --- a/examples/nodes.py +++ b/examples/nodes.py @@ -6,12 +6,11 @@ VIcon, VSelect, ) +from trame_flow.module.core import Node, create_node from trame_flow.widgets.flow import ( CustomNode, Handle, - Node, NodeEditor, - create_node, ) diff --git a/examples/serialization.py b/examples/serialization.py index 9636c08..81d1e9e 100644 --- a/examples/serialization.py +++ b/examples/serialization.py @@ -6,7 +6,8 @@ VRow, VTextarea, ) -from trame_flow.widgets.flow import Node, NodeEditor +from trame_flow.module.core import Node +from trame_flow.widgets.flow import NodeEditor DEFAULT_GRAPH = """{ "nodes": [ diff --git a/examples/style.py b/examples/style.py index 895b777..8f7fe29 100644 --- a/examples/style.py +++ b/examples/style.py @@ -8,7 +8,8 @@ VRow, VSlider, ) -from trame_flow.widgets.flow import NodeEditor, create_node +from trame_flow.module.core import create_node +from trame_flow.widgets.flow import NodeEditor class Example(TrameApp): diff --git a/src/trame_flow/module/core.py b/src/trame_flow/module/core.py new file mode 100644 index 0000000..13ade82 --- /dev/null +++ b/src/trame_flow/module/core.py @@ -0,0 +1,187 @@ +from typing import Literal, Optional, Union + +from typing_extensions import NotRequired, TypedDict + +__all__ = [ + "DEFAULT_EXTENT", + "Dimensions", + "Edge", + "EdgeMarkerType", + "EdgeType", + "Extent", + "Graph", + "HandlePosition", + "Node", + "NodeType", + "Position", + "create_edge", + "create_node", +] + + +class Position(TypedDict): + x: float + y: float + + +HandlePosition = Literal["top", "bottom", "left", "right"] + + +class Dimensions(TypedDict): + height: float + width: float + + +# "parent" or [[x-from, y-from], [x-to, y-to]] +Extent = Union[Literal["parent"], list[list[float]]] +DEFAULT_EXTENT = [[float("-inf"), float("-inf")], [float("+inf"), float("+inf")]] + +NodeType = Literal["default", "input", "output"] | str + + +Node = TypedDict( + "Node", + { + "ariaLabel": NotRequired[str], + "class": NotRequired[str], + "connectable": NotRequired[bool], + "data": NotRequired[dict], + "deletable": NotRequired[bool], + "draggable": NotRequired[bool], + "expandParent": NotRequired[bool], + "extent": NotRequired[Extent], + "focusable": NotRequired[bool], + "height": Union[int, str], + "hidden": NotRequired[bool], + "id": str, + "parentNode": NotRequired[str], + "position": Position, + "selectable": NotRequired[bool], + "sourcePosition": NotRequired[HandlePosition], + "style": NotRequired[dict], + "targetPosition": NotRequired[HandlePosition], + "type": NodeType, + "width": Union[int, str], + "zIndex": NotRequired[int], + }, +) + + +def create_node( + id: str, + type: NodeType, + x: float, + y: float, + label: str, + parent_id: Optional[str] = None, + expand_parent: bool = False, + extent: Optional[Extent] = None, + width: Union[int, str] = "auto", + height: Union[int, str] = "auto", + style: Optional[dict] = None, + data: Optional[dict] = None, +) -> Node: + """Helper function to build a Node.""" + node = Node( + id=id, + type=type, + data={"label": label}, + position=Position(x=x, y=y), + expandParent=expand_parent, + width=width, + height=height, + ) + if extent: + node["extent"] = extent + if extent == "parent" and parent_id is None: + parent_id = "" + if parent_id: + node["parentNode"] = parent_id + if style: + node["style"] = style + if data: + node["data"] = node["data"] | data + # set default node style for custom node + if type not in ["default", "input", "output"]: + node["class"] = "vue-flow__node-default" + return node + + +EdgeType = Literal["default", "step", "smoothstep", "straight"] + +EdgeMarkerType = Literal["arrow", "arrowclosed"] + + +class EdgeMarker(TypedDict): + color: NotRequired[str] + height: NotRequired[float] + id: NotRequired[str] + markerUnits: NotRequired[str] + orient: NotRequired[str] + strokeWidth: NotRequired[float] + type: EdgeMarkerType + width: NotRequired[float] + + +Edge = TypedDict( + "Edge", + { + "animated": NotRequired[bool], + "ariaLabel": NotRequired[str], + "class": NotRequired[str], + "data": NotRequired[dict], + "deletable": NotRequired[bool], + "focusable": NotRequired[bool], + "hidden": NotRequired[bool], + "id": str, + "interactionWidth": NotRequired[float], + "label": NotRequired[str], + "labelBgBorderRadius": NotRequired[float], + "labelBgPadding": NotRequired[tuple[float, float]], + "labelBgStyle": NotRequired[dict], + "labelShowBg": NotRequired[bool], + "labelStyle": NotRequired[dict], + "markerEnd": NotRequired[Union[EdgeMarkerType, EdgeMarker]], + "markerStart": NotRequired[Union[EdgeMarkerType, EdgeMarker]], + "selectable": NotRequired[bool], + "source": str, + "style": NotRequired[dict], + "target": str, + "type": EdgeType, + "zIndex": NotRequired[int], + }, +) + + +def create_edge( + source_id: str, + target_id: str, + type: EdgeType = "default", + label: Optional[str] = None, + animated: bool = False, + marker_start: Optional[Union[EdgeMarkerType, EdgeMarker]] = None, + marker_end: Optional[Union[EdgeMarkerType, EdgeMarker]] = None, + style: Optional[dict] = None, +): + """Helper function to build an edge.""" + edge = Edge( + id=f"{source_id}->{target_id}", + source=source_id, + target=target_id, + type=type, + animated=animated, + ) + if label: + edge["label"] = label + if marker_start: + edge["markerStart"] = marker_start + if marker_end: + edge["markerEnd"] = marker_end + if style: + edge["style"] = style + return edge + + +class Graph(TypedDict): + nodes: list[Node] + edges: list[Edge] diff --git a/src/trame_flow/widgets/flow/__init__.py b/src/trame_flow/widgets/flow/__init__.py new file mode 100644 index 0000000..271212d --- /dev/null +++ b/src/trame_flow/widgets/flow/__init__.py @@ -0,0 +1,7 @@ +from trame_flow.widgets.flow.node_editor import CustomNode, Handle, NodeEditor + +__all__ = [ + "CustomNode", + "Handle", + "NodeEditor", +] diff --git a/src/trame_flow/widgets/flow.py b/src/trame_flow/widgets/flow/node_editor.py similarity index 64% rename from src/trame_flow/widgets/flow.py rename to src/trame_flow/widgets/flow/node_editor.py index 8604109..269e7a1 100644 --- a/src/trame_flow/widgets/flow.py +++ b/src/trame_flow/widgets/flow/node_editor.py @@ -1,207 +1,23 @@ from ast import literal_eval -from typing import Callable, Literal, Optional, Union +from typing import Callable, Literal -from trame_client.widgets.core import AbstractElement, Template -from typing_extensions import NotRequired, TypedDict - -from .. import module - - -class HtmlElement(AbstractElement): - def __init__(self, _elem_name, children=None, **kwargs): - super().__init__(_elem_name, children, **kwargs) - if self.server: - self.server.enable_module(module) +from trame_client.widgets.core import Template +from trame_flow.module.core import ( + Edge, + Graph, + HandlePosition, + Node, +) +from trame_flow.widgets.flow.common import HtmlElement __all__ = [ - "DEFAULT_EXTENT", "CustomNode", - "Dimensions", - "Edge", - "EdgeMarkerType", - "EdgeType", - "Extent", - "Graph", "Handle", - "HandlePosition", - "Node", "NodeEditor", - "NodeType", - "Position", - "create_edge", - "create_node", ] -class Position(TypedDict): - x: float - y: float - - -HandlePosition = Literal["top", "bottom", "left", "right"] - - -class Dimensions(TypedDict): - height: float - width: float - - -# "parent" or [[x-from, y-from], [x-to, y-to]] -Extent = Union[Literal["parent"], list[list[float]]] -DEFAULT_EXTENT = [[float("-inf"), float("-inf")], [float("+inf"), float("+inf")]] - -NodeType = Literal["default", "input", "output"] | str - - -Node = TypedDict( - "Node", - { - "ariaLabel": NotRequired[str], - "class": NotRequired[str], - "connectable": NotRequired[bool], - "data": NotRequired[dict], - "deletable": NotRequired[bool], - "draggable": NotRequired[bool], - "expandParent": NotRequired[bool], - "extent": NotRequired[Extent], - "focusable": NotRequired[bool], - "height": Union[int, str], - "hidden": NotRequired[bool], - "id": str, - "parentNode": NotRequired[str], - "position": Position, - "selectable": NotRequired[bool], - "sourcePosition": NotRequired[HandlePosition], - "style": NotRequired[dict], - "targetPosition": NotRequired[HandlePosition], - "type": NodeType, - "width": Union[int, str], - "zIndex": NotRequired[int], - }, -) - - -def create_node( - id: str, - type: NodeType, - x: float, - y: float, - label: str, - parent_id: Optional[str] = None, - expand_parent: bool = False, - extent: Optional[Extent] = None, - width: Union[int, str] = "auto", - height: Union[int, str] = "auto", - style: Optional[dict] = None, - data: Optional[dict] = None, -) -> Node: - """Helper function to build a Node.""" - node = Node( - id=id, - type=type, - data={"label": label}, - position=Position(x=x, y=y), - expandParent=expand_parent, - width=width, - height=height, - ) - if extent: - node["extent"] = extent - if extent == "parent" and parent_id is None: - parent_id = "" - if parent_id: - node["parentNode"] = parent_id - if style: - node["style"] = style - if data: - node["data"] = node["data"] | data - # set default node style for custom node - if type not in ["default", "input", "output"]: - node["class"] = "vue-flow__node-default" - return node - - -EdgeType = Literal["default", "step", "smoothstep", "straight"] - -EdgeMarkerType = Literal["arrow", "arrowclosed"] - - -class EdgeMarker(TypedDict): - color: NotRequired[str] - height: NotRequired[float] - id: NotRequired[str] - markerUnits: NotRequired[str] - orient: NotRequired[str] - strokeWidth: NotRequired[float] - type: EdgeMarkerType - width: NotRequired[float] - - -Edge = TypedDict( - "Edge", - { - "animated": NotRequired[bool], - "ariaLabel": NotRequired[str], - "class": NotRequired[str], - "data": NotRequired[dict], - "deletable": NotRequired[bool], - "focusable": NotRequired[bool], - "hidden": NotRequired[bool], - "id": str, - "interactionWidth": NotRequired[float], - "label": NotRequired[str], - "labelBgBorderRadius": NotRequired[float], - "labelBgPadding": NotRequired[tuple[float, float]], - "labelBgStyle": NotRequired[dict], - "labelShowBg": NotRequired[bool], - "labelStyle": NotRequired[dict], - "markerEnd": NotRequired[Union[EdgeMarkerType, EdgeMarker]], - "markerStart": NotRequired[Union[EdgeMarkerType, EdgeMarker]], - "selectable": NotRequired[bool], - "source": str, - "style": NotRequired[dict], - "target": str, - "type": EdgeType, - "zIndex": NotRequired[int], - }, -) - - -def create_edge( - source_id: str, - target_id: str, - type: EdgeType = "default", - label: Optional[str] = None, - animated: bool = False, - marker_start: Optional[Union[EdgeMarkerType, EdgeMarker]] = None, - marker_end: Optional[Union[EdgeMarkerType, EdgeMarker]] = None, - style: Optional[dict] = None, -): - """Helper function to build an edge.""" - edge = Edge( - id=f"{source_id}->{target_id}", - source=source_id, - target=target_id, - type=type, - animated=animated, - ) - if label: - edge["label"] = label - if marker_start: - edge["markerStart"] = marker_start - if marker_end: - edge["markerEnd"] = marker_end - if style: - edge["style"] = style - return edge - - -class Graph(TypedDict): - nodes: list[Node] - edges: list[Edge] - - class CustomNode(Template): def __init__(self, type: str, var_name: str = "props", **kwargs): """Shortcut to define a custom node's HTML. @@ -242,25 +58,7 @@ def __init__( class NodeEditor(HtmlElement): - """ - Node Editor based on VueFlow. - - Args: - background_pattern_color (html color string): - Color of the pattern in the background. - background_pattern_gap (number): - Size of the gaps for the pattern in the background. - background_pattern_size (number): - Size of the pattern in the background. - background_pattern_variant ("dots" or "lines"): - Pattern type in the background. - show_controls (boolean): - Show the controls panel (zoom in, zoom out, fit view, lock graph). - Enabled by default. - show_mini_map (boolean): - Show the nodes mini map. - Enabled by default. - """ + """Node Editor based on VueFlow.""" _next_id = 0 From a8a681942ae01bf1a6e1667b57fcbce58ef038bf Mon Sep 17 00:00:00 2001 From: Corentin MUNOZ Date: Wed, 14 Jan 2026 14:48:30 +0100 Subject: [PATCH 3/3] feat: add all vueflow components Add Background, Controls, ControlsButton, MiniMap, MiniMapNode, NodeResizer and NodeToolbar --- examples/nodes.py | 18 +++++- examples/serialization.py | 7 ++- examples/style.py | 8 ++- src/trame_flow/widgets/flow/__init__.py | 12 ++++ src/trame_flow/widgets/flow/background.py | 27 +++++++++ src/trame_flow/widgets/flow/common.py | 10 ++++ src/trame_flow/widgets/flow/controls.py | 40 ++++++++++++++ src/trame_flow/widgets/flow/minimap.py | 61 +++++++++++++++++++++ src/trame_flow/widgets/flow/node_editor.py | 8 --- src/trame_flow/widgets/flow/node_resizer.py | 33 +++++++++++ src/trame_flow/widgets/flow/node_toolbar.py | 22 ++++++++ tests/test_import.py | 26 ++++++++- vue-components/package-lock.json | 28 +++++++++- vue-components/package.json | 4 +- vue-components/src/components/NodeEditor.js | 37 ------------- vue-components/src/components/index.js | 12 ++++ vue-components/src/components/style.css | 1 + 17 files changed, 299 insertions(+), 55 deletions(-) create mode 100644 src/trame_flow/widgets/flow/background.py create mode 100644 src/trame_flow/widgets/flow/common.py create mode 100644 src/trame_flow/widgets/flow/controls.py create mode 100644 src/trame_flow/widgets/flow/minimap.py create mode 100644 src/trame_flow/widgets/flow/node_resizer.py create mode 100644 src/trame_flow/widgets/flow/node_toolbar.py diff --git a/examples/nodes.py b/examples/nodes.py index d68397d..2cf27ea 100644 --- a/examples/nodes.py +++ b/examples/nodes.py @@ -8,9 +8,13 @@ ) from trame_flow.module.core import Node, create_node from trame_flow.widgets.flow import ( + Background, + Controls, CustomNode, Handle, NodeEditor, + NodeResizer, + NodeToolbar, ) @@ -49,7 +53,9 @@ def build_ui(self): with layout.toolbar: VSelect( label="Node type", - items=("['default', 'input', 'output', 'text', 'title']",), + items=( + "['default', 'input', 'output', 'text', 'title', 'toolbar']", + ), v_model=("node_type", "default"), density="compact", hide_details="true", @@ -67,12 +73,22 @@ def build_ui(self): VIcon("mdi-minus") with NodeEditor() as self.vueflow: + Background(gap=10, size=1, pattern_color="#81818a") + Controls() with CustomNode("title"): Handle(type="target", position="top") H4("{{props.data.label}}") P("{{props.data.subtitle}}") with CustomNode("text", var_name="nodeProps"): + NodeResizer() P("{{nodeProps.data.label}}") + with CustomNode("toolbar"): + with NodeToolbar( + is_visible=True, + style="background: darkgray; padding: 4px; font-size: 1.2em; border-radius: 4px", + ): + P("This is a toolbar") + P("{{props.data.label}}") def on_graph_change(nodes, edges): with self.state: diff --git a/examples/serialization.py b/examples/serialization.py index 81d1e9e..63d4e75 100644 --- a/examples/serialization.py +++ b/examples/serialization.py @@ -7,7 +7,7 @@ VTextarea, ) from trame_flow.module.core import Node -from trame_flow.widgets.flow import NodeEditor +from trame_flow.widgets.flow import Background, Controls, NodeEditor DEFAULT_GRAPH = """{ "nodes": [ @@ -123,8 +123,9 @@ def build_ui(self): VContainer(fluid=True, classes="h-100"), VRow(classes="h-100"), ): - with VCol(): - self.vueflow = NodeEditor() + with VCol(), NodeEditor() as self.vueflow: + Background(gap=10, size=1, pattern_color="#81818a") + Controls() with VCol(): VTextarea( density="compact", diff --git a/examples/style.py b/examples/style.py index 8f7fe29..3dc3379 100644 --- a/examples/style.py +++ b/examples/style.py @@ -9,7 +9,7 @@ VSlider, ) from trame_flow.module.core import create_node -from trame_flow.widgets.flow import NodeEditor +from trame_flow.widgets.flow import Background, Controls, NodeEditor class Example(TrameApp): @@ -48,7 +48,11 @@ def build_ui(self): VRow(classes="h-100"), ): with VCol(): - self.vueflow = NodeEditor(show_mini_map=False, show_controls=False) + with NodeEditor( + show_mini_map=False, show_controls=False + ) as self.vueflow: + Background(gap=10, size=1, pattern_color="#81818a") + Controls() self.vueflow.add_node( create_node(id="0", type="default", x=0, y=0, label="My node") ) diff --git a/src/trame_flow/widgets/flow/__init__.py b/src/trame_flow/widgets/flow/__init__.py index 271212d..febd772 100644 --- a/src/trame_flow/widgets/flow/__init__.py +++ b/src/trame_flow/widgets/flow/__init__.py @@ -1,7 +1,19 @@ +from trame_flow.widgets.flow.background import Background +from trame_flow.widgets.flow.controls import Controls, ControlsButton +from trame_flow.widgets.flow.minimap import MiniMap, MiniMapNode from trame_flow.widgets.flow.node_editor import CustomNode, Handle, NodeEditor +from trame_flow.widgets.flow.node_resizer import NodeResizer +from trame_flow.widgets.flow.node_toolbar import NodeToolbar __all__ = [ + "Background", + "Controls", + "ControlsButton", "CustomNode", "Handle", + "MiniMap", + "MiniMapNode", "NodeEditor", + "NodeResizer", + "NodeToolbar", ] diff --git a/src/trame_flow/widgets/flow/background.py b/src/trame_flow/widgets/flow/background.py new file mode 100644 index 0000000..9a0a3c3 --- /dev/null +++ b/src/trame_flow/widgets/flow/background.py @@ -0,0 +1,27 @@ +from trame_flow.widgets.flow.common import HtmlElement + +__all__ = [ + "Background", +] + + +class Background(HtmlElement): + """Background for `NodeEditor`.""" + + def __init__( + self, + **kwargs, + ): + super().__init__("Background", **kwargs) + + self._attr_names += [ + "variant", + "gap", + "size", + ("pattern_color", "patternColor"), + ("bg_color", "bgColor"), + "height", + "width", + "x", + "y", + ] diff --git a/src/trame_flow/widgets/flow/common.py b/src/trame_flow/widgets/flow/common.py new file mode 100644 index 0000000..86a9654 --- /dev/null +++ b/src/trame_flow/widgets/flow/common.py @@ -0,0 +1,10 @@ +from trame_client.widgets.core import AbstractElement + +from trame_flow import module + + +class HtmlElement(AbstractElement): + def __init__(self, _elem_name, children=None, **kwargs): + super().__init__(_elem_name, children, **kwargs) + if self.server: + self.server.enable_module(module) diff --git a/src/trame_flow/widgets/flow/controls.py b/src/trame_flow/widgets/flow/controls.py new file mode 100644 index 0000000..1e1bdea --- /dev/null +++ b/src/trame_flow/widgets/flow/controls.py @@ -0,0 +1,40 @@ +from trame_flow.widgets.flow.common import HtmlElement + +__all__ = [ + "Controls", + "ControlsButton", +] + + +class Controls(HtmlElement): + """Controls for `NodeEditor`.""" + + def __init__( + self, + **kwargs, + ): + super().__init__("Controls", **kwargs) + + self._attr_names += [ + ("show_fit_view", "showFitView"), + ("show_interactive", "showInteractive"), + ("show_zoom", "showZoom"), + ("fit_view_params", "fitViewParams"), + ] + + self._event_names += [ + ("zoom_in", "zoom-in"), + ("zoom_out", "zoom-out"), + ("fit_view", "fit-view"), + ("interaction_change", "interaction-change"), + ] + + +class ControlsButton(HtmlElement): + """ControlsButton for `Controls`.""" + + def __init__( + self, + **kwargs, + ): + super().__init__("ControlsButton", **kwargs) diff --git a/src/trame_flow/widgets/flow/minimap.py b/src/trame_flow/widgets/flow/minimap.py new file mode 100644 index 0000000..91974ae --- /dev/null +++ b/src/trame_flow/widgets/flow/minimap.py @@ -0,0 +1,61 @@ +from trame_flow.module.core import Dimensions, Position +from trame_flow.widgets.flow.common import HtmlElement + +__all__ = [ + "MiniMap", + "MiniMapNode", +] + + +class MiniMap(HtmlElement): + """MiniMap for `NodeEditor`.""" + + def __init__( + self, + **kwargs, + ): + super().__init__("MiniMap", **kwargs) + + self._attr_names += [ + ("node_color", "nodeColor"), + ("node_stroke_color", "nodeStrokeColor"), + ("node_class_name", "nodeClassName"), + ("node_border_radius", "nodeBorderRadius"), + ("node_stroke_width", "nodeStrokeWidth"), + ("mask_color", "maskColor"), + "pannable", + "zoomable", + ] + + +class MiniMapNode(HtmlElement): + """MiniMapNode for `MiniMap`.""" + + def __init__( + self, + id: str, + position: Position, + dimensions: Dimensions, + **kwargs, + ): + super().__init__( + "MiniMapNode", + id=id, + position=position, + dimensions=dimensions, + **kwargs, + ) + + self._attr_names += [ + "id", + ("parent_node", "parentNode"), + "selected", + "dragging", + "position", + "dimensions", + ("border_radius", "borderRadius"), + "color", + ("shape_rendering", "shapeRendering"), + ("stroke_color", "strokeColor"), + ("stroke_width", "strokeWidth"), + ] diff --git a/src/trame_flow/widgets/flow/node_editor.py b/src/trame_flow/widgets/flow/node_editor.py index 269e7a1..b50216c 100644 --- a/src/trame_flow/widgets/flow/node_editor.py +++ b/src/trame_flow/widgets/flow/node_editor.py @@ -68,14 +68,6 @@ def __init__(self, **kwargs): **kwargs, ) - self._attr_names += [ - ("background_pattern_color", "backgroundPatternColor"), - ("background_pattern_gap", "backgroundPatternGap"), - ("background_pattern_size", "backgroundPatternSize"), - ("background_pattern_variant", "backgroundPatternVariant"), - ("show_controls", "showControls"), - ("show_mini_map", "showMiniMap"), - ] self._event_names += [ ("click_connect_end", "clickConnectEnd"), ("click_connect_start", "clickConnectStart"), diff --git a/src/trame_flow/widgets/flow/node_resizer.py b/src/trame_flow/widgets/flow/node_resizer.py new file mode 100644 index 0000000..f892179 --- /dev/null +++ b/src/trame_flow/widgets/flow/node_resizer.py @@ -0,0 +1,33 @@ +from trame_flow.widgets.flow.common import HtmlElement + +__all__ = [ + "NodeResizer", +] + + +class NodeResizer(HtmlElement): + """NodeResizer for `CustomNode`.""" + + def __init__( + self, + **kwargs, + ): + super().__init__("NodeResizer", **kwargs) + + self._attr_names += [ + ("node_id", "nodeId"), + "color", + ("handle_class_name", "handleClassName"), + ("handle_style", "handleStyle"), + ("line_class_name", "lineClassName"), + ("line_style", "lineStyle"), + ("is_visible", "isVisible"), + ("min_width", "minWidth"), + ("min_height", "minHeight"), + ] + + self._event_names += [ + ("resize_start", "resizeStart"), + "resize", + ("resize_end", "resizeEnd"), + ] diff --git a/src/trame_flow/widgets/flow/node_toolbar.py b/src/trame_flow/widgets/flow/node_toolbar.py new file mode 100644 index 0000000..5633e0b --- /dev/null +++ b/src/trame_flow/widgets/flow/node_toolbar.py @@ -0,0 +1,22 @@ +from trame_flow.widgets.flow.common import HtmlElement + +__all__ = [ + "NodeToolbar", +] + + +class NodeToolbar(HtmlElement): + """NodeToolbar for `NodeEditor`.""" + + def __init__( + self, + **kwargs, + ): + super().__init__("NodeToolbar", **kwargs) + + self._attr_names += [ + ("node_id", "nodeId"), + ("is_visible", "isVisible"), + "position", + "offset", + ] diff --git a/tests/test_import.py b/tests/test_import.py index 5f6c7e5..7cc51dd 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -1,3 +1,25 @@ def test_import(): - from trame.widgets.flow import NodeEditor - from trame_flow.widgets.flow import NodeEditor # noqa: F401,F811 + from trame.widgets.flow import ( + Background, + Controls, + ControlsButton, + CustomNode, + Handle, + MiniMap, + MiniMapNode, + NodeEditor, + NodeResizer, + NodeToolbar, + ) + from trame_flow.widgets.flow import ( # noqa: F401,F811 + Background, + Controls, + ControlsButton, + CustomNode, + Handle, + MiniMap, + MiniMapNode, + NodeEditor, + NodeResizer, + NodeToolbar, + ) diff --git a/vue-components/package-lock.json b/vue-components/package-lock.json index 9bfa10c..bd42aba 100644 --- a/vue-components/package-lock.json +++ b/vue-components/package-lock.json @@ -11,7 +11,9 @@ "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", "@vue-flow/core": "^1.47.0", - "@vue-flow/minimap": "^1.5.4" + "@vue-flow/minimap": "^1.5.4", + "@vue-flow/node-resizer": "^1.5.0", + "@vue-flow/node-toolbar": "^1.1.1" }, "devDependencies": { "@rushstack/eslint-patch": "^1.1.4", @@ -1038,6 +1040,30 @@ "vue": "^3.3.0" } }, + "node_modules/@vue-flow/node-resizer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue-flow/node-resizer/-/node-resizer-1.5.0.tgz", + "integrity": "sha512-FmvOZ6+yVrBEf+8oJcCU20PUZ105QsyM01iiP4vTKHGJ01hzoh9d0/wP9iJkxkIpvBU59CyOHyTKQZlDr4qDhA==", + "license": "MIT", + "dependencies": { + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0" + }, + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, + "node_modules/@vue-flow/node-toolbar": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vue-flow/node-toolbar/-/node-toolbar-1.1.1.tgz", + "integrity": "sha512-vgSnGMLd3FXstdpvC721qcBDzGkPsudmyA8urEP55/EMikCp59klgzPvVULTDxxsew5MdXtTBCumsqOi+PXJdg==", + "license": "MIT", + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", diff --git a/vue-components/package.json b/vue-components/package.json index 5c7ce44..4fb849b 100644 --- a/vue-components/package.json +++ b/vue-components/package.json @@ -21,7 +21,9 @@ "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", "@vue-flow/core": "^1.47.0", - "@vue-flow/minimap": "^1.5.4" + "@vue-flow/minimap": "^1.5.4", + "@vue-flow/node-resizer": "^1.5.0", + "@vue-flow/node-toolbar": "^1.1.1" }, "peerDependencies": { "vue": "^2.7.0 || >=3.0.0" diff --git a/vue-components/src/components/NodeEditor.js b/vue-components/src/components/NodeEditor.js index ec083d5..912dff7 100644 --- a/vue-components/src/components/NodeEditor.js +++ b/vue-components/src/components/NodeEditor.js @@ -1,41 +1,9 @@ import "./style.css"; import { VueFlow, useVueFlow } from "@vue-flow/core"; -import { Background } from "@vue-flow/background"; -import { MiniMap } from "@vue-flow/minimap"; -import { Controls } from "@vue-flow/controls"; export default { components: { VueFlow, - Background, - MiniMap, - Controls, - }, - props: { - backgroundPatternVariant: { - type: "dots" | "lines", - default: "dots", - }, - backgroundPatternColor: { - type: String, - default: "#81818a", - }, - backgroundPatternSize: { - type: Number, - default: 1, - }, - backgroundPatternGap: { - type: Number, - default: 10, - }, - showControls: { - type: Boolean, - default: true, - }, - showMiniMap: { - type: Boolean, - default: true, - }, }, setup() { return { @@ -44,15 +12,10 @@ export default { }, template: ` - - - - - `, }; diff --git a/vue-components/src/components/index.js b/vue-components/src/components/index.js index 29905be..f5bc3ec 100644 --- a/vue-components/src/components/index.js +++ b/vue-components/src/components/index.js @@ -1,7 +1,19 @@ import NodeEditor from "./NodeEditor"; import { Handle } from "@vue-flow/core"; +import { MiniMap, MiniMapNode } from "@vue-flow/minimap"; +import { Controls, ControlButton } from "@vue-flow/controls"; +import { Background } from "@vue-flow/background"; +import { NodeToolbar } from "@vue-flow/node-toolbar"; +import { NodeResizer } from "@vue-flow/node-resizer"; export default { NodeEditor, Handle, + MiniMap, + MiniMapNode, + Background, + Controls, + ControlButton, + NodeToolbar, + NodeResizer, }; diff --git a/vue-components/src/components/style.css b/vue-components/src/components/style.css index 61f25dd..b10b20b 100644 --- a/vue-components/src/components/style.css +++ b/vue-components/src/components/style.css @@ -6,3 +6,4 @@ @import "@vue-flow/minimap/dist/style.css"; @import "@vue-flow/controls/dist/style.css"; +@import "@vue-flow/node-resizer/dist/style.css";