PlotDirector/PlotLine/Controllers/StoryAssetsController.cs
Nick Beckley 34a8d94cb4 Implemented the Visual Identity infrastructure pass.
Changed
Added image fields for Characters and Story Assets in models, view models, repositories, services, and save flows.
Added reusable avatar/initials rendering via [AvatarHelper.cs](C:/Source/PlotLine/PlotLine/Services/AvatarHelper.cs) and [_Avatar.cshtml](C:/Source/PlotLine/PlotLine/Views/Shared/_Avatar.cshtml).
Added upload/remove processing through existing upload storage in [VisualIdentityImageService.cs](C:/Source/PlotLine/PlotLine/Services/VisualIdentityImageService.cs).
Updated Character and Story Asset list/detail/edit views with thumbnails, initials fallback, upload, replace, and remove controls.
Added styling in [site.css](C:/Source/PlotLine/PlotLine/wwwroot/css/site.css) and refreshed site.min.css.
Database
Added new migration [079_VisualIdentityCharacterAssetImages.sql](C:/Source/PlotLine/PlotLine/Sql/079_VisualIdentityCharacterAssetImages.sql).
Adds nullable ImagePath and ThumbnailPath to Characters and StoryAssets.
Applied locally and confirmed the columns exist.
Stored Procedures
Updated active procedure definitions only:
Character_ListByProject
Character_Get
Character_Save
Character_UpdateImage
StoryAsset_ListByProject
StoryAsset_Get
StoryAsset_Save
StoryAsset_UpdateImage
2026-06-22 10:10:39 +01:00

123 lines
3.9 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using PlotLine.Services;
using PlotLine.ViewModels;
namespace PlotLine.Controllers;
[Authorize]
public sealed class StoryAssetsController(IAssetService assets) : Controller
{
public async Task<IActionResult> Index(int projectId)
{
var model = await assets.GetAssetsAsync(projectId);
return model is null ? NotFound() : View(model);
}
public async Task<IActionResult> Create(int projectId)
{
var model = await assets.GetCreateAssetAsync(projectId);
return model is null ? NotFound() : View("Edit", model);
}
public async Task<IActionResult> Edit(int id)
{
var model = await assets.GetEditAssetAsync(id);
return model is null ? NotFound() : View(model);
}
public async Task<IActionResult> Details(int id)
{
var model = await assets.GetAssetDetailAsync(id);
return model is null ? NotFound() : View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Save(StoryAssetEditViewModel model)
{
if (!ModelState.IsValid)
{
var invalidModel = await RehydrateEditModel(model);
return invalidModel is null ? NotFound() : View("Edit", invalidModel);
}
int assetId;
try
{
assetId = await assets.SaveAssetAsync(model);
}
catch (EntityImageUploadException ex)
{
TempData["ImageError"] = ex.Message;
return RedirectToAction(nameof(Edit), new { id = ex.EntityId });
}
catch (InvalidOperationException ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
var editModel = await RehydrateEditModel(model);
return editModel is null ? NotFound() : View("Edit", editModel);
}
return RedirectToAction(nameof(Details), new { id = assetId });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Archive(int id, int projectId)
{
await assets.ArchiveAssetAsync(id);
TempData["ArchiveMessage"] = "Asset archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddDependency(AssetDependencyEditViewModel model, int returnAssetId)
{
await assets.SaveDependencyAsync(model);
return RedirectToAction(nameof(Details), new { id = returnAssetId });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DropDependency(AssetDependencyEditViewModel model)
{
if (model.SourceAssetID != model.TargetAssetID)
{
await assets.SaveDependencyAsync(model);
}
return RedirectToAction(nameof(Index), new { projectId = model.ProjectID });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteDependency(int id, int returnAssetId)
{
await assets.DeleteDependencyAsync(id);
return RedirectToAction(nameof(Details), new { id = returnAssetId });
}
private async Task<StoryAssetEditViewModel?> RehydrateEditModel(StoryAssetEditViewModel posted)
{
var model = posted.StoryAssetID == 0
? await assets.GetCreateAssetAsync(posted.ProjectID)
: await assets.GetEditAssetAsync(posted.StoryAssetID);
if (model is null)
{
return null;
}
model.AssetName = posted.AssetName;
model.AssetKindID = posted.AssetKindID;
model.Description = posted.Description;
model.Importance = posted.Importance;
model.CurrentStateID = posted.CurrentStateID;
model.CurrentLocationID = posted.CurrentLocationID;
model.IsResolved = posted.IsResolved;
model.ShowInQuickAddBar = posted.ShowInQuickAddBar;
return model;
}
}