CSS: add GradientColorStripSource with visual editor
- Backend: GradientColorStripSource storage model, GradientColorStripStream with numpy interpolation (bidirectional stops, auto-size from device LED count), ColorStop Pydantic schema, API create/update/guard routes - Frontend: gradient editor modal (canvas preview, draggable markers, stop rows), CSS hard-edge card swatch, locale keys (en + ru) - Fixes: stop row mousedown no longer rebuilds DOM (buttons now clickable), position input max-width, bidir/remove button static width Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,11 +8,22 @@ from pydantic import BaseModel, Field
|
||||
from wled_controller.api.schemas.devices import Calibration
|
||||
|
||||
|
||||
class ColorStop(BaseModel):
|
||||
"""A single color stop in a gradient."""
|
||||
|
||||
position: float = Field(description="Relative position along the strip (0.0–1.0)", ge=0.0, le=1.0)
|
||||
color: List[int] = Field(description="Primary RGB color [R, G, B] (0–255 each)")
|
||||
color_right: Optional[List[int]] = Field(
|
||||
None,
|
||||
description="Optional right-side RGB color for a hard edge (bidirectional stop)",
|
||||
)
|
||||
|
||||
|
||||
class ColorStripSourceCreate(BaseModel):
|
||||
"""Request to create a color strip source."""
|
||||
|
||||
name: str = Field(description="Source name", min_length=1, max_length=100)
|
||||
source_type: Literal["picture", "static"] = Field(default="picture", description="Source type")
|
||||
source_type: Literal["picture", "static", "gradient"] = Field(default="picture", description="Source type")
|
||||
# picture-type fields
|
||||
picture_source_id: str = Field(default="", description="Picture source ID (for picture type)")
|
||||
fps: int = Field(default=30, description="Target frames per second", ge=10, le=90)
|
||||
@@ -24,6 +35,8 @@ class ColorStripSourceCreate(BaseModel):
|
||||
calibration: Optional[Calibration] = Field(None, description="LED calibration (position and count per edge)")
|
||||
# static-type fields
|
||||
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
|
||||
# gradient-type fields
|
||||
stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
|
||||
# shared
|
||||
led_count: int = Field(default=0, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
@@ -44,6 +57,8 @@ class ColorStripSourceUpdate(BaseModel):
|
||||
calibration: Optional[Calibration] = Field(None, description="LED calibration")
|
||||
# static-type fields
|
||||
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
|
||||
# gradient-type fields
|
||||
stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
|
||||
# shared
|
||||
led_count: Optional[int] = Field(None, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
@@ -66,6 +81,8 @@ class ColorStripSourceResponse(BaseModel):
|
||||
calibration: Optional[Calibration] = Field(None, description="LED calibration")
|
||||
# static-type fields
|
||||
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B]")
|
||||
# gradient-type fields
|
||||
stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
|
||||
# shared
|
||||
led_count: int = Field(0, description="Total LED count (0 = auto from calibration / device)")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
|
||||
Reference in New Issue
Block a user