410 lines
20 KiB
Plaintext
410 lines
20 KiB
Plaintext
@model StoryBibleViewModel
|
|
@{
|
|
ViewData["Title"] = $"Story Bible - {Model.Project.ProjectName}";
|
|
var timelineByBook = Model.Timeline.GroupBy(x => new { x.BookID, x.BookTitle });
|
|
var relationshipsByCharacter = Model.Relationships.GroupBy(x => new { x.CharacterAID, x.CharacterAName });
|
|
var revisionByGroup = Model.RevisionItems.GroupBy(x => x.GroupName);
|
|
}
|
|
|
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
|
<span>Story Bible</span>
|
|
</nav>
|
|
|
|
<section class="context-header">
|
|
<div class="context-title">
|
|
<span class="eyebrow">Project workspace</span>
|
|
<strong>@Model.Project.ProjectName</strong>
|
|
</div>
|
|
<nav class="context-nav" aria-label="Project navigation">
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
|
<a class="active" asp-controller="StoryBible" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story Bible</a>
|
|
<a asp-controller="RelationshipMap" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Relationship Map</a>
|
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
|
</nav>
|
|
</section>
|
|
|
|
<div class="page-heading story-bible-heading">
|
|
<div>
|
|
<p class="eyebrow">Reference</p>
|
|
<h1>Story Bible <help-icon key="storyBible.overview" /></h1>
|
|
<p class="lead-text">A project-level reference for people, threads, places, assets, continuity, and revision attention.</p>
|
|
</div>
|
|
<form class="story-bible-search" asp-action="Index" method="get">
|
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
|
<input class="form-control" name="q" value="@Model.Query" placeholder="Search story bible" />
|
|
<button class="btn btn-primary" type="submit">Search</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="button-row export-action-row">
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="Exports" asp-action="StoryBible" asp-route-projectId="@Model.Project.ProjectID">Printable Story Bible</a>
|
|
<a class="btn btn-outline-secondary btn-sm" asp-controller="Exports" asp-action="StoryBible" asp-route-projectId="@Model.Project.ProjectID" asp-route-format="markdown">Markdown</a>
|
|
<a class="btn btn-outline-secondary btn-sm" asp-controller="Exports" asp-action="StoryBible" asp-route-projectId="@Model.Project.ProjectID" asp-route-format="text">Text</a>
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="Exports" asp-action="NarrativeTimeline" asp-route-projectId="@Model.Project.ProjectID">Timeline export</a>
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="Exports" asp-action="ResearchPack" asp-route-projectId="@Model.Project.ProjectID">Research pack</a>
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="RelationshipMap" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Open Relationship Map</a>
|
|
</div>
|
|
|
|
<nav class="story-bible-nav" aria-label="Story Bible sections">
|
|
<a href="#characters">Characters</a>
|
|
<a href="#threads">Plot Threads</a>
|
|
<a href="#assets">Story Assets</a>
|
|
<a href="#relationships">Relationships</a>
|
|
<a href="#locations">Locations</a>
|
|
<a href="#timeline-summary">Timeline Summary</a>
|
|
<a href="#attention">Things that may need attention</a>
|
|
<a href="#revision">Revision Status</a>
|
|
</nav>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(Model.Query))
|
|
{
|
|
<section class="story-bible-section" id="search-results">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Search</p>
|
|
<h2>Results for "@Model.Query"</h2>
|
|
</div>
|
|
<a class="btn btn-outline-secondary btn-sm" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Clear search</a>
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="Exports" asp-action="Search" asp-route-projectId="@Model.Project.ProjectID" asp-route-q="@Model.Query" asp-route-format="markdown">Export results</a>
|
|
</div>
|
|
@if (!Model.SearchResults.Any())
|
|
{
|
|
<p class="muted">No matching story bible entries. Try a character name, location, asset, plot thread, or scene title.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="story-bible-search-results">
|
|
@foreach (var group in Model.SearchResults.GroupBy(x => x.ResultType))
|
|
{
|
|
<article class="story-bible-card">
|
|
<h3>@group.Key</h3>
|
|
<ul class="story-bible-link-list">
|
|
@foreach (var result in group)
|
|
{
|
|
<li>
|
|
<a href="@StoryBibleLink(result, Model.Project.ProjectID)">@result.Title</a>
|
|
@if (!string.IsNullOrWhiteSpace(result.Detail))
|
|
{
|
|
<span>@result.Detail</span>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
</article>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
}
|
|
|
|
<section class="story-bible-section" id="characters">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">People</p>
|
|
<h2>Characters</h2>
|
|
</div>
|
|
<span class="soft-count">@Model.Characters.Count characters</span>
|
|
</div>
|
|
<div class="story-bible-grid">
|
|
@foreach (var character in Model.Characters)
|
|
{
|
|
<article class="story-bible-card">
|
|
<div class="story-bible-card-title">
|
|
<h3>@character.CharacterName</h3>
|
|
<span>@character.SceneCount scenes</span>
|
|
</div>
|
|
<p>@(character.Description ?? "No profile description yet.")</p>
|
|
<div class="story-bible-meta">
|
|
@if (character.AgeAtSeriesStart.HasValue) { <span>Age @character.AgeAtSeriesStart</span> }
|
|
@if (!string.IsNullOrWhiteSpace(character.EyeColour)) { <span>@character.EyeColour eyes</span> }
|
|
@if (!string.IsNullOrWhiteSpace(character.Height)) { <span>@character.Height</span> }
|
|
<span>@character.PovSceneCount POV scenes</span>
|
|
<span>@character.RelationshipCount relationships</span>
|
|
<span>@character.KnowledgeCount knowledge notes</span>
|
|
</div>
|
|
<dl class="story-bible-facts">
|
|
<dt>First</dt><dd>@(character.FirstSceneLabel ?? "Not placed")</dd>
|
|
<dt>Last</dt><dd>@(character.LastSceneLabel ?? "Not placed")</dd>
|
|
<dt>Locations</dt><dd>@(character.LocationHistory ?? "No location history yet")</dd>
|
|
</dl>
|
|
@if (character.MissingLocationCount > 0)
|
|
{
|
|
<span class="badge text-bg-warning">@character.MissingLocationCount missing locations</span>
|
|
}
|
|
<div class="story-bible-actions">
|
|
<a asp-controller="Characters" asp-action="Details" asp-route-id="@character.CharacterID">Open profile</a>
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusCharacterId="@character.CharacterID">Show on timeline</a>
|
|
</div>
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="story-bible-section" id="threads">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Questions and promises</p>
|
|
<h2>Plot Threads</h2>
|
|
</div>
|
|
<span class="soft-count">@Model.PlotThreads.Count threads</span>
|
|
</div>
|
|
<div class="story-bible-grid">
|
|
@foreach (var thread in Model.PlotThreads)
|
|
{
|
|
<article class="story-bible-card">
|
|
<div class="story-bible-card-title">
|
|
<h3>@thread.ThreadTitle</h3>
|
|
<span>@thread.ThreadStatusName</span>
|
|
</div>
|
|
<div class="story-bible-meta">
|
|
<span>@thread.PlotLineName</span>
|
|
<span>@thread.ThreadTypeName</span>
|
|
<span>Importance @thread.Importance</span>
|
|
<span>@thread.EventCount events</span>
|
|
</div>
|
|
<dl class="story-bible-facts">
|
|
<dt>Introduced</dt><dd>@(thread.IntroducedSceneLabel ?? "Not set")</dd>
|
|
<dt>Last touched</dt><dd>@(thread.LastTouchedSceneLabel ?? "No events yet")</dd>
|
|
<dt>Planned end</dt><dd>@(thread.PlannedResolutionSceneLabel ?? "Not planned")</dd>
|
|
<dt>Actual end</dt><dd>@(thread.ActualResolutionSceneLabel ?? "Not resolved")</dd>
|
|
<dt>History</dt><dd>@(thread.EventHistory ?? "No event history yet")</dd>
|
|
</dl>
|
|
@if (thread.WarningCount > 0)
|
|
{
|
|
<span class="badge text-bg-warning">@thread.WarningCount warnings</span>
|
|
}
|
|
<div class="story-bible-actions">
|
|
<a asp-controller="PlotThreads" asp-action="Edit" asp-route-id="@thread.PlotThreadID">Edit thread</a>
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusPlotThreadId="@thread.PlotThreadID">Show on timeline</a>
|
|
</div>
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="story-bible-section" id="assets">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Objects and evidence</p>
|
|
<h2>Story Assets</h2>
|
|
</div>
|
|
<span class="soft-count">@Model.Assets.Count assets</span>
|
|
</div>
|
|
<div class="story-bible-grid">
|
|
@foreach (var asset in Model.Assets)
|
|
{
|
|
<article class="story-bible-card">
|
|
<div class="story-bible-card-title">
|
|
<h3>@asset.AssetName</h3>
|
|
<span>@(asset.IsResolved ? "Resolved" : "Open")</span>
|
|
</div>
|
|
<div class="story-bible-meta">
|
|
<span>@asset.KindName</span>
|
|
<span>@(asset.CurrentStateName ?? "No state")</span>
|
|
<span>Importance @asset.Importance</span>
|
|
<span>@asset.EventCount events</span>
|
|
</div>
|
|
<dl class="story-bible-facts">
|
|
<dt>Location</dt><dd>@(asset.CurrentLocationPath ?? "No current location")</dd>
|
|
<dt>Dependencies</dt><dd>@(asset.DependencySummary ?? "None")</dd>
|
|
<dt>Custody</dt><dd>@(asset.CustodySummary ?? "No custody history")</dd>
|
|
<dt>History</dt><dd>@(asset.EventHistory ?? "No event history yet")</dd>
|
|
</dl>
|
|
@if (asset.WarningCount > 0)
|
|
{
|
|
<span class="badge text-bg-warning">@asset.WarningCount warnings</span>
|
|
}
|
|
<div class="story-bible-actions">
|
|
<a asp-controller="StoryAssets" asp-action="Details" asp-route-id="@asset.StoryAssetID">Open asset</a>
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusAssetId="@asset.StoryAssetID">Show on timeline</a>
|
|
</div>
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="story-bible-section" id="relationships">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Character web</p>
|
|
<h2>Relationships</h2>
|
|
</div>
|
|
<span class="soft-count">@Model.Relationships.Count relationships</span>
|
|
</div>
|
|
<div class="story-bible-grid">
|
|
@foreach (var categoryGroup in Model.Relationships.GroupBy(x => new { x.RelationshipCategoryName, x.CategorySortOrder }).OrderBy(x => x.Key.CategorySortOrder).ThenBy(x => x.Key.RelationshipCategoryName))
|
|
{
|
|
<article class="story-bible-card">
|
|
<h3>@categoryGroup.Key.RelationshipCategoryName</h3>
|
|
<ul class="story-bible-link-list">
|
|
@foreach (var relationship in categoryGroup.OrderBy(x => x.CharacterAName).ThenBy(x => x.CharacterBName).ThenBy(x => x.RelationshipTypeSortOrder))
|
|
{
|
|
<li>
|
|
<a asp-controller="Characters" asp-action="Details" asp-route-id="@relationship.CharacterBID">@relationship.CharacterAName / @relationship.CharacterBName</a>
|
|
<span>@relationship.RelationshipTypeName <span class="status-pill">@relationship.RelationshipCategoryName</span></span>
|
|
<span>Initial: @(relationship.InitialRelationshipName ?? relationship.RelationshipTypeName)@if (!string.IsNullOrWhiteSpace(relationship.InitialStateName)) { <text> / @relationship.InitialStateName</text> }@if (relationship.InitialIntensity.HasValue) { <text> / Intensity @relationship.InitialIntensity</text> }</span>
|
|
<span>Current: @(relationship.CurrentStateName ?? "No current state")@if (relationship.CurrentIntensity.HasValue) { <text> / Intensity @relationship.CurrentIntensity</text> }</span>
|
|
@if (!string.IsNullOrWhiteSpace(relationship.EventHistory))
|
|
{
|
|
<span>Relationship Event History: @relationship.EventHistory</span>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
<section class="story-bible-section" id="locations">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Place</p>
|
|
<h2>Locations</h2>
|
|
</div>
|
|
<span class="soft-count">@Model.Locations.Count locations</span>
|
|
</div>
|
|
<div class="story-bible-grid story-bible-grid-compact">
|
|
@foreach (var location in Model.Locations)
|
|
{
|
|
<article class="story-bible-card">
|
|
<div class="story-bible-card-title">
|
|
<h3>@location.LocationName</h3>
|
|
<span>@(location.LocationTypeName ?? "Location")</span>
|
|
</div>
|
|
<p>@location.LocationPath</p>
|
|
<div class="story-bible-meta">
|
|
<span>@location.SceneCount scenes</span>
|
|
<span>@location.CharacterCount characters</span>
|
|
<span>@location.AssetCount assets</span>
|
|
<span>@location.RelationshipCount links</span>
|
|
</div>
|
|
@if (!string.IsNullOrWhiteSpace(location.RelatedLocations))
|
|
{
|
|
<p class="muted">Linked to @location.RelatedLocations</p>
|
|
}
|
|
<div class="story-bible-actions">
|
|
<a asp-controller="Locations" asp-action="Details" asp-route-id="@location.LocationID">Open location</a>
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusLocationId="@location.LocationID">Show on timeline</a>
|
|
</div>
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="story-bible-section" id="timeline-summary">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Narrative order</p>
|
|
<h2>Timeline Summary</h2>
|
|
</div>
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Open timeline</a>
|
|
</div>
|
|
<div class="story-bible-timeline-summary">
|
|
@foreach (var book in timelineByBook)
|
|
{
|
|
<article class="story-bible-card story-bible-wide-card">
|
|
<h3>@book.Key.BookTitle</h3>
|
|
@foreach (var chapter in book.GroupBy(x => new { x.ChapterID, x.ChapterNumber, x.ChapterTitle }))
|
|
{
|
|
<div class="story-bible-chapter-summary">
|
|
<h4>Chapter @chapter.Key.ChapterNumber: @chapter.Key.ChapterTitle</h4>
|
|
@foreach (var scene in chapter)
|
|
{
|
|
<div class="story-bible-scene-summary">
|
|
<strong>Scene @scene.SceneNumber: @scene.SceneTitle</strong>
|
|
<span>@scene.RevisionStatusName</span>
|
|
<span>POV: @(scene.PovCharacterName ?? "Not set")</span>
|
|
<span>Location: @(scene.LocationPath ?? "Not set")</span>
|
|
@if (!string.IsNullOrWhiteSpace(scene.Summary))
|
|
{
|
|
<p>@scene.Summary</p>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="story-bible-section" id="attention">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Open Questions</p>
|
|
<h2>Things that may need attention</h2>
|
|
</div>
|
|
<span class="soft-count">@Model.OpenQuestions.Count items</span>
|
|
</div>
|
|
<div class="attention-list">
|
|
@foreach (var item in Model.OpenQuestions)
|
|
{
|
|
<article>
|
|
<span class="badge text-bg-light">@item.Category</span>
|
|
<strong>@item.Title</strong>
|
|
@if (!string.IsNullOrWhiteSpace(item.Detail))
|
|
{
|
|
<p>@item.Detail</p>
|
|
}
|
|
@if (item.SceneID.HasValue)
|
|
{
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@item.SceneID">Open scene</a>
|
|
}
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="story-bible-section" id="revision">
|
|
<div class="story-bible-section-heading">
|
|
<div>
|
|
<p class="eyebrow">Revision</p>
|
|
<h2>Revision Status</h2>
|
|
</div>
|
|
</div>
|
|
<div class="story-bible-grid story-bible-grid-compact">
|
|
@foreach (var group in revisionByGroup)
|
|
{
|
|
<article class="story-bible-card">
|
|
<div class="story-bible-card-title">
|
|
<h3>@group.Key</h3>
|
|
<span>@group.Sum(x => x.ItemCount)</span>
|
|
</div>
|
|
<ul class="story-bible-link-list">
|
|
@foreach (var item in group)
|
|
{
|
|
<li>
|
|
<span>@(item.Detail ?? item.GroupName)</span>
|
|
<strong>@item.ItemCount</strong>
|
|
</li>
|
|
}
|
|
</ul>
|
|
</article>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
@functions {
|
|
private string StoryBibleLink(StoryBibleSearchResult result, int projectId)
|
|
{
|
|
return result.ResultType switch
|
|
{
|
|
"Scene" => Url.Action("Index", "Timeline", new { projectId, selectedSceneId = result.SceneID ?? result.EntityID }) ?? "#",
|
|
"Character" => Url.Action("Details", "Characters", new { id = result.EntityID }) ?? "#",
|
|
"Story Asset" => Url.Action("Details", "StoryAssets", new { id = result.EntityID }) ?? "#",
|
|
"Plot Thread" => Url.Action("Edit", "PlotThreads", new { id = result.EntityID }) ?? "#",
|
|
"Location" => Url.Action("Details", "Locations", new { id = result.EntityID }) ?? "#",
|
|
"Warning" => Url.Action("Index", "Warnings", new { projectId, sceneId = result.SceneID }) ?? "#",
|
|
_ => Url.Action("Index", "StoryBible", new { projectId }) ?? "#"
|
|
};
|
|
}
|
|
}
|
|
|