@model TimelineViewModel
@{
ViewData["Title"] = "Timeline";
ViewData["ShellClass"] = "plotline-shell-wide";
var selectedBook = Model.Books.FirstOrDefault(x => x.Book.BookID == Model.SelectedBookID)?.Book;
var timelineMetricScenes = Model.Books
.SelectMany(book => book.Chapters.SelectMany(chapter => chapter.Scenes.Select(scene => new
{
sceneId = scene.SceneID,
axisLabel = $"{chapter.Chapter.ChapterNumber:g}.{scene.SceneNumber:g}",
chapterLabel = $"Chapter {chapter.Chapter.ChapterNumber:g}",
sceneNumber = scene.SceneNumber.ToString("g"),
sceneTitle = scene.SceneTitle,
bookTitle = book.Book.BookTitle,
timelineUrl = Url.Action("Index", "Timeline", new { projectId = Model.Project.ProjectID, bookId = book.Book.BookID, selectedSceneId = scene.SceneID })
})))
.ToList();
var timelineMetricShapePayload = new
{
scenes = timelineMetricScenes,
metrics = Model.MetricGraphs.Select(graph => new
{
metricName = graph.MetricName,
points = graph.Points.Select(point => new { sceneId = point.SceneID, value = point.Value })
})
};
var hasActiveFilter = Model.MatchingSceneIDs.Count != Model.OrderedScenes.Count || !string.IsNullOrWhiteSpace(Model.FocusLabel);
var warningScenes = Model.OrderedScenes.Where(x => x.WarningCount > 0).ToList();
}
Story timeline
@Model.Project.ProjectName
@if (!string.IsNullOrWhiteSpace(Model.FocusLabel))
{
Focus: @Model.FocusLabel
@Model.FocusSceneIDs.Count matching scene@(Model.FocusSceneIDs.Count == 1 ? "" : "s")
Clear focus
}
@if (Model.Books.Any())
{
@foreach (var book in Model.Books)
{
@foreach (var chapter in book.Chapters)
{
var chapterWarnings = chapter.Scenes.Sum(x => x.WarningCount);
Ch @chapter.Chapter.ChapterNumber
}
}
}
@if (!Model.Books.Any())
{
No timeline yet
Add books, chapters and scenes to see them arranged here in narrative order.
}
else
{
@foreach (var book in Model.Books)
{
Book @book.Book.BookNumber: @book.Book.BookTitle
@foreach (var chapter in book.Chapters)
{
Chapter @chapter.Chapter.ChapterNumber
@chapter.Chapter.ChapterTitle
}
}
@if (Model.PlotLanes.Any() && Model.OrderedScenes.Any())
{
Plot line lanes
Thread event markers by scene
@foreach (var group in Model.PlotLanes.GroupBy(x => x.PlotLine.PlotLineTypeName).OrderBy(x => x.Key))
{
@group.Key
@foreach (var lane in Model.PlotLanes)
{
if (lane.PlotLine.PlotLineTypeName != group.Key) { continue; }
var laneFocusClass = Model.Filter.FocusType == "plotline" && Model.Filter.FocusID.HasValue && Model.Filter.FocusID != lane.PlotLine.PlotLineID ? "lane-dim" : "";
@lane.PlotLine.PlotLineName
@foreach (var slot in lane.SceneSlots)
{
@foreach (var threadEvent in slot.Events)
{
var markerClass = threadEvent.EventTypeName is "Resolved" or "Payoff"
? "resolved"
: threadEvent.EventTypeName is "Contradicted"
? "contradicted"
: "";
@threadEvent.MarkerText
}
}
}
}
}
else
{
Add visible plot lines and thread events to see narrative lanes here.
}
@if (Model.AssetLanes.Any() && Model.OrderedScenes.Any())
{
Story asset lanes
Asset event markers by scene
@foreach (var group in Model.AssetLanes.GroupBy(x => x.Asset.Importance >= 8 ? "High importance" : "Other assets"))
{
@group.Key
@foreach (var lane in Model.AssetLanes)
{
if ((lane.Asset.Importance >= 8 ? "High importance" : "Other assets") != group.Key) { continue; }
var assetFocusClass = Model.Filter.FocusType == "asset" && Model.Filter.FocusID.HasValue && Model.Filter.FocusID != lane.Asset.StoryAssetID ? "lane-dim" : "";
}
}
}
else
{
Add story assets and asset events to see asset lanes here.
}
@if (Model.CharacterLanes.Any() && Model.OrderedScenes.Any())
{
Character appearances
First twelve project characters
@foreach (var group in Model.CharacterLanes.Select((lane, index) => new { lane, group = index < 6 ? "Major characters" : "Secondary characters" }).GroupBy(x => x.group))
{
@group.Key
@foreach (var laneItem in group)
{
var lane = laneItem.lane;
var characterFocusClass = Model.Filter.FocusType == "character" && Model.Filter.FocusID.HasValue && Model.Filter.FocusID != lane.Character.CharacterID ? "lane-dim" : "";
}
}
}
else
{
Add characters to scenes to see character appearance lanes here.
}
@Model.OrderedScenes.Count scene@(Model.OrderedScenes.Count == 1 ? "" : "s")
@if (!Model.MetricGraphs.Any() || !Model.OrderedScenes.Any())
{
Add scenes to begin seeing emotional and pacing shape.
}
else
{
Compare selected metrics across the same scene timeline. Click a point to focus that scene.
Metric chart could not load. The raw metric rows are still available below.
Show raw metric rows
@foreach (var graph in Model.MetricGraphs)
{
}
}
@if (Model.SelectedScene is null)
{
Scene inspector
Select a scene
Click a scene card to edit its summary, timing, purposes, outcomes and metric sliders without leaving the timeline.
}
else
{
}
}
@section Scripts {
}