From 2af1feb412b16e1423fac182f6ed088d3978071c Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Tue, 30 Jun 2026 15:27:55 +0100 Subject: [PATCH] PlotDirector Phase 17I - Relationship Editor Migration --- PlotLine/Controllers/ScenesController.cs | 52 +++++- PlotLine/Services/CoreServices.cs | 12 ++ PlotLine/Services/ProjectAccessFilter.cs | 1 + PlotLine/ViewModels/CoreViewModels.cs | 1 + .../SceneInspectorV2/_ScenePeopleCard.cshtml | 2 +- .../_SceneRelationshipsEditor.cshtml | 175 ++++++++++++++++++ 6 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 PlotLine/Views/Shared/SceneInspectorV2/_SceneRelationshipsEditor.cshtml diff --git a/PlotLine/Controllers/ScenesController.cs b/PlotLine/Controllers/ScenesController.cs index b8beff8..ffdf7eb 100644 --- a/PlotLine/Controllers/ScenesController.cs +++ b/PlotLine/Controllers/ScenesController.cs @@ -133,6 +133,15 @@ public sealed class ScenesController( : PartialView("~/Views/Shared/SceneInspectorV2/_SceneKnowledgeEditor.cshtml", model); } + [HttpGet] + public async Task SceneRelationshipsEditor(int sceneId) + { + var model = await scenes.GetEditAsync(sceneId); + return model is null + ? NotFound() + : PartialView("~/Views/Shared/SceneInspectorV2/_SceneRelationshipsEditor.cshtml", model); + } + [HttpGet] public async Task SceneLocationsEditor(int sceneId) { @@ -1170,7 +1179,38 @@ public sealed class ScenesController( [ValidateAntiForgeryToken] public async Task AddRelationshipEvent(RelationshipEventCreateViewModel model) { - await characters.AddRelationshipEventAsync(model); + try + { + await characters.AddRelationshipEventAsync(model); + } + catch (InvalidOperationException ex) + { + if (IsAjaxRequest()) + { + return BadRequest(new { success = false, message = ex.Message }); + } + + TempData["RelationshipEventAddError"] = ex.Message; + } + + if (IsAjaxRequest()) + { + return await ScenePeopleContinuityRefreshJsonAsync(model.SceneID); + } + + return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UpdateRelationshipEvent(RelationshipEventCreateViewModel model) + { + await characters.UpdateRelationshipEventAsync(model); + if (IsAjaxRequest()) + { + return await ScenePeopleContinuityRefreshJsonAsync(model.SceneID); + } + return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID); } @@ -1179,6 +1219,11 @@ public sealed class ScenesController( public async Task DeleteRelationshipEvent(int id, int sceneId, int? projectId, int? bookId) { await characters.DeleteRelationshipEventAsync(id); + if (IsAjaxRequest()) + { + return await ScenePeopleContinuityRefreshJsonAsync(sceneId); + } + return RedirectForScene(sceneId, projectId, bookId); } @@ -1392,6 +1437,11 @@ public sealed class ScenesController( } private async Task SceneKnowledgeRefreshJsonAsync(int sceneId) + { + return await ScenePeopleContinuityRefreshJsonAsync(sceneId); + } + + private async Task ScenePeopleContinuityRefreshJsonAsync(int sceneId) { var updated = await scenes.GetEditAsync(sceneId); if (updated is null) diff --git a/PlotLine/Services/CoreServices.cs b/PlotLine/Services/CoreServices.cs index 6f7d62e..dfb72a6 100644 --- a/PlotLine/Services/CoreServices.cs +++ b/PlotLine/Services/CoreServices.cs @@ -264,6 +264,7 @@ public interface ICharacterService Task UpdateKnowledgeAsync(CharacterKnowledgeCreateViewModel model); Task DeleteKnowledgeAsync(int characterKnowledgeId); Task AddRelationshipEventAsync(RelationshipEventCreateViewModel model); + Task UpdateRelationshipEventAsync(RelationshipEventCreateViewModel model); Task DeleteRelationshipEventAsync(int relationshipEventId); string DisplayAge(Character character, DateTime? sceneStartDateTime = null); } @@ -9577,6 +9578,17 @@ public sealed class CharacterService( return relationshipEventId; } + public Task UpdateRelationshipEventAsync(RelationshipEventCreateViewModel model) => characters.SaveRelationshipEventAsync(new RelationshipEvent + { + RelationshipEventID = model.RelationshipEventID, + CharacterRelationshipID = model.CharacterRelationshipID.GetValueOrDefault(), + SceneID = model.SceneID, + RelationshipStateID = model.RelationshipStateID, + Intensity = model.Intensity, + IsKnownToOtherCharacter = model.IsKnownToOtherCharacter, + Description = model.Description + }); + public Task DeleteRelationshipEventAsync(int relationshipEventId) => characters.DeleteRelationshipEventAsync(relationshipEventId); public string DisplayAge(Character character, DateTime? sceneStartDateTime = null) diff --git a/PlotLine/Services/ProjectAccessFilter.cs b/PlotLine/Services/ProjectAccessFilter.cs index cb20cf5..01899ee 100644 --- a/PlotLine/Services/ProjectAccessFilter.cs +++ b/PlotLine/Services/ProjectAccessFilter.cs @@ -60,6 +60,7 @@ public sealed class ProjectAccessFilter(IProjectAccessService access, ICurrentUs ["PlotThreadID"] = "PlotThread", ["CharacterRelationshipID"] = "CharacterRelationship", ["CharacterKnowledgeID"] = "CharacterKnowledge", + ["RelationshipEventID"] = "RelationshipEvent", ["TimelineViewPresetID"] = "TimelineViewPreset", ["ScenarioID"] = "Scenario" }; diff --git a/PlotLine/ViewModels/CoreViewModels.cs b/PlotLine/ViewModels/CoreViewModels.cs index bdabf2b..a870653 100644 --- a/PlotLine/ViewModels/CoreViewModels.cs +++ b/PlotLine/ViewModels/CoreViewModels.cs @@ -2039,6 +2039,7 @@ public sealed class CharacterKnowledgeCreateViewModel public sealed class RelationshipEventCreateViewModel { + public int RelationshipEventID { get; set; } public int SceneID { get; set; } public int? CharacterRelationshipID { get; set; } public int? CharacterAID { get; set; } diff --git a/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml b/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml index 6db5d29..8461cb0 100644 --- a/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml +++ b/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml @@ -54,6 +54,6 @@
- +
diff --git a/PlotLine/Views/Shared/SceneInspectorV2/_SceneRelationshipsEditor.cshtml b/PlotLine/Views/Shared/SceneInspectorV2/_SceneRelationshipsEditor.cshtml new file mode 100644 index 0000000..cbe7b24 --- /dev/null +++ b/PlotLine/Views/Shared/SceneInspectorV2/_SceneRelationshipsEditor.cshtml @@ -0,0 +1,175 @@ +@model SceneEditViewModel + +
+
+
+
+ Relationship events + @Model.RelationshipEvents.Count +
+ + @if (!Model.RelationshipEvents.Any()) + { +

No relationship events are recorded for this scene yet.

+ } + else + { +
+ @foreach (var item in Model.RelationshipEvents) + { +
+
+
+

@item.CharacterAName -> @item.CharacterBName

+

@item.RelationshipTypeName

+
+ @item.RelationshipStateName +
+ +

Intensity @(item.Intensity?.ToString() ?? "not set")@(item.IsKnownToOtherCharacter ? " / known to other character" : "")

+ @if (!string.IsNullOrWhiteSpace(item.Description)) + { +

@item.Description

+ } + +
+ @Html.AntiForgeryToken() + + + + + + + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + + +
+ + +
+ +
+ +
+
+ +
+ @Html.AntiForgeryToken() + + + + + + +
+
+ } +
+ } +
+ +
+
+ Add relationship event +
+ +
+ @Html.AntiForgeryToken() + + + + + + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + + +
+ + +
+ +
+ +
+
+
+
+ + +