6ee211fb35
Build backend images / build content-svc (push) Failing after 1m46s
Build backend images / build file-svc (push) Failing after 2m32s
Build backend images / build gateway (push) Failing after 1m18s
Build backend images / build identity-svc (push) Failing after 1m2s
Build backend images / build notification-svc (push) Failing after 2m59s
Build backend images / build render-svc (push) Failing after 6m12s
Build backend images / build studio-svc (push) Failing after 4m14s
Extends CopyTemplateGraphAsync: repeater children flatten into saved_scene_contents (repeater_item_key/index via repeater_items); scene characters+controllers and color presets+items copied, correlated by (new scene, original-id/sort) since studio tables lack original-id columns. studio character.key is a uuid → store original char id. No regression on templates without these (copy 0 rows). All enum cols cast ::text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
407 lines
21 KiB
C#
407 lines
21 KiB
C#
using FlatRender.StudioSvc.Domain.Entities;
|
|
using FlatRender.StudioSvc.Domain.Enums;
|
|
using FlatRender.StudioSvc.Infrastructure.Data;
|
|
using FlatRender.StudioSvc.Models.Requests;
|
|
using FlatRender.StudioSvc.Models.Responses;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FlatRender.StudioSvc.Application.Services;
|
|
|
|
public class StudioService(StudioDbContext db)
|
|
{
|
|
public async Task<PagedResponse<SavedProjectSummaryResponse>> ListProjectsAsync(
|
|
Guid userId, SavedProjectListRequest req)
|
|
{
|
|
var q = db.SavedProjects.Where(x => x.UserId == userId);
|
|
|
|
if (!string.IsNullOrWhiteSpace(req.Q))
|
|
q = q.Where(x => EF.Functions.ILike(x.Name, $"%{req.Q}%"));
|
|
|
|
if (!string.IsNullOrWhiteSpace(req.Type) &&
|
|
Enum.TryParse<SavedProjectType>(req.Type, true, out var t))
|
|
q = q.Where(x => x.Type == t);
|
|
|
|
var total = await q.LongCountAsync();
|
|
var items = await q
|
|
.OrderByDescending(x => x.LastEditDate)
|
|
.Skip((req.Page - 1) * req.PageSize)
|
|
.Take(req.PageSize)
|
|
.ToListAsync();
|
|
|
|
return new PagedResponse<SavedProjectSummaryResponse>(
|
|
items.Select(MapSummary),
|
|
new PaginationMeta(req.Page, req.PageSize, total,
|
|
(int)Math.Ceiling((double)total / req.PageSize)));
|
|
}
|
|
|
|
public async Task<SavedProjectFullResponse> GetProjectAsync(Guid id, Guid userId)
|
|
{
|
|
var p = await LoadProjectQuery()
|
|
.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId)
|
|
?? throw new KeyNotFoundException($"Project {id} not found");
|
|
|
|
return MapFull(p);
|
|
}
|
|
|
|
/// <summary>Internal: load project without user-ownership check (for service-to-service calls).</summary>
|
|
public async Task<SavedProjectFullResponse> GetProjectForRenderAsync(Guid id)
|
|
{
|
|
var p = await LoadProjectQuery()
|
|
.FirstOrDefaultAsync(x => x.Id == id)
|
|
?? throw new KeyNotFoundException($"Project {id} not found");
|
|
|
|
return MapFull(p);
|
|
}
|
|
|
|
private IQueryable<Domain.Entities.SavedProject> LoadProjectQuery() =>
|
|
db.SavedProjects
|
|
.Include(x => x.Scenes.OrderBy(s => s.Sort))
|
|
.ThenInclude(s => s.Contents.OrderBy(c => c.Sort))
|
|
.Include(x => x.Scenes).ThenInclude(s => s.Colors.OrderBy(c => c.Sort))
|
|
.Include(x => x.Scenes).ThenInclude(s => s.ColorPresets.OrderBy(c => c.Sort))
|
|
.ThenInclude(cp => cp.Items.OrderBy(i => i.Sort))
|
|
.Include(x => x.Scenes).ThenInclude(s => s.Characters)
|
|
.ThenInclude(ch => ch.Controllers.OrderBy(c => c.Sort))
|
|
.Include(x => x.SharedColors.OrderBy(c => c.Sort))
|
|
.Include(x => x.SharedColorPresets.OrderBy(c => c.Sort))
|
|
.ThenInclude(cp => cp.Items.OrderBy(i => i.Sort))
|
|
.Include(x => x.SharedLayers.OrderBy(l => l.Sort));
|
|
|
|
public async Task<SavedProjectFullResponse> CreateProjectAsync(
|
|
Guid userId, Guid tenantId, CreateSavedProjectRequest req)
|
|
{
|
|
var project = new SavedProject
|
|
{
|
|
TenantId = tenantId,
|
|
UserId = userId,
|
|
OriginalProjectId = req.OriginalProjectId,
|
|
OriginalProjectName = req.Name ?? $"Project {DateTime.UtcNow:yyyy-MM-dd}",
|
|
Name = req.Name ?? $"Project {DateTime.UtcNow:yyyy-MM-dd}",
|
|
SelectedPresetStoryId = req.PresetStoryId,
|
|
ProjectDurationSec = 0,
|
|
};
|
|
|
|
await using var tx = await db.Database.BeginTransactionAsync();
|
|
db.SavedProjects.Add(project);
|
|
await db.SaveChangesAsync();
|
|
|
|
// Deep-copy the template's scene graph (scenes + content/colour elements) from
|
|
// the content schema so the new project opens editable in the studio. Same DB,
|
|
// so this is one atomic cross-schema copy.
|
|
if (req.CopyDefaultValues && req.OriginalProjectId != Guid.Empty)
|
|
await CopyTemplateGraphAsync(project.Id, req.OriginalProjectId);
|
|
|
|
await tx.CommitAsync();
|
|
return await GetProjectAsync(project.Id, userId);
|
|
}
|
|
|
|
/// <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;
|
|
/// the temp scene-id map drops on commit. Same Postgres DB → atomic cross-schema copy.
|
|
/// </summary>
|
|
private async Task CopyTemplateGraphAsync(Guid savedProjectId, Guid originalProjectId)
|
|
{
|
|
// 1. scenes → capture old→new id mapping in a temp table
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
CREATE TEMP TABLE _scene_map ON COMMIT DROP AS
|
|
WITH ins AS (
|
|
INSERT INTO studio.saved_scenes
|
|
(saved_project_id, original_scene_id, key, title, image, demo,
|
|
scene_color_svg, scene_type, sort, scene_length_sec, min_duration_sec,
|
|
max_duration_sec, overlap_at_end_sec, can_handle_duration,
|
|
manual_color_selection, created_at, updated_at)
|
|
SELECT {0}, s.id, s.key, s.title, s.image, s.demo, s.scene_color_svg,
|
|
s.scene_type::text, s.sort, COALESCE(s.default_duration_sec, 0),
|
|
s.min_duration_sec, s.max_duration_sec, s.overlap_at_end_sec,
|
|
s.can_handle_duration, s.manual_color_selection, now(), now()
|
|
FROM content.scenes s
|
|
WHERE s.project_id = {1} AND s.deleted_at IS NULL AND s.is_active = true
|
|
RETURNING id AS new_id, original_scene_id AS old_id
|
|
)
|
|
SELECT new_id, old_id FROM ins;",
|
|
savedProjectId, originalProjectId);
|
|
|
|
// 2. content elements — incl. repeater children flattened via repeater_item_key
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
INSERT INTO studio.saved_scene_contents
|
|
(saved_scene_id, key, title, localized_title, hint, type, value,
|
|
font_face, font_face_name, font_size, default_font_size, default_font_face,
|
|
justify, position_in_container, direction_layer_value, is_text_box,
|
|
ai_input_type, mapped_list, thumbnail, sort, repeater_item_key,
|
|
repeater_index, status, created_at, updated_at)
|
|
SELECT sm.new_id, ce.key, ce.title, ce.localized_title, ce.hint, ce.type::text,
|
|
ce.default_value, ce.font_face, ce.font_face_name, ce.font_size,
|
|
ce.default_font_size, ce.default_font_face, ce.justify::text,
|
|
ce.position_in_container, ce.direction_layer_value, ce.is_text_box,
|
|
ce.ai_input_type::text, ce.mapped_list, ce.thumbnail, ce.sort,
|
|
ri.repeat_item_key, ri.sort, 'default', now(), now()
|
|
FROM content.scene_content_elements ce
|
|
JOIN _scene_map sm ON sm.old_id = ce.scene_id
|
|
LEFT JOIN content.repeater_items ri ON ri.id = ce.repeater_item_id;");
|
|
|
|
// 3. colour elements
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
INSERT INTO studio.saved_scene_colors
|
|
(saved_scene_id, element_key, title, icon, attr_value, value, is_selected, sort)
|
|
SELECT sm.new_id, ce.element_key, ce.title, ce.icon, ce.attr_value::text,
|
|
COALESCE(ce.default_color, ''), false, ce.sort
|
|
FROM content.scene_color_elements ce
|
|
JOIN _scene_map sm ON sm.old_id = ce.scene_id;");
|
|
|
|
// 3b. characters — studio's `key` is a uuid; store the original character id
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
INSERT INTO studio.saved_scene_characters (saved_scene_id, key, name, icon)
|
|
SELECT sm.new_id, ch.id, ch.name, ch.icon
|
|
FROM content.scene_characters ch
|
|
JOIN _scene_map sm ON sm.old_id = ch.scene_id;");
|
|
|
|
// 3c. character controllers (correlate the new character by scene + original id)
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
INSERT INTO studio.saved_scene_character_controllers
|
|
(saved_scene_character_id, name, key, value, sort)
|
|
SELECT sc.id, cc.name, cc.key, cc.default_value, cc.sort
|
|
FROM content.scene_character_controllers cc
|
|
JOIN content.scene_characters cch ON cch.id = cc.scene_character_id
|
|
JOIN _scene_map sm ON sm.old_id = cch.scene_id
|
|
JOIN studio.saved_scene_characters sc
|
|
ON sc.saved_scene_id = sm.new_id AND sc.key = cch.id;");
|
|
|
|
// 3d. colour presets
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
INSERT INTO studio.saved_scene_color_presets (saved_scene_id, is_selected, sort)
|
|
SELECT sm.new_id, false, cp.sort
|
|
FROM content.scene_color_presets cp
|
|
JOIN _scene_map sm ON sm.old_id = cp.scene_id;");
|
|
|
|
// 3e. colour preset items (correlate the new preset by scene + sort)
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
INSERT INTO studio.saved_scene_color_preset_items (preset_id, element_key, value, sort)
|
|
SELECT sp.id, ci.element_key, ci.value, ci.sort
|
|
FROM content.scene_color_preset_items ci
|
|
JOIN content.scene_color_presets cp ON cp.id = ci.preset_id
|
|
JOIN _scene_map sm ON sm.old_id = cp.scene_id
|
|
JOIN studio.saved_scene_color_presets sp
|
|
ON sp.saved_scene_id = sm.new_id AND sp.sort = cp.sort;");
|
|
|
|
// 4. shared (project-level) colours — frshare frd_* controls
|
|
await db.Database.ExecuteSqlRawAsync(@"
|
|
INSERT INTO studio.saved_shared_colors
|
|
(saved_project_id, element_key, title, icon, attr_value, value, is_selected, sort)
|
|
SELECT {0}, sc.element_key, sc.title, sc.icon, sc.attr_value::text,
|
|
COALESCE(sc.default_color, ''), false, sc.sort
|
|
FROM content.shared_colors sc
|
|
WHERE sc.project_id = {1};",
|
|
savedProjectId, originalProjectId);
|
|
}
|
|
|
|
public async Task<SavedProjectFullResponse> UpdateProjectAsync(
|
|
Guid id, Guid userId, UpdateSavedProjectRequest req)
|
|
{
|
|
var project = await db.SavedProjects
|
|
.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId)
|
|
?? throw new KeyNotFoundException($"Project {id} not found");
|
|
|
|
if (req.Name != null) project.Name = req.Name;
|
|
if (req.Image != null) project.Image = req.Image;
|
|
if (req.Type != null && Enum.TryParse<SavedProjectType>(req.Type, true, out var t))
|
|
project.Type = t;
|
|
if (req.MusicFileId.HasValue) project.MusicFileId = req.MusicFileId;
|
|
if (req.MusicTrackId.HasValue) project.MusicTrackId = req.MusicTrackId;
|
|
if (req.MusicVolume.HasValue) project.MusicVolume = req.MusicVolume.Value;
|
|
if (req.VoiceoverFileId.HasValue) project.VoiceoverFileId = req.VoiceoverFileId;
|
|
if (req.VoiceoverVolume.HasValue) project.VoiceoverVolume = req.VoiceoverVolume.Value;
|
|
if (req.VoiceoverRecordedInBrowser.HasValue)
|
|
project.VoiceoverRecordedInBrowser = req.VoiceoverRecordedInBrowser.Value;
|
|
if (req.SfxVolume.HasValue) project.SfxVolume = req.SfxVolume.Value;
|
|
if (req.SfxEnabled.HasValue) project.SfxEnabled = req.SfxEnabled.Value;
|
|
if (req.AudioVisualizerMusicUrl != null) project.AudioVisualizerMusicUrl = req.AudioVisualizerMusicUrl;
|
|
if (req.AudioVisualizerDurationSec.HasValue)
|
|
project.AudioVisualizerDurationSec = req.AudioVisualizerDurationSec;
|
|
if (req.ManualColorPicker.HasValue) project.ManualColorPicker = req.ManualColorPicker.Value;
|
|
if (req.SelectedPresetStoryId.HasValue) project.SelectedPresetStoryId = req.SelectedPresetStoryId;
|
|
if (req.LastEditStep != null) project.LastEditStep = req.LastEditStep;
|
|
if (req.EditState != null) project.EditState = req.EditState;
|
|
|
|
project.LastEditDate = DateTime.UtcNow;
|
|
project.UpdatedAt = DateTime.UtcNow;
|
|
await db.SaveChangesAsync();
|
|
|
|
return await GetProjectAsync(id, userId);
|
|
}
|
|
|
|
public async Task DeleteProjectAsync(Guid id, Guid userId)
|
|
{
|
|
var project = await db.SavedProjects
|
|
.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId)
|
|
?? throw new KeyNotFoundException($"Project {id} not found");
|
|
project.DeletedAt = DateTime.UtcNow;
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Full scene save — replaces all scene data atomically.
|
|
/// Called by the studio editor on every save checkpoint.
|
|
/// </summary>
|
|
public async Task<SavedProjectFullResponse> SaveScenesAsync(
|
|
Guid projectId, Guid userId, List<SaveSceneRequest> scenes)
|
|
{
|
|
var project = await db.SavedProjects
|
|
.FirstOrDefaultAsync(x => x.Id == projectId && x.UserId == userId)
|
|
?? throw new KeyNotFoundException($"Project {projectId} not found");
|
|
|
|
// Delete existing scenes (cascade deletes children)
|
|
var existing = await db.SavedScenes
|
|
.Where(x => x.SavedProjectId == projectId).ToListAsync();
|
|
db.SavedScenes.RemoveRange(existing);
|
|
|
|
foreach (var (sceneReq, idx) in scenes.Select((s, i) => (s, i)))
|
|
{
|
|
var scene = new SavedScene
|
|
{
|
|
SavedProjectId = projectId,
|
|
OriginalSceneId = sceneReq.OriginalSceneId,
|
|
Key = sceneReq.Key,
|
|
Title = sceneReq.Title,
|
|
Image = sceneReq.Image,
|
|
SceneColorSvg = sceneReq.SceneColorSvg,
|
|
SceneType = sceneReq.SceneType,
|
|
Sort = sceneReq.Sort,
|
|
SceneLengthSec = sceneReq.SceneLengthSec,
|
|
MinDurationSec = sceneReq.MinDurationSec,
|
|
MaxDurationSec = sceneReq.MaxDurationSec,
|
|
OverlapAtEndSec = sceneReq.OverlapAtEndSec,
|
|
CanHandleDuration = sceneReq.CanHandleDuration,
|
|
ManualColorSelection = sceneReq.ManualColorSelection,
|
|
};
|
|
|
|
foreach (var c in sceneReq.Contents)
|
|
scene.Contents.Add(MapContentEntity(c));
|
|
|
|
foreach (var c in sceneReq.Colors)
|
|
scene.Colors.Add(new SavedSceneColor
|
|
{
|
|
ElementKey = c.ElementKey, Title = c.Title, Icon = c.Icon,
|
|
AttrValue = c.AttrValue, Value = c.Value, IsSelected = c.IsSelected, Sort = c.Sort
|
|
});
|
|
|
|
db.SavedScenes.Add(scene);
|
|
}
|
|
|
|
// Sync shared colors
|
|
var sharedColors = scenes.SelectMany(s => s.SharedColors).DistinctBy(x => x.ElementKey).ToList();
|
|
if (sharedColors.Count > 0)
|
|
{
|
|
var existingSC = await db.SavedSharedColors
|
|
.Where(x => x.SavedProjectId == projectId).ToListAsync();
|
|
db.SavedSharedColors.RemoveRange(existingSC);
|
|
foreach (var c in sharedColors)
|
|
db.SavedSharedColors.Add(new SavedSharedColor
|
|
{
|
|
SavedProjectId = projectId, ElementKey = c.ElementKey, Title = c.Title,
|
|
Icon = c.Icon, AttrValue = c.AttrValue, Value = c.Value,
|
|
IsSelected = c.IsSelected, Sort = c.Sort
|
|
});
|
|
}
|
|
|
|
// Sync shared layers
|
|
var sharedLayers = scenes.SelectMany(s => s.SharedLayers).DistinctBy(x => x.Key).ToList();
|
|
if (sharedLayers.Count > 0)
|
|
{
|
|
var existingSL = await db.SavedSharedLayers
|
|
.Where(x => x.SavedProjectId == projectId).ToListAsync();
|
|
db.SavedSharedLayers.RemoveRange(existingSL);
|
|
foreach (var l in sharedLayers)
|
|
db.SavedSharedLayers.Add(MapSharedLayerEntity(projectId, l));
|
|
}
|
|
|
|
project.LastEditDate = DateTime.UtcNow;
|
|
project.UpdatedAt = DateTime.UtcNow;
|
|
|
|
// Recalc total duration
|
|
project.ProjectDurationSec = scenes.Sum(s => s.SceneLengthSec - s.OverlapAtEndSec);
|
|
|
|
await db.SaveChangesAsync();
|
|
return await GetProjectAsync(projectId, userId);
|
|
}
|
|
|
|
// ── Mappers ───────────────────────────────────────────────────────────────
|
|
|
|
private static SavedProjectSummaryResponse MapSummary(SavedProject p) => new(
|
|
p.Id, p.UserId, p.OriginalProjectId, p.OriginalProjectName, p.OriginalContainerSlug,
|
|
p.Name, p.Image, p.Type.ToString(), p.Resolution, p.ChooseMode,
|
|
p.ProjectDurationSec, p.LastEditDate, p.CreatedAt);
|
|
|
|
private static SavedProjectFullResponse MapFull(SavedProject p) => new(
|
|
p.Id, p.UserId, p.OriginalProjectId, p.OriginalProjectName, p.OriginalContainerSlug,
|
|
p.Name, p.Image, p.Type.ToString(), p.FrameRate, p.ProjectDurationSec,
|
|
p.Resolution, p.ChooseMode, p.VipFactor,
|
|
p.MusicFileId, p.MusicTrackId, p.MusicVolume,
|
|
p.VoiceoverFileId, p.VoiceoverVolume, p.VoiceoverRecordedInBrowser,
|
|
p.SfxVolume, p.SfxEnabled, p.AudioVisualizerMusicUrl, p.AudioVisualizerDurationSec,
|
|
p.ManualColorPicker, p.SelectedPresetStoryId,
|
|
p.LastEditStep, p.EditState, p.LastEditDate, p.CreatedAt,
|
|
p.Scenes.Select(MapSceneResponse).ToList(),
|
|
p.SharedColors.Select(MapSharedColorResponse).ToList(),
|
|
p.SharedColorPresets.Select(MapSharedColorPresetResponse).ToList(),
|
|
p.SharedLayers.Select(MapSharedLayerResponse).ToList()
|
|
);
|
|
|
|
private static SavedSceneResponse MapSceneResponse(SavedScene s) => new(
|
|
s.Id, s.OriginalSceneId, s.Key, s.Title, s.Image, s.SceneType,
|
|
s.Sort, s.SceneLengthSec, s.MinDurationSec, s.MaxDurationSec,
|
|
s.OverlapAtEndSec, s.CanHandleDuration, s.ManualColorSelection, s.SelectedColorPresetId,
|
|
s.Contents.Select(MapContentResponse).ToList(),
|
|
s.Colors.Select(c => new SavedSceneColorResponse(c.Id, c.ElementKey, c.Title, c.Icon, c.AttrValue, c.Value, c.IsSelected, c.Sort)).ToList(),
|
|
s.ColorPresets.Select(cp => new SavedSceneColorPresetResponse(cp.Id, cp.IsSelected, cp.Sort,
|
|
cp.Items.Select(i => new SavedColorPresetItemResponse(i.Id, i.ElementKey, i.Value, i.Sort)).ToList()
|
|
)).ToList(),
|
|
s.Characters.Select(ch => new SavedSceneCharacterResponse(ch.Id, ch.Key, ch.Name, ch.Icon,
|
|
ch.Controllers.Select(c => new SavedCharacterControllerResponse(c.Id, c.Name, c.Key, c.Value, c.Sort)).ToList()
|
|
)).ToList()
|
|
);
|
|
|
|
private static SavedSceneContentResponse MapContentResponse(SavedSceneContent c) => new(
|
|
c.Id, c.Key, c.Title, c.Type, c.Value, c.ValueFileId, c.FileUrlCached,
|
|
c.FontFace, c.FontSize, c.Justify, c.PositionInContainer, c.DirectionLayerValue,
|
|
c.IsTextBox, c.AiInputType, c.SelectedDp, c.RepeaterItemKey, c.RepeaterIndex,
|
|
c.IsFocused, c.MappedList, c.Sort);
|
|
|
|
private static SavedSharedColorResponse MapSharedColorResponse(SavedSharedColor c) =>
|
|
new(c.Id, c.ElementKey, c.Title, c.Icon, c.AttrValue, c.Value, c.IsSelected, c.Sort);
|
|
|
|
private static SavedSharedColorPresetResponse MapSharedColorPresetResponse(SavedSharedColorPreset cp) =>
|
|
new(cp.Id, cp.Name, cp.IsSelected, cp.Sort,
|
|
cp.Items.Select(i => new SavedColorPresetItemResponse(i.Id, i.ElementKey, i.Value, i.Sort)).ToList());
|
|
|
|
private static SavedSharedLayerResponse MapSharedLayerResponse(SavedSharedLayer l) => new(
|
|
l.Id, l.Key, l.Title, l.Type, l.Value, l.ValueFileId, l.FileUrlCached,
|
|
l.FontFace, l.FontSize, l.Justify, l.PositionInContainer, l.DirectionLayerValue,
|
|
l.IsTextBox, l.AiInputType, l.MappedList, l.Width, l.Height,
|
|
l.IsFocused, l.IsFontChangeable, l.IsFontSizeChangeable, l.Sort);
|
|
|
|
private static SavedSceneContent MapContentEntity(SaveSceneContentRequest c) => new()
|
|
{
|
|
Key = c.Key, Title = c.Title, Type = c.Type, Value = c.Value,
|
|
ValueFileId = c.ValueFileId, InsertedFileType = c.InsertedFileType,
|
|
FontFace = c.FontFace, FontFaceName = c.FontFaceName, FontSize = c.FontSize,
|
|
DefaultFontSize = c.DefaultFontSize, DefaultFontFace = c.DefaultFontFace,
|
|
Justify = c.Justify, PositionInContainer = c.PositionInContainer,
|
|
DirectionLayerValue = c.DirectionLayerValue, IsTextBox = c.IsTextBox,
|
|
AiInputType = c.AiInputType, SelectedDp = c.SelectedDp,
|
|
RepeaterItemKey = c.RepeaterItemKey, RepeaterIndex = c.RepeaterIndex,
|
|
IsFocused = c.IsFocused, MappedList = c.MappedList, Thumbnail = c.Thumbnail, Sort = c.Sort
|
|
};
|
|
|
|
private static SavedSharedLayer MapSharedLayerEntity(Guid projectId, SaveSharedLayerRequest l) => new()
|
|
{
|
|
SavedProjectId = projectId, Key = l.Key, Title = l.Title, Type = l.Type,
|
|
Value = l.Value, ValueFileId = l.ValueFileId, FontFace = l.FontFace,
|
|
FontFaceName = l.FontFaceName, FontSize = l.FontSize, Justify = l.Justify,
|
|
PositionInContainer = l.PositionInContainer, DirectionLayerValue = l.DirectionLayerValue,
|
|
IsTextBox = l.IsTextBox, AiInputType = l.AiInputType, MappedList = l.MappedList,
|
|
Thumbnail = l.Thumbnail, Width = l.Width, Height = l.Height, IsFocused = l.IsFocused,
|
|
IsFontChangeable = l.IsFontChangeable, IsFontSizeChangeable = l.IsFontSizeChangeable, Sort = l.Sort
|
|
};
|
|
}
|