feat(studio+render): wire theme picker → saved_shared_colors → FlexStory render
Closes the theme→render gap: the studio theme picker now actually drives a
FlexStory render's colours. GetFlexStoryProps reads saved_shared_colors by
element_key (accentColor/secondaryColor/backgroundColor/textColor), but the studio
only wrote the theme into scene_data — so the picker never reached the MP4.
- studio-svc: UpdateSharedColorsAsync upserts saved_shared_colors by (project,
element_key) + PATCH /v1/saved-projects/{id}/shared-colors endpoint +
UpdateColorsRequest/UpdateColorItem. Mirrors UpdateSceneContentsAsync. (dotnet
build: 0 errors.)
- gateway already wildcard-routes /v1/saved-projects/*path → studio-svc (no change).
- Next: /api/projects/[id]/colors route → gateway; project-api patchProjectColors
+ themeColorsFromSceneData (maps scene_data sceneAccentColor… → the colorSchema
keys); performSave best-effort pushes the 4 colours alongside contents.
Chain: theme picker → store → scene_data → performSave → patchProjectColors →
gateway → studio-svc upsert → saved_shared_colors → GetFlexStoryProps → render.
Verified: Next build + dotnet build both clean; theme presets render cohesively
across all 6 (incl. dark Midnight). End-to-end studio→render needs the live stack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -321,6 +321,40 @@ public class StudioService(StudioDbContext db)
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the project's theme colours by element key (accentColor/secondaryColor/
|
||||
/// backgroundColor/textColor). Upserts saved_shared_colors so the FlexStory render
|
||||
/// binder (GetFlexStoryProps) reads the studio theme picker's colours.
|
||||
/// </summary>
|
||||
public async Task<int> UpdateSharedColorsAsync(Guid projectId, Guid userId, List<UpdateColorItem> items)
|
||||
{
|
||||
var owns = await db.SavedProjects.AnyAsync(x => x.Id == projectId && x.UserId == userId);
|
||||
if (!owns) throw new KeyNotFoundException($"Project {projectId} not found");
|
||||
|
||||
var updated = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.Key) || string.IsNullOrWhiteSpace(item.Value)) continue;
|
||||
// Upsert by (project, element_key): the row usually exists (copied from the
|
||||
// template's shared colours); insert if a FlexStory key is missing.
|
||||
var n = await db.Database.ExecuteSqlInterpolatedAsync($@"
|
||||
UPDATE studio.saved_shared_colors
|
||||
SET value = {item.Value}
|
||||
WHERE saved_project_id = {projectId} AND element_key = {item.Key}");
|
||||
if (n == 0)
|
||||
{
|
||||
n = await db.Database.ExecuteSqlInterpolatedAsync($@"
|
||||
INSERT INTO studio.saved_shared_colors
|
||||
(saved_project_id, element_key, attr_value, value, is_selected, sort)
|
||||
VALUES ({projectId}, {item.Key}, 'fill', {item.Value}, true, 0)");
|
||||
}
|
||||
updated += n;
|
||||
}
|
||||
await db.Database.ExecuteSqlInterpolatedAsync(
|
||||
$"UPDATE studio.saved_projects SET last_edit_date = now(), updated_at = now() WHERE id = {projectId}");
|
||||
return updated;
|
||||
}
|
||||
|
||||
public async Task DeleteProjectAsync(Guid id, Guid userId)
|
||||
{
|
||||
var project = await db.SavedProjects
|
||||
|
||||
@@ -60,6 +60,12 @@ public class StudioController(StudioService svc) : ControllerBase
|
||||
public async Task<IActionResult> UpdateContents(Guid id, [FromBody] UpdateContentsRequest req) =>
|
||||
Ok(new { updated = await svc.UpdateSceneContentsAsync(id, UserId, req.Items ?? new List<UpdateContentItem>()) });
|
||||
|
||||
/// <summary>Update the project's theme colours (accentColor/secondaryColor/
|
||||
/// backgroundColor/textColor) so the FlexStory render reads the theme picker.</summary>
|
||||
[HttpPatch("{id:guid}/shared-colors")]
|
||||
public async Task<IActionResult> UpdateColors(Guid id, [FromBody] UpdateColorsRequest req) =>
|
||||
Ok(new { updated = await svc.UpdateSharedColorsAsync(id, UserId, req.Items ?? new List<UpdateColorItem>()) });
|
||||
|
||||
/// <summary>Internal endpoint: get project for render service (no user-ownership check).</summary>
|
||||
[HttpGet("{id:guid}/render-payload")]
|
||||
[Authorize(Roles = "Service")]
|
||||
|
||||
@@ -128,3 +128,9 @@ public record SavedProjectListRequest(
|
||||
/// writes the user's edits here (by content key) so the render binder picks them up.</summary>
|
||||
public record UpdateContentsRequest(List<UpdateContentItem> Items);
|
||||
public record UpdateContentItem(string Key, string? Value, Guid? ValueFileId);
|
||||
|
||||
/// <summary>Update the project-wide theme colours (the studio theme picker) by
|
||||
/// element key (accentColor/secondaryColor/backgroundColor/textColor) so the
|
||||
/// FlexStory render binder reads them from saved_shared_colors.</summary>
|
||||
public record UpdateColorsRequest(List<UpdateColorItem> Items);
|
||||
public record UpdateColorItem(string Key, string Value);
|
||||
|
||||
Reference in New Issue
Block a user