feat(presets): pre-fill the user's project from preset values (A4)
Build backend images / build content-svc (push) Failing after 30s
Build backend images / build file-svc (push) Failing after 30s
Build backend images / build gateway (push) Failing after 31s
Build backend images / build identity-svc (push) Failing after 31s
Build backend images / build notification-svc (push) Failing after 30s
Build backend images / build render-svc (push) Failing after 30s
Build backend images / build studio-svc (push) Failing after 31s
Build backend images / build content-svc (push) Failing after 30s
Build backend images / build file-svc (push) Failing after 30s
Build backend images / build gateway (push) Failing after 31s
Build backend images / build identity-svc (push) Failing after 31s
Build backend images / build notification-svc (push) Failing after 30s
Build backend images / build render-svc (push) Failing after 30s
Build backend images / build studio-svc (push) Failing after 31s
"Use this example" now actually fills the new project, not just stores a ref.
- studio-svc: CreateProjectAsync applies the chosen preset story's saved values
after the template-graph copy. ApplyPresetValuesAsync reads
content.preset_stories.scenes_spa = { values: {contentKey:value},
colors: {elementKey:hex} } and overlays them onto studio.saved_scene_contents
(by key) + saved_shared_colors/saved_scene_colors (by element_key, is_selected).
Keys are globally unique (AE convention) so key-only matching is safe.
Malformed scenes_spa is skipped (defaults kept). Runs in the create tx.
- admin UI: ProjectPresetStories raw scenes_spa textarea replaced with a
structured PresetValueEditor — loads each preset scene's content elements +
the project's shared colours and renders a type-aware input per item
(text/textarea/number, media→upload, fill/color→colour). Serializes to
scenes_spa {values,colors}; parses it back on edit.
Verified e2e: authored a preset with values+colour → used it → the new
project's saved_scene_contents + saved_shared_colors carry the preset values
(which the B2 render binder then writes into AE).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using FlatRender.StudioSvc.Domain.Entities;
|
||||
using FlatRender.StudioSvc.Domain.Enums;
|
||||
using FlatRender.StudioSvc.Infrastructure.Data;
|
||||
@@ -91,10 +92,67 @@ public class StudioService(StudioDbContext db)
|
||||
if (req.CopyDefaultValues && req.OriginalProjectId != Guid.Empty)
|
||||
await CopyTemplateGraphAsync(project.Id, req.OriginalProjectId);
|
||||
|
||||
// Pre-fill from the chosen preset story (premade example video): overlay its
|
||||
// saved values onto the freshly-copied scene contents + colours.
|
||||
if (req.PresetStoryId.HasValue)
|
||||
await ApplyPresetValuesAsync(project.Id, req.PresetStoryId.Value);
|
||||
|
||||
await tx.CommitAsync();
|
||||
return await GetProjectAsync(project.Id, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies an admin-authored preset story's filled values onto a freshly-created
|
||||
/// project. The preset stores them in content.preset_stories.scenes_spa as
|
||||
/// <c>{ "values": { contentKey: value }, "colors": { elementKey: hex } }</c>.
|
||||
/// Content/colour keys are globally unique (AE naming convention), so matching by
|
||||
/// key alone reaches the right scene element. Runs inside the create transaction.
|
||||
/// </summary>
|
||||
private async Task ApplyPresetValuesAsync(Guid savedProjectId, Guid presetStoryId)
|
||||
{
|
||||
var spa = await db.Database
|
||||
.SqlQuery<string?>($@"SELECT scenes_spa AS ""Value"" FROM content.preset_stories
|
||||
WHERE id = {presetStoryId} AND deleted_at IS NULL")
|
||||
.FirstOrDefaultAsync();
|
||||
if (string.IsNullOrWhiteSpace(spa)) return;
|
||||
|
||||
JsonElement root;
|
||||
try { root = JsonDocument.Parse(spa).RootElement; }
|
||||
catch { return; } // malformed scenes_spa → skip pre-fill, leave defaults
|
||||
if (root.ValueKind != JsonValueKind.Object) return;
|
||||
|
||||
// content element values → saved_scene_contents.value (match by key)
|
||||
if (root.TryGetProperty("values", out var vals) && vals.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var valuesJson = vals.GetRawText();
|
||||
await db.Database.ExecuteSqlInterpolatedAsync($@"
|
||||
UPDATE studio.saved_scene_contents c
|
||||
SET value = pv.value, updated_at = now()
|
||||
FROM json_each_text({valuesJson}::json) AS pv(key, value)
|
||||
WHERE c.key = pv.key
|
||||
AND c.saved_scene_id IN (
|
||||
SELECT id FROM studio.saved_scenes WHERE saved_project_id = {savedProjectId});");
|
||||
}
|
||||
|
||||
// colour values → shared + per-scene colours (match by element_key)
|
||||
if (root.TryGetProperty("colors", out var cols) && cols.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var colorsJson = cols.GetRawText();
|
||||
await db.Database.ExecuteSqlInterpolatedAsync($@"
|
||||
UPDATE studio.saved_shared_colors sc
|
||||
SET value = pc.value, is_selected = true
|
||||
FROM json_each_text({colorsJson}::json) AS pc(key, value)
|
||||
WHERE sc.element_key = pc.key AND sc.saved_project_id = {savedProjectId};");
|
||||
await db.Database.ExecuteSqlInterpolatedAsync($@"
|
||||
UPDATE studio.saved_scene_colors c
|
||||
SET value = pc.value, is_selected = true
|
||||
FROM json_each_text({colorsJson}::json) AS pc(key, value)
|
||||
WHERE c.element_key = pc.key
|
||||
AND c.saved_scene_id IN (
|
||||
SELECT id FROM studio.saved_scenes WHERE saved_project_id = {savedProjectId});");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a content template project's scenes, content elements and colour elements
|
||||
/// into a freshly-created editable project. Runs inside the caller's transaction;
|
||||
|
||||
Reference in New Issue
Block a user