PlotDirector Phase 17H - Character Knowledge Editor Migration
This commit is contained in:
parent
0adca386d9
commit
36a4c76921
@ -124,6 +124,15 @@ public sealed class ScenesController(
|
|||||||
: PartialView("~/Views/Shared/SceneInspectorV2/_SceneCharactersEditor.cshtml", model);
|
: PartialView("~/Views/Shared/SceneInspectorV2/_SceneCharactersEditor.cshtml", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> SceneKnowledgeEditor(int sceneId)
|
||||||
|
{
|
||||||
|
var model = await scenes.GetEditAsync(sceneId);
|
||||||
|
return model is null
|
||||||
|
? NotFound()
|
||||||
|
: PartialView("~/Views/Shared/SceneInspectorV2/_SceneKnowledgeEditor.cshtml", model);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> SceneLocationsEditor(int sceneId)
|
public async Task<IActionResult> SceneLocationsEditor(int sceneId)
|
||||||
{
|
{
|
||||||
@ -1123,6 +1132,24 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> AddCharacterKnowledge(CharacterKnowledgeCreateViewModel model)
|
public async Task<IActionResult> AddCharacterKnowledge(CharacterKnowledgeCreateViewModel model)
|
||||||
{
|
{
|
||||||
await characters.AddKnowledgeAsync(model);
|
await characters.AddKnowledgeAsync(model);
|
||||||
|
if (IsAjaxRequest())
|
||||||
|
{
|
||||||
|
return await SceneKnowledgeRefreshJsonAsync(model.SceneID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> UpdateCharacterKnowledge(CharacterKnowledgeCreateViewModel model)
|
||||||
|
{
|
||||||
|
await characters.UpdateKnowledgeAsync(model);
|
||||||
|
if (IsAjaxRequest())
|
||||||
|
{
|
||||||
|
return await SceneKnowledgeRefreshJsonAsync(model.SceneID);
|
||||||
|
}
|
||||||
|
|
||||||
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1131,6 +1158,11 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> DeleteCharacterKnowledge(int id, int sceneId, int? projectId, int? bookId)
|
public async Task<IActionResult> DeleteCharacterKnowledge(int id, int sceneId, int? projectId, int? bookId)
|
||||||
{
|
{
|
||||||
await characters.DeleteKnowledgeAsync(id);
|
await characters.DeleteKnowledgeAsync(id);
|
||||||
|
if (IsAjaxRequest())
|
||||||
|
{
|
||||||
|
return await SceneKnowledgeRefreshJsonAsync(sceneId);
|
||||||
|
}
|
||||||
|
|
||||||
return RedirectForScene(sceneId, projectId, bookId);
|
return RedirectForScene(sceneId, projectId, bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1359,6 +1391,30 @@ public sealed class ScenesController(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<IActionResult> SceneKnowledgeRefreshJsonAsync(int sceneId)
|
||||||
|
{
|
||||||
|
var updated = await scenes.GetEditAsync(sceneId);
|
||||||
|
if (updated is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
html = new
|
||||||
|
{
|
||||||
|
health = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneInspectorHealthStrip.cshtml", updated),
|
||||||
|
people = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml", updated),
|
||||||
|
continuity = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneContinuityCard.cshtml", updated)
|
||||||
|
},
|
||||||
|
scene = new
|
||||||
|
{
|
||||||
|
updated.SceneID
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private IActionResult RedirectForScene(int sceneId, int? projectId, int? bookId)
|
private IActionResult RedirectForScene(int sceneId, int? projectId, int? bookId)
|
||||||
{
|
{
|
||||||
if (TryGetLocalReturnUrl(out var returnUrl))
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
||||||
|
|||||||
@ -261,6 +261,7 @@ public interface ICharacterService
|
|||||||
Task RejectSceneCharacterSuggestionAsync(int suggestionId);
|
Task RejectSceneCharacterSuggestionAsync(int suggestionId);
|
||||||
Task<int> AddAttributeEventAsync(CharacterAttributeEventCreateViewModel model);
|
Task<int> AddAttributeEventAsync(CharacterAttributeEventCreateViewModel model);
|
||||||
Task<int> AddKnowledgeAsync(CharacterKnowledgeCreateViewModel model);
|
Task<int> AddKnowledgeAsync(CharacterKnowledgeCreateViewModel model);
|
||||||
|
Task<int> UpdateKnowledgeAsync(CharacterKnowledgeCreateViewModel model);
|
||||||
Task DeleteKnowledgeAsync(int characterKnowledgeId);
|
Task DeleteKnowledgeAsync(int characterKnowledgeId);
|
||||||
Task<int> AddRelationshipEventAsync(RelationshipEventCreateViewModel model);
|
Task<int> AddRelationshipEventAsync(RelationshipEventCreateViewModel model);
|
||||||
Task DeleteRelationshipEventAsync(int relationshipEventId);
|
Task DeleteRelationshipEventAsync(int relationshipEventId);
|
||||||
@ -9521,13 +9522,19 @@ public sealed class CharacterService(
|
|||||||
Description = model.Description
|
Description = model.Description
|
||||||
});
|
});
|
||||||
|
|
||||||
public Task<int> AddKnowledgeAsync(CharacterKnowledgeCreateViewModel model) => characters.SaveKnowledgeAsync(new CharacterKnowledgeItem
|
public Task<int> AddKnowledgeAsync(CharacterKnowledgeCreateViewModel model) => SaveKnowledgeAsync(model);
|
||||||
|
|
||||||
|
public Task<int> UpdateKnowledgeAsync(CharacterKnowledgeCreateViewModel model) => SaveKnowledgeAsync(model);
|
||||||
|
|
||||||
|
private Task<int> SaveKnowledgeAsync(CharacterKnowledgeCreateViewModel model) => characters.SaveKnowledgeAsync(new CharacterKnowledgeItem
|
||||||
{
|
{
|
||||||
|
CharacterKnowledgeID = model.CharacterKnowledgeID,
|
||||||
CharacterID = model.CharacterID,
|
CharacterID = model.CharacterID,
|
||||||
StoryAssetID = model.StoryAssetID,
|
StoryAssetID = model.StoryAssetID,
|
||||||
PlotThreadID = model.PlotThreadID,
|
PlotThreadID = model.PlotThreadID,
|
||||||
SceneID = model.SceneID,
|
SceneID = model.SceneID,
|
||||||
KnowledgeStateID = model.KnowledgeStateID,
|
KnowledgeStateID = model.KnowledgeStateID,
|
||||||
|
SourceEventID = model.SourceEventID,
|
||||||
Description = model.Description
|
Description = model.Description
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,7 @@ public sealed class ProjectAccessFilter(IProjectAccessService access, ICurrentUs
|
|||||||
["ParentPlotLineID"] = "PlotLine",
|
["ParentPlotLineID"] = "PlotLine",
|
||||||
["PlotThreadID"] = "PlotThread",
|
["PlotThreadID"] = "PlotThread",
|
||||||
["CharacterRelationshipID"] = "CharacterRelationship",
|
["CharacterRelationshipID"] = "CharacterRelationship",
|
||||||
|
["CharacterKnowledgeID"] = "CharacterKnowledge",
|
||||||
["TimelineViewPresetID"] = "TimelineViewPreset",
|
["TimelineViewPresetID"] = "TimelineViewPreset",
|
||||||
["ScenarioID"] = "Scenario"
|
["ScenarioID"] = "Scenario"
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2025,11 +2025,13 @@ public sealed class CharacterAttributeEventCreateViewModel
|
|||||||
|
|
||||||
public sealed class CharacterKnowledgeCreateViewModel
|
public sealed class CharacterKnowledgeCreateViewModel
|
||||||
{
|
{
|
||||||
|
public int CharacterKnowledgeID { get; set; }
|
||||||
public int SceneID { get; set; }
|
public int SceneID { get; set; }
|
||||||
public int CharacterID { get; set; }
|
public int CharacterID { get; set; }
|
||||||
public int? StoryAssetID { get; set; }
|
public int? StoryAssetID { get; set; }
|
||||||
public int? PlotThreadID { get; set; }
|
public int? PlotThreadID { get; set; }
|
||||||
public int KnowledgeStateID { get; set; }
|
public int KnowledgeStateID { get; set; }
|
||||||
|
public int? SourceEventID { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public int? ReturnProjectID { get; set; }
|
public int? ReturnProjectID { get; set; }
|
||||||
public int? ReturnBookID { get; set; }
|
public int? ReturnBookID { get; set; }
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
: $"{warningCount} warning{(warningCount == 1 ? "" : "s")} need review.";
|
: $"{warningCount} warning{(warningCount == 1 ? "" : "s")} need review.";
|
||||||
}
|
}
|
||||||
|
|
||||||
<article class="scene-inspector-v2-card">
|
<article class="scene-inspector-v2-card" data-scene-summary-part="continuity">
|
||||||
<div class="scene-inspector-v2-card-header">
|
<div class="scene-inspector-v2-card-header">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Continuity</p>
|
<p class="eyebrow">Continuity</p>
|
||||||
|
|||||||
@ -0,0 +1,165 @@
|
|||||||
|
@model SceneEditViewModel
|
||||||
|
|
||||||
|
<div class="scene-inspector-editor-form">
|
||||||
|
<div class="scene-inspector-editor-fields">
|
||||||
|
<section class="scene-structure-editor-section">
|
||||||
|
<div class="scene-structure-editor-heading">
|
||||||
|
<span>Knowledge changes</span>
|
||||||
|
<strong>@Model.CharacterKnowledge.Count</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!Model.CharacterKnowledge.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No knowledge changes are recorded for this scene yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="scene-structure-editor-list">
|
||||||
|
@foreach (var item in Model.CharacterKnowledge)
|
||||||
|
{
|
||||||
|
<article class="scene-structure-editor-card">
|
||||||
|
<div class="scene-structure-card-header">
|
||||||
|
<div>
|
||||||
|
<h4>@item.CharacterName</h4>
|
||||||
|
<p>@(item.AssetName ?? item.ThreadTitle ?? "Scene knowledge")</p>
|
||||||
|
</div>
|
||||||
|
<span class="scene-structure-marker">@item.KnowledgeStateName</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(item.Description))
|
||||||
|
{
|
||||||
|
<p>@item.Description</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="UpdateCharacterKnowledge" method="post" class="scene-structure-editor-form" data-scene-editor-form>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
<input type="hidden" name="CharacterKnowledgeID" value="@item.CharacterKnowledgeID" />
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="SourceEventID" value="@item.SourceEventID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
|
||||||
|
<div class="scene-inspector-editor-error" data-scene-editor-error hidden></div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Knowledge_@(item.CharacterKnowledgeID)__CharacterID">Character</label>
|
||||||
|
<select class="form-control form-control-sm" id="Knowledge_@(item.CharacterKnowledgeID)__CharacterID" name="CharacterID">
|
||||||
|
@foreach (var option in Model.CharacterOptions)
|
||||||
|
{
|
||||||
|
<option value="@option.Value" selected="@(option.Value == item.CharacterID.ToString())">@option.Text</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scene-inspector-editor-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Knowledge_@(item.CharacterKnowledgeID)__StoryAssetID">Related asset</label>
|
||||||
|
<select class="form-control form-control-sm" id="Knowledge_@(item.CharacterKnowledgeID)__StoryAssetID" name="StoryAssetID">
|
||||||
|
<option value="">No asset</option>
|
||||||
|
@foreach (var option in Model.AssetOptions)
|
||||||
|
{
|
||||||
|
<option value="@option.Value" selected="@(item.StoryAssetID?.ToString() == option.Value)">@option.Text</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Knowledge_@(item.CharacterKnowledgeID)__PlotThreadID">Related plot thread</label>
|
||||||
|
<select class="form-control form-control-sm" id="Knowledge_@(item.CharacterKnowledgeID)__PlotThreadID" name="PlotThreadID">
|
||||||
|
<option value="">No plot thread</option>
|
||||||
|
@foreach (var option in Model.PlotThreadOptions)
|
||||||
|
{
|
||||||
|
<option value="@option.Value" selected="@(item.PlotThreadID?.ToString() == option.Value)">@option.Text</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Knowledge_@(item.CharacterKnowledgeID)__KnowledgeStateID">Knowledge state</label>
|
||||||
|
<select class="form-control form-control-sm" id="Knowledge_@(item.CharacterKnowledgeID)__KnowledgeStateID" name="KnowledgeStateID">
|
||||||
|
@foreach (var option in Model.KnowledgeStateOptions)
|
||||||
|
{
|
||||||
|
<option value="@option.Value" selected="@(option.Value == item.KnowledgeStateID.ToString())">@option.Text</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Knowledge_@(item.CharacterKnowledgeID)__Description">Notes</label>
|
||||||
|
<textarea class="form-control form-control-sm" id="Knowledge_@(item.CharacterKnowledgeID)__Description" name="Description" rows="3">@item.Description</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scene-structure-action-row">
|
||||||
|
<button class="btn btn-sm btn-primary" type="submit" data-scene-editor-save>Save knowledge</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteCharacterKnowledge" method="post" class="scene-structure-delete-form" data-scene-editor-form>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
<input type="hidden" name="id" value="@item.CharacterKnowledgeID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||||
|
<div class="scene-inspector-editor-error" data-scene-editor-error hidden></div>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" type="submit" data-scene-editor-save>Remove knowledge</button>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="scene-structure-editor-section">
|
||||||
|
<div class="scene-structure-editor-heading">
|
||||||
|
<span>Add knowledge change</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="AddCharacterKnowledge" method="post" class="scene-structure-editor-card scene-structure-editor-form" data-scene-editor-form>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
|
||||||
|
<div class="scene-inspector-editor-error" data-scene-editor-error hidden></div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="KnowledgeCharacterID">Character</label>
|
||||||
|
<select class="form-control form-control-sm" id="KnowledgeCharacterID" name="CharacterID" asp-items="Model.CharacterOptions"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scene-inspector-editor-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="KnowledgeStoryAssetID">Related asset</label>
|
||||||
|
<select class="form-control form-control-sm" id="KnowledgeStoryAssetID" name="StoryAssetID" asp-items="Model.AssetOptions">
|
||||||
|
<option value="">No asset</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="KnowledgePlotThreadID">Related plot thread</label>
|
||||||
|
<select class="form-control form-control-sm" id="KnowledgePlotThreadID" name="PlotThreadID" asp-items="Model.PlotThreadOptions">
|
||||||
|
<option value="">No plot thread</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="KnowledgeStateID">Knowledge state</label>
|
||||||
|
<select class="form-control form-control-sm" id="KnowledgeStateID" name="KnowledgeStateID" asp-items="Model.KnowledgeStateOptions"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="KnowledgeDescription">Notes</label>
|
||||||
|
<textarea class="form-control form-control-sm" id="KnowledgeDescription" name="Description" rows="3" placeholder="Reveal or discovery context"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scene-structure-action-row">
|
||||||
|
<button class="btn btn-sm btn-primary" type="submit" data-scene-editor-save>Add knowledge</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scene-inspector-editor-footer">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-scene-editor-close>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
<div class="scene-inspector-v2-actions">
|
<div class="scene-inspector-v2-actions">
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-scene-editor="characters" data-scene-id="@Model.SceneID" data-scene-editor-url="@Url.Action("SceneCharactersEditor", "Scenes", new { sceneId = Model.SceneID })">Manage characters</button>
|
<button type="button" class="btn btn-sm btn-outline-primary" data-scene-editor="characters" data-scene-id="@Model.SceneID" data-scene-editor-url="@Url.Action("SceneCharactersEditor", "Scenes", new { sceneId = Model.SceneID })">Manage characters</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-scene-editor="knowledge" data-scene-id="@Model.SceneID">Manage knowledge</button>
|
<button type="button" class="btn btn-sm btn-outline-primary" data-scene-editor="knowledge" data-scene-id="@Model.SceneID" data-scene-editor-url="@Url.Action("SceneKnowledgeEditor", "Scenes", new { sceneId = Model.SceneID })">Manage knowledge</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" data-scene-editor="relationships" data-scene-id="@Model.SceneID">Manage relationships</button>
|
<button type="button" class="btn btn-sm btn-outline-primary" data-scene-editor="relationships" data-scene-id="@Model.SceneID">Manage relationships</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user