Phase 12B.2: Continuity Explorer Result Context Improvements.
This commit is contained in:
parent
1420c43310
commit
dc4324f970
@ -2280,6 +2280,8 @@ public sealed class StoryStateService(
|
||||
{
|
||||
Project = project,
|
||||
Filter = filter,
|
||||
ShowBookContext = ShouldShowBookContext(filter, scopedScenes, timeline.Chapters),
|
||||
ShowChapterContext = ShouldShowChapterContext(filter, scopedScenes, timeline.Chapters),
|
||||
BookOptions = timeline.Books.Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString(), x.BookID == filter.BookID)).ToList(),
|
||||
ChapterOptions = timeline.Chapters.Select(x => new SelectListItem($"Ch {x.ChapterNumber}: {x.ChapterTitle}", x.ChapterID.ToString(), x.ChapterID == filter.ChapterID)).ToList(),
|
||||
SceneOptions = orderedScenes.Select(x => new SelectListItem($"Scene {x.SceneNumber:g}: {x.SceneTitle}", x.SceneID.ToString(), x.SceneID == filter.SelectedSceneID)).ToList(),
|
||||
@ -2365,12 +2367,19 @@ public sealed class StoryStateService(
|
||||
}
|
||||
|
||||
previousLocationId = row.LocationID;
|
||||
var sceneContext = context.SceneContexts.GetValueOrDefault(row.SceneID);
|
||||
result.Add(new CharacterJourneyViewModel
|
||||
{
|
||||
CharacterID = character.CharacterID,
|
||||
CharacterName = character.CharacterName,
|
||||
BookID = sceneContext?.BookID ?? 0,
|
||||
BookDisplayName = sceneContext?.BookDisplayName ?? string.Empty,
|
||||
ChapterID = sceneContext?.ChapterID ?? 0,
|
||||
ChapterDisplayName = sceneContext?.ChapterDisplayName ?? string.Empty,
|
||||
SceneID = row.SceneID,
|
||||
SceneTitle = sceneContext?.SceneTitle ?? string.Empty,
|
||||
SceneLabel = context.SceneLabels.GetValueOrDefault(row.SceneID, $"Scene {row.SceneNumber:g}"),
|
||||
SceneDisplayName = sceneContext?.SceneDisplayName ?? context.SceneLabels.GetValueOrDefault(row.SceneID, $"Scene {row.SceneNumber:g}"),
|
||||
Location = row.LocationPath ?? row.LocationName ?? "Unknown"
|
||||
});
|
||||
}
|
||||
@ -2408,12 +2417,19 @@ public sealed class StoryStateService(
|
||||
}
|
||||
|
||||
previousLabel = row.Label;
|
||||
var sceneContext = context.SceneContexts.GetValueOrDefault(row.SceneID);
|
||||
result.Add(new AssetJourneyViewModel
|
||||
{
|
||||
StoryAssetID = asset.StoryAssetID,
|
||||
AssetName = asset.AssetName,
|
||||
BookID = sceneContext?.BookID ?? 0,
|
||||
BookDisplayName = sceneContext?.BookDisplayName ?? string.Empty,
|
||||
ChapterID = sceneContext?.ChapterID ?? 0,
|
||||
ChapterDisplayName = sceneContext?.ChapterDisplayName ?? string.Empty,
|
||||
SceneID = row.SceneID,
|
||||
SceneTitle = sceneContext?.SceneTitle ?? string.Empty,
|
||||
SceneLabel = context.SceneLabels.GetValueOrDefault(row.SceneID, "Unknown scene"),
|
||||
SceneDisplayName = sceneContext?.SceneDisplayName ?? context.SceneLabels.GetValueOrDefault(row.SceneID, "Unknown scene"),
|
||||
HolderOrLocation = row.Label
|
||||
});
|
||||
}
|
||||
@ -2461,12 +2477,19 @@ public sealed class StoryStateService(
|
||||
continue;
|
||||
}
|
||||
|
||||
var sceneContext = context.SceneContexts.GetValueOrDefault(scene.SceneID);
|
||||
result.Add(new LocationActivityViewModel
|
||||
{
|
||||
LocationID = location.LocationID,
|
||||
LocationName = location.LocationPath,
|
||||
BookID = sceneContext?.BookID ?? 0,
|
||||
BookDisplayName = sceneContext?.BookDisplayName ?? string.Empty,
|
||||
ChapterID = sceneContext?.ChapterID ?? 0,
|
||||
ChapterDisplayName = sceneContext?.ChapterDisplayName ?? string.Empty,
|
||||
SceneID = scene.SceneID,
|
||||
SceneTitle = sceneContext?.SceneTitle ?? scene.SceneTitle,
|
||||
SceneLabel = SceneLabel(scene.SceneNumber, scene.SceneTitle),
|
||||
SceneDisplayName = sceneContext?.SceneDisplayName ?? SceneLabel(scene.SceneNumber, scene.SceneTitle),
|
||||
CharactersPresent = sceneCharacters.Any() ? string.Join(", ", sceneCharacters) : "None",
|
||||
AssetsPresent = sceneAssets.Any() ? string.Join(", ", sceneAssets) : "None"
|
||||
});
|
||||
@ -2485,7 +2508,8 @@ public sealed class StoryStateService(
|
||||
scopedScenes,
|
||||
scopedScenes.Select(x => x.SceneID).ToHashSet(),
|
||||
BuildSceneIndexes(orderedScenes),
|
||||
orderedScenes.ToDictionary(x => x.SceneID, x => SceneLabel(x.SceneNumber, x.SceneTitle)));
|
||||
orderedScenes.ToDictionary(x => x.SceneID, x => SceneLabel(x.SceneNumber, x.SceneTitle)),
|
||||
BuildSceneContexts(timeline));
|
||||
}
|
||||
|
||||
private static IEnumerable<Scene> OrderScenes(TimelineData timeline)
|
||||
@ -2709,6 +2733,71 @@ public sealed class StoryStateService(
|
||||
.ToDictionary(x => x.SceneID, x => x.Index);
|
||||
}
|
||||
|
||||
private static Dictionary<int, SceneContext> BuildSceneContexts(TimelineData timeline)
|
||||
{
|
||||
var chaptersById = timeline.Chapters.ToDictionary(x => x.ChapterID);
|
||||
var booksById = timeline.Books.ToDictionary(x => x.BookID);
|
||||
|
||||
return timeline.Scenes.ToDictionary(
|
||||
scene => scene.SceneID,
|
||||
scene =>
|
||||
{
|
||||
chaptersById.TryGetValue(scene.ChapterID, out var chapter);
|
||||
var book = chapter is null ? null : booksById.GetValueOrDefault(chapter.BookID);
|
||||
|
||||
return new SceneContext(
|
||||
book?.BookID ?? 0,
|
||||
book is null ? string.Empty : $"Book {book.BookNumber}: {book.BookTitle}",
|
||||
chapter?.ChapterID ?? 0,
|
||||
chapter is null ? string.Empty : $"Ch {chapter.ChapterNumber:g}: {chapter.ChapterTitle}",
|
||||
scene.SceneID,
|
||||
scene.SceneTitle,
|
||||
SceneLabel(scene.SceneNumber, scene.SceneTitle));
|
||||
});
|
||||
}
|
||||
|
||||
private static bool ShouldShowBookContext(ContinuityFilter filter, IReadOnlyList<Scene> scopedScenes, IReadOnlyList<Chapter> chapters)
|
||||
{
|
||||
if (string.Equals(filter.SceneRangeMode, "EntireProject", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!string.Equals(filter.SceneRangeMode, "Custom", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var chapterBookIds = chapters.ToDictionary(x => x.ChapterID, x => x.BookID);
|
||||
return scopedScenes
|
||||
.Select(scene => chapterBookIds.GetValueOrDefault(scene.ChapterID))
|
||||
.Where(bookId => bookId > 0)
|
||||
.Distinct()
|
||||
.Count() > 1;
|
||||
}
|
||||
|
||||
private static bool ShouldShowChapterContext(ContinuityFilter filter, IReadOnlyList<Scene> scopedScenes, IReadOnlyList<Chapter> chapters)
|
||||
{
|
||||
if (string.Equals(filter.SceneRangeMode, "EntireProject", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(filter.SceneRangeMode, "Book", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(filter.SceneRangeMode, "Chapter", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(filter.SceneRangeMode, "Custom", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var showBookContext = ShouldShowBookContext(filter, scopedScenes, chapters);
|
||||
return showBookContext || scopedScenes.Select(scene => scene.ChapterID).Distinct().Count() > 1;
|
||||
}
|
||||
|
||||
private static void NormaliseFilter(ContinuityFilter filter)
|
||||
{
|
||||
if (filter.ProjectID <= 0)
|
||||
@ -2751,7 +2840,17 @@ public sealed class StoryStateService(
|
||||
IReadOnlyList<Scene> ScopedScenes,
|
||||
IReadOnlySet<int> AllowedSceneIds,
|
||||
IReadOnlyDictionary<int, int> SceneIndexes,
|
||||
IReadOnlyDictionary<int, string> SceneLabels);
|
||||
IReadOnlyDictionary<int, string> SceneLabels,
|
||||
IReadOnlyDictionary<int, SceneContext> SceneContexts);
|
||||
|
||||
private sealed record SceneContext(
|
||||
int BookID,
|
||||
string BookDisplayName,
|
||||
int ChapterID,
|
||||
string ChapterDisplayName,
|
||||
int SceneID,
|
||||
string SceneTitle,
|
||||
string SceneDisplayName);
|
||||
}
|
||||
|
||||
public sealed class PlotService(IProjectRepository projects, IBookRepository books, IPlotRepository plots, IProjectActivityService activity) : IPlotService
|
||||
|
||||
@ -307,14 +307,22 @@ public sealed class ContinuityExplorerViewModel
|
||||
public IReadOnlyList<SelectListItem> CharacterOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> AssetOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> LocationOptions { get; set; } = [];
|
||||
public bool ShowBookContext { get; set; }
|
||||
public bool ShowChapterContext { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CharacterJourneyViewModel
|
||||
{
|
||||
public int CharacterID { get; set; }
|
||||
public string CharacterName { get; set; } = string.Empty;
|
||||
public int BookID { get; set; }
|
||||
public string BookDisplayName { get; set; } = string.Empty;
|
||||
public int ChapterID { get; set; }
|
||||
public string ChapterDisplayName { get; set; } = string.Empty;
|
||||
public int SceneID { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
public string SceneLabel { get; set; } = string.Empty;
|
||||
public string SceneDisplayName { get; set; } = string.Empty;
|
||||
public string Location { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@ -322,8 +330,14 @@ public sealed class AssetJourneyViewModel
|
||||
{
|
||||
public int StoryAssetID { get; set; }
|
||||
public string AssetName { get; set; } = string.Empty;
|
||||
public int BookID { get; set; }
|
||||
public string BookDisplayName { get; set; } = string.Empty;
|
||||
public int ChapterID { get; set; }
|
||||
public string ChapterDisplayName { get; set; } = string.Empty;
|
||||
public int SceneID { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
public string SceneLabel { get; set; } = string.Empty;
|
||||
public string SceneDisplayName { get; set; } = string.Empty;
|
||||
public string HolderOrLocation { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@ -331,8 +345,14 @@ public sealed class LocationActivityViewModel
|
||||
{
|
||||
public int LocationID { get; set; }
|
||||
public string LocationName { get; set; } = string.Empty;
|
||||
public int BookID { get; set; }
|
||||
public string BookDisplayName { get; set; } = string.Empty;
|
||||
public int ChapterID { get; set; }
|
||||
public string ChapterDisplayName { get; set; } = string.Empty;
|
||||
public int SceneID { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
public string SceneLabel { get; set; } = string.Empty;
|
||||
public string SceneDisplayName { get; set; } = string.Empty;
|
||||
public string CharactersPresent { get; set; } = string.Empty;
|
||||
public string AssetsPresent { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@ -245,11 +245,35 @@ else if (Model.Filter.DisplayMode == "Journey")
|
||||
<h3>@group.Key.CharacterName</h3>
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>Scene</th><th>Location</th></tr></thead>
|
||||
<thead>
|
||||
<tr>
|
||||
@if (Model.ShowBookContext)
|
||||
{
|
||||
<th>Book</th>
|
||||
}
|
||||
@if (Model.ShowChapterContext)
|
||||
{
|
||||
<th>Chapter</th>
|
||||
}
|
||||
<th>Scene</th>
|
||||
<th>Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var row in group)
|
||||
{
|
||||
<tr><td>@row.SceneLabel</td><td>@row.Location</td></tr>
|
||||
<tr>
|
||||
@if (Model.ShowBookContext)
|
||||
{
|
||||
<td>@row.BookDisplayName</td>
|
||||
}
|
||||
@if (Model.ShowChapterContext)
|
||||
{
|
||||
<td>@row.ChapterDisplayName</td>
|
||||
}
|
||||
<td>@row.SceneDisplayName</td>
|
||||
<td>@row.Location</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -261,11 +285,35 @@ else if (Model.Filter.DisplayMode == "Journey")
|
||||
<h3>@group.Key.AssetName</h3>
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>Scene</th><th>Holder / Location</th></tr></thead>
|
||||
<thead>
|
||||
<tr>
|
||||
@if (Model.ShowBookContext)
|
||||
{
|
||||
<th>Book</th>
|
||||
}
|
||||
@if (Model.ShowChapterContext)
|
||||
{
|
||||
<th>Chapter</th>
|
||||
}
|
||||
<th>Scene</th>
|
||||
<th>Holder / Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var row in group)
|
||||
{
|
||||
<tr><td>@row.SceneLabel</td><td>@row.HolderOrLocation</td></tr>
|
||||
<tr>
|
||||
@if (Model.ShowBookContext)
|
||||
{
|
||||
<td>@row.BookDisplayName</td>
|
||||
}
|
||||
@if (Model.ShowChapterContext)
|
||||
{
|
||||
<td>@row.ChapterDisplayName</td>
|
||||
}
|
||||
<td>@row.SceneDisplayName</td>
|
||||
<td>@row.HolderOrLocation</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -286,11 +334,39 @@ else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>Location</th><th>Scene</th><th>Characters Present</th><th>Assets Present</th></tr></thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Location</th>
|
||||
@if (Model.ShowBookContext)
|
||||
{
|
||||
<th>Book</th>
|
||||
}
|
||||
@if (Model.ShowChapterContext)
|
||||
{
|
||||
<th>Chapter</th>
|
||||
}
|
||||
<th>Scene</th>
|
||||
<th>Characters Present</th>
|
||||
<th>Assets Present</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var row in Model.LocationActivity)
|
||||
{
|
||||
<tr><td>@row.LocationName</td><td>@row.SceneLabel</td><td>@row.CharactersPresent</td><td>@row.AssetsPresent</td></tr>
|
||||
<tr>
|
||||
<td>@row.LocationName</td>
|
||||
@if (Model.ShowBookContext)
|
||||
{
|
||||
<td>@row.BookDisplayName</td>
|
||||
}
|
||||
@if (Model.ShowChapterContext)
|
||||
{
|
||||
<td>@row.ChapterDisplayName</td>
|
||||
}
|
||||
<td>@row.SceneDisplayName</td>
|
||||
<td>@row.CharactersPresent</td>
|
||||
<td>@row.AssetsPresent</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user