feat(admin): manually edit scene inputs (content elements)
Scene content elements (the editable Text/Media/Color/… inputs inside a scene) had no CRUD — only AEP-import created them, so admins couldn't define or edit them. Added full management: content-svc: - SceneElementsController: GET/POST/PUT/DELETE /v1/scene-elements?scene_id= - SceneColorService: Get/Create/Update/DeleteContentElementAsync - ContentElementResponse + SaveContentElementRequest (key, title, type, default_value, hint, position, text-box/font/media flags) gateway: route /v1/scene-elements/*path → content frontend: SceneColorEditor scenes tab → per-scene "ورودیها" expander with full add/edit/delete of inputs (15 element types: Text/Media/Color/Number/…) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,63 @@ public class SceneColorService(ContentDbContext db)
|
||||
p.Items.OrderBy(i => i.Sort).Select(i => new ColorPresetItemResponse(i.Id, i.ElementKey, i.Value, i.Sort)).ToList()))
|
||||
.FirstAsync();
|
||||
|
||||
// ── Scene content elements (editable inputs) ────────────────────────────
|
||||
|
||||
public async Task<List<ContentElementResponse>> GetContentElementsAsync(Guid sceneId) =>
|
||||
await db.SceneContentElements.Where(e => e.SceneId == sceneId)
|
||||
.OrderBy(e => e.PositionInContainer)
|
||||
.Select(e => ToElementResponse(e)).ToListAsync();
|
||||
|
||||
public async Task<ContentElementResponse> CreateContentElementAsync(SaveContentElementRequest req)
|
||||
{
|
||||
var e = new SceneContentElement { SceneId = req.SceneId };
|
||||
ApplyElement(e, req);
|
||||
db.SceneContentElements.Add(e);
|
||||
await db.SaveChangesAsync();
|
||||
return ToElementResponse(e);
|
||||
}
|
||||
|
||||
public async Task<ContentElementResponse> UpdateContentElementAsync(Guid id, SaveContentElementRequest req)
|
||||
{
|
||||
var e = await db.SceneContentElements.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Element {id} not found");
|
||||
ApplyElement(e, req);
|
||||
await db.SaveChangesAsync();
|
||||
return ToElementResponse(e);
|
||||
}
|
||||
|
||||
public async Task DeleteContentElementAsync(Guid id)
|
||||
{
|
||||
var e = await db.SceneContentElements.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Element {id} not found");
|
||||
db.SceneContentElements.Remove(e);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static void ApplyElement(SceneContentElement e, SaveContentElementRequest req)
|
||||
{
|
||||
e.Key = req.Key;
|
||||
e.Title = req.Title;
|
||||
e.Hint = req.Hint;
|
||||
e.Type = Enum.TryParse<ContentElementType>(req.Type, true, out var t) ? t : ContentElementType.Text;
|
||||
e.DefaultValue = req.DefaultValue;
|
||||
e.PositionInContainer = req.PositionInContainer;
|
||||
e.IsTextBox = req.IsTextBox;
|
||||
e.MaxSize = req.MaxSize;
|
||||
e.FontSize = req.FontSize;
|
||||
e.IsFontChangeable = req.IsFontChangeable;
|
||||
e.IsFontSizeChangeable = req.IsFontSizeChangeable;
|
||||
e.VideoSupport = req.VideoSupport;
|
||||
e.Width = req.Width;
|
||||
e.Height = req.Height;
|
||||
e.Thumbnail = req.Thumbnail;
|
||||
}
|
||||
|
||||
private static ContentElementResponse ToElementResponse(SceneContentElement e) => new(
|
||||
e.Id, e.SceneId, e.Key, e.Title, e.Hint, e.Type.ToString(), e.DefaultValue,
|
||||
e.PositionInContainer, e.IsTextBox, e.MaxSize, e.FontSize, e.IsFontChangeable,
|
||||
e.IsFontSizeChangeable, e.VideoSupport, e.Width, e.Height, e.Thumbnail);
|
||||
|
||||
// ── helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
private static SceneResponse ToSceneResponse(Scene s) => new(
|
||||
|
||||
@@ -32,6 +32,33 @@ public class ScenesController(SceneColorService svc) : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/scene-elements")]
|
||||
public class SceneElementsController(SceneColorService svc) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List([FromQuery(Name = "scene_id")] Guid sceneId) =>
|
||||
Ok(await svc.GetContentElementsAsync(sceneId));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] SaveContentElementRequest req) =>
|
||||
Ok(await svc.CreateContentElementAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, [FromBody] SaveContentElementRequest req) =>
|
||||
Ok(await svc.UpdateContentElementAsync(id, req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
await svc.DeleteContentElementAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/shared-colors")]
|
||||
public class SharedColorsController(SceneColorService svc) : ControllerBase
|
||||
|
||||
@@ -48,3 +48,21 @@ public record ColorPresetItemInput(string ElementKey, string Value, int Sort);
|
||||
public record SaveColorPresetRequest(
|
||||
Guid ProjectId, string? Name, int Sort, List<ColorPresetItemInput> Items
|
||||
);
|
||||
|
||||
// ── Scene content elements (the editable inputs inside a scene) ───────────────
|
||||
|
||||
public record ContentElementResponse(
|
||||
Guid Id, Guid SceneId, string Key, string Title, string? Hint,
|
||||
string Type, string? DefaultValue, int PositionInContainer,
|
||||
bool IsTextBox, int? MaxSize, int? FontSize, bool IsFontChangeable,
|
||||
bool IsFontSizeChangeable, bool VideoSupport, int? Width, int? Height,
|
||||
string? Thumbnail
|
||||
);
|
||||
|
||||
public record SaveContentElementRequest(
|
||||
Guid SceneId, string Key, string Title, string? Hint,
|
||||
string Type, string? DefaultValue, int PositionInContainer,
|
||||
bool IsTextBox, int? MaxSize, int? FontSize,
|
||||
bool IsFontChangeable, bool IsFontSizeChangeable,
|
||||
bool VideoSupport, int? Width, int? Height, string? Thumbnail
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user