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,
|
Project = project,
|
||||||
Filter = filter,
|
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(),
|
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(),
|
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(),
|
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;
|
previousLocationId = row.LocationID;
|
||||||
|
var sceneContext = context.SceneContexts.GetValueOrDefault(row.SceneID);
|
||||||
result.Add(new CharacterJourneyViewModel
|
result.Add(new CharacterJourneyViewModel
|
||||||
{
|
{
|
||||||
CharacterID = character.CharacterID,
|
CharacterID = character.CharacterID,
|
||||||
CharacterName = character.CharacterName,
|
CharacterName = character.CharacterName,
|
||||||
|
BookID = sceneContext?.BookID ?? 0,
|
||||||
|
BookDisplayName = sceneContext?.BookDisplayName ?? string.Empty,
|
||||||
|
ChapterID = sceneContext?.ChapterID ?? 0,
|
||||||
|
ChapterDisplayName = sceneContext?.ChapterDisplayName ?? string.Empty,
|
||||||
SceneID = row.SceneID,
|
SceneID = row.SceneID,
|
||||||
|
SceneTitle = sceneContext?.SceneTitle ?? string.Empty,
|
||||||
SceneLabel = context.SceneLabels.GetValueOrDefault(row.SceneID, $"Scene {row.SceneNumber:g}"),
|
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"
|
Location = row.LocationPath ?? row.LocationName ?? "Unknown"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2408,12 +2417,19 @@ public sealed class StoryStateService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
previousLabel = row.Label;
|
previousLabel = row.Label;
|
||||||
|
var sceneContext = context.SceneContexts.GetValueOrDefault(row.SceneID);
|
||||||
result.Add(new AssetJourneyViewModel
|
result.Add(new AssetJourneyViewModel
|
||||||
{
|
{
|
||||||
StoryAssetID = asset.StoryAssetID,
|
StoryAssetID = asset.StoryAssetID,
|
||||||
AssetName = asset.AssetName,
|
AssetName = asset.AssetName,
|
||||||
|
BookID = sceneContext?.BookID ?? 0,
|
||||||
|
BookDisplayName = sceneContext?.BookDisplayName ?? string.Empty,
|
||||||
|
ChapterID = sceneContext?.ChapterID ?? 0,
|
||||||
|
ChapterDisplayName = sceneContext?.ChapterDisplayName ?? string.Empty,
|
||||||
SceneID = row.SceneID,
|
SceneID = row.SceneID,
|
||||||
|
SceneTitle = sceneContext?.SceneTitle ?? string.Empty,
|
||||||
SceneLabel = context.SceneLabels.GetValueOrDefault(row.SceneID, "Unknown scene"),
|
SceneLabel = context.SceneLabels.GetValueOrDefault(row.SceneID, "Unknown scene"),
|
||||||
|
SceneDisplayName = sceneContext?.SceneDisplayName ?? context.SceneLabels.GetValueOrDefault(row.SceneID, "Unknown scene"),
|
||||||
HolderOrLocation = row.Label
|
HolderOrLocation = row.Label
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2461,12 +2477,19 @@ public sealed class StoryStateService(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sceneContext = context.SceneContexts.GetValueOrDefault(scene.SceneID);
|
||||||
result.Add(new LocationActivityViewModel
|
result.Add(new LocationActivityViewModel
|
||||||
{
|
{
|
||||||
LocationID = location.LocationID,
|
LocationID = location.LocationID,
|
||||||
LocationName = location.LocationPath,
|
LocationName = location.LocationPath,
|
||||||
|
BookID = sceneContext?.BookID ?? 0,
|
||||||
|
BookDisplayName = sceneContext?.BookDisplayName ?? string.Empty,
|
||||||
|
ChapterID = sceneContext?.ChapterID ?? 0,
|
||||||
|
ChapterDisplayName = sceneContext?.ChapterDisplayName ?? string.Empty,
|
||||||
SceneID = scene.SceneID,
|
SceneID = scene.SceneID,
|
||||||
|
SceneTitle = sceneContext?.SceneTitle ?? scene.SceneTitle,
|
||||||
SceneLabel = SceneLabel(scene.SceneNumber, scene.SceneTitle),
|
SceneLabel = SceneLabel(scene.SceneNumber, scene.SceneTitle),
|
||||||
|
SceneDisplayName = sceneContext?.SceneDisplayName ?? SceneLabel(scene.SceneNumber, scene.SceneTitle),
|
||||||
CharactersPresent = sceneCharacters.Any() ? string.Join(", ", sceneCharacters) : "None",
|
CharactersPresent = sceneCharacters.Any() ? string.Join(", ", sceneCharacters) : "None",
|
||||||
AssetsPresent = sceneAssets.Any() ? string.Join(", ", sceneAssets) : "None"
|
AssetsPresent = sceneAssets.Any() ? string.Join(", ", sceneAssets) : "None"
|
||||||
});
|
});
|
||||||
@ -2485,7 +2508,8 @@ public sealed class StoryStateService(
|
|||||||
scopedScenes,
|
scopedScenes,
|
||||||
scopedScenes.Select(x => x.SceneID).ToHashSet(),
|
scopedScenes.Select(x => x.SceneID).ToHashSet(),
|
||||||
BuildSceneIndexes(orderedScenes),
|
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)
|
private static IEnumerable<Scene> OrderScenes(TimelineData timeline)
|
||||||
@ -2709,6 +2733,71 @@ public sealed class StoryStateService(
|
|||||||
.ToDictionary(x => x.SceneID, x => x.Index);
|
.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)
|
private static void NormaliseFilter(ContinuityFilter filter)
|
||||||
{
|
{
|
||||||
if (filter.ProjectID <= 0)
|
if (filter.ProjectID <= 0)
|
||||||
@ -2751,7 +2840,17 @@ public sealed class StoryStateService(
|
|||||||
IReadOnlyList<Scene> ScopedScenes,
|
IReadOnlyList<Scene> ScopedScenes,
|
||||||
IReadOnlySet<int> AllowedSceneIds,
|
IReadOnlySet<int> AllowedSceneIds,
|
||||||
IReadOnlyDictionary<int, int> SceneIndexes,
|
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
|
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> CharacterOptions { get; set; } = [];
|
||||||
public IReadOnlyList<SelectListItem> AssetOptions { get; set; } = [];
|
public IReadOnlyList<SelectListItem> AssetOptions { get; set; } = [];
|
||||||
public IReadOnlyList<SelectListItem> LocationOptions { get; set; } = [];
|
public IReadOnlyList<SelectListItem> LocationOptions { get; set; } = [];
|
||||||
|
public bool ShowBookContext { get; set; }
|
||||||
|
public bool ShowChapterContext { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CharacterJourneyViewModel
|
public sealed class CharacterJourneyViewModel
|
||||||
{
|
{
|
||||||
public int CharacterID { get; set; }
|
public int CharacterID { get; set; }
|
||||||
public string CharacterName { get; set; } = string.Empty;
|
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 int SceneID { get; set; }
|
||||||
|
public string SceneTitle { get; set; } = string.Empty;
|
||||||
public string SceneLabel { 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;
|
public string Location { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,8 +330,14 @@ public sealed class AssetJourneyViewModel
|
|||||||
{
|
{
|
||||||
public int StoryAssetID { get; set; }
|
public int StoryAssetID { get; set; }
|
||||||
public string AssetName { get; set; } = string.Empty;
|
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 int SceneID { get; set; }
|
||||||
|
public string SceneTitle { get; set; } = string.Empty;
|
||||||
public string SceneLabel { 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;
|
public string HolderOrLocation { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,8 +345,14 @@ public sealed class LocationActivityViewModel
|
|||||||
{
|
{
|
||||||
public int LocationID { get; set; }
|
public int LocationID { get; set; }
|
||||||
public string LocationName { get; set; } = string.Empty;
|
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 int SceneID { get; set; }
|
||||||
|
public string SceneTitle { get; set; } = string.Empty;
|
||||||
public string SceneLabel { 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 CharactersPresent { get; set; } = string.Empty;
|
||||||
public string AssetsPresent { 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>
|
<h3>@group.Key.CharacterName</h3>
|
||||||
<div class="table-responsive mb-3">
|
<div class="table-responsive mb-3">
|
||||||
<table class="table table-sm align-middle">
|
<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>
|
<tbody>
|
||||||
@foreach (var row in group)
|
@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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -261,11 +285,35 @@ else if (Model.Filter.DisplayMode == "Journey")
|
|||||||
<h3>@group.Key.AssetName</h3>
|
<h3>@group.Key.AssetName</h3>
|
||||||
<div class="table-responsive mb-3">
|
<div class="table-responsive mb-3">
|
||||||
<table class="table table-sm align-middle">
|
<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>
|
<tbody>
|
||||||
@foreach (var row in group)
|
@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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -286,11 +334,39 @@ else
|
|||||||
{
|
{
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm align-middle">
|
<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>
|
<tbody>
|
||||||
@foreach (var row in Model.LocationActivity)
|
@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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user