JSON Canvas MCP Server

by Cam10001110101
Verified
  • jsoncanvas
"""Node implementations for JSON Canvas.""" from abc import ABC, abstractmethod from typing import Dict, Optional, Union, Literal from .errors import InvalidNodeError class Node(ABC): """Abstract base class for all node types.""" def __init__( self, id: str, x: int, y: int, width: int, height: int, color: Optional[str] = None ) -> None: """Initialize a node. Args: id: Unique identifier for the node x: X position of the node in pixels y: Y position of the node in pixels width: Width of the node in pixels height: Height of the node in pixels color: Optional color of the node (hex format or preset number) """ self.id = id self.x = x self.y = y self.width = width self.height = height self.validate_color(color) self.color = color @property @abstractmethod def type(self) -> str: """Get the type of the node.""" pass def to_dict(self) -> Dict: """Convert node to dictionary representation. Returns: Dictionary representation of the node """ node_dict = { "id": self.id, "type": self.type, "x": self.x, "y": self.y, "width": self.width, "height": self.height, } if self.color is not None: node_dict["color"] = self.color return node_dict @classmethod def validate_color(cls, color: Optional[str]) -> None: """Validate a color value. Args: color: Color value to validate (hex format or preset number) Raises: InvalidNodeError: If the color is invalid """ if color is not None: if not ( (color.startswith("#") and len(color) == 7) or color in ["1", "2", "3", "4", "5", "6"] ): raise InvalidNodeError( "Color must be a hex code (#RRGGBB) or preset number (1-6)" ) class TextNode(Node): """Text type node implementation.""" def __init__( self, id: str, x: int, y: int, width: int, height: int, text: str, color: Optional[str] = None ) -> None: """Initialize a text node. Args: id: Unique identifier for the node x: X position of the node in pixels y: Y position of the node in pixels width: Width of the node in pixels height: Height of the node in pixels text: Text content with Markdown syntax color: Optional color of the node """ super().__init__(id, x, y, width, height, color) self.text = text @property def type(self) -> str: """Get the type of the node.""" return "text" def to_dict(self) -> Dict: """Convert text node to dictionary representation.""" node_dict = super().to_dict() node_dict["text"] = self.text return node_dict class FileNode(Node): """File type node implementation.""" def __init__( self, id: str, x: int, y: int, width: int, height: int, file: str, subpath: Optional[str] = None, color: Optional[str] = None ) -> None: """Initialize a file node. Args: id: Unique identifier for the node x: X position of the node in pixels y: Y position of the node in pixels width: Width of the node in pixels height: Height of the node in pixels file: Path to the file within the system subpath: Optional subpath that may link to a heading or block color: Optional color of the node """ super().__init__(id, x, y, width, height, color) self.file = file if subpath is not None and not subpath.startswith("#"): raise InvalidNodeError("Subpath must start with '#'") self.subpath = subpath @property def type(self) -> str: """Get the type of the node.""" return "file" def to_dict(self) -> Dict: """Convert file node to dictionary representation.""" node_dict = super().to_dict() node_dict["file"] = self.file if self.subpath is not None: node_dict["subpath"] = self.subpath return node_dict class LinkNode(Node): """Link type node implementation.""" def __init__( self, id: str, x: int, y: int, width: int, height: int, url: str, color: Optional[str] = None ) -> None: """Initialize a link node. Args: id: Unique identifier for the node x: X position of the node in pixels y: Y position of the node in pixels width: Width of the node in pixels height: Height of the node in pixels url: URL to link to color: Optional color of the node """ super().__init__(id, x, y, width, height, color) self.url = url @property def type(self) -> str: """Get the type of the node.""" return "link" def to_dict(self) -> Dict: """Convert link node to dictionary representation.""" node_dict = super().to_dict() node_dict["url"] = self.url return node_dict class GroupNode(Node): """Group type node implementation.""" def __init__( self, id: str, x: int, y: int, width: int, height: int, label: Optional[str] = None, background: Optional[str] = None, background_style: Optional[Literal["cover", "ratio", "repeat"]] = None, color: Optional[str] = None ) -> None: """Initialize a group node. Args: id: Unique identifier for the node x: X position of the node in pixels y: Y position of the node in pixels width: Width of the node in pixels height: Height of the node in pixels label: Optional text label for the group background: Optional path to the background image background_style: Optional rendering style of the background image color: Optional color of the node """ super().__init__(id, x, y, width, height, color) self.label = label self.background = background if background_style not in [None, "cover", "ratio", "repeat"]: raise InvalidNodeError( "Background style must be one of: cover, ratio, repeat" ) self.background_style = background_style @property def type(self) -> str: """Get the type of the node.""" return "group" def to_dict(self) -> Dict: """Convert group node to dictionary representation.""" node_dict = super().to_dict() if self.label is not None: node_dict["label"] = self.label if self.background is not None: node_dict["background"] = self.background if self.background_style is not None: node_dict["backgroundStyle"] = self.background_style return node_dict