feat: selectable marker shapes per edge endpoint

Replace the on/off arrowhead toggle with a per-end shape picker. Each end
(start/end) independently selects: none, arrow, arrow-open, circle, diamond,
or square. Markers still recolor live from the resolved stroke color.

Frontend:
- MarkerShape type + edgeMarkers util (normalizeMarker, MARKER_GEOMETRY);
  legacy boolean coerces to 'arrow' on read.
- Per-shape <marker> inner geometry; symmetric shapes use fixed orient.
- MarkerShapePicker reused in EdgeModal and CustomStyleModal.
- Serializer normalizes to shape strings.

Backend:
- Edge marker columns Boolean -> String (default 'none'); TEXT migration.
- normalize_marker() + validators coerce legacy bool / unknown values.

ha-relevant: yes
This commit is contained in:
Pouzor
2026-07-05 11:30:49 +02:00
parent 1cf525844b
commit c95d104245
20 changed files with 385 additions and 157 deletions
+8 -3
View File
@@ -4,7 +4,7 @@ from pydantic import BaseModel, field_validator
from app.schemas.edges import EdgeResponse
from app.schemas.nodes import NodeResponse
from app.schemas.utils import normalize_animated
from app.schemas.utils import normalize_animated, normalize_marker
class NodeSave(BaseModel):
@@ -52,8 +52,8 @@ class EdgeSave(BaseModel):
custom_color: str | None = None
path_style: str | None = None
animated: str = 'none'
marker_start: bool = False
marker_end: bool = False
marker_start: str = 'none'
marker_end: str = 'none'
source_handle: str | None = None
target_handle: str | None = None
waypoints: list[dict[str, float]] | None = None
@@ -63,6 +63,11 @@ class EdgeSave(BaseModel):
def validate_animated(cls, v: object) -> str:
return normalize_animated(v)
@field_validator('marker_start', 'marker_end', mode='before')
@classmethod
def validate_marker(cls, v: object) -> str:
return normalize_marker(v)
class CanvasSaveRequest(BaseModel):
nodes: list[NodeSave] = []