@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); }
Workspace context @Model.Project.ProjectName@if (selectedBook is not null) { / @selectedBook.BookTitle }

Story timeline

@Model.Project.ProjectName

@if (Model.SelectedBookID.HasValue) {
} Warnings Story health
Filters and focus
Clear filters
Visibility
@if (Model.ViewPresets.Any()) {
@foreach (var preset in Model.ViewPresets) { @preset.PresetName
}
}
@if (!string.IsNullOrWhiteSpace(Model.FocusLabel)) {
Focus: @Model.FocusLabel @Model.FocusSceneIDs.Count matching scene@(Model.FocusSceneIDs.Count == 1 ? "" : "s") Clear focus
} @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 (!chapter.Scenes.Any()) {
No scenes
} else { @foreach (var scene in chapter.Scenes) { var selectedClass = scene.SceneID == Model.SelectedSceneID ? "selected" : ""; var matchClass = Model.MatchingSceneIDs.Contains(scene.SceneID) ? "filter-match" : "filter-dim"; var focusClass = Model.FocusSceneIDs.Contains(scene.SceneID) ? "focus-match" : (Model.FocusSceneIDs.Any() ? "focus-dim" : ""); Scene @scene.SceneNumber @scene.SceneTitle
@scene.RevisionStatusName @if (scene.WarningCount > 0) { @scene.WarningCount warning@(scene.WarningCount == 1 ? "" : "s") } else { Warnings 0 } @if (scene.DependencyCount > 0) { @scene.DependencyCount dep@(scene.DependencyCount == 1 ? "" : "s") }
@scene.TimeLabel @if (!string.IsNullOrWhiteSpace(scene.PrimaryLocationName)) { @scene.PrimaryLocationName } @if (scene.PurposeLabels.Any()) {
@foreach (var purpose in scene.PurposeLabels.Take(3)) { @purpose.PurposeName }
} @scene.Summary
} }
}
} @if (Model.PlotLanes.Any() && Model.OrderedScenes.Any()) {
Plot line lanes Thread event markers by scene
@foreach (var lane in Model.PlotLanes) { 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 lane in Model.AssetLanes) { var assetFocusClass = Model.Filter.FocusType == "asset" && Model.Filter.FocusID.HasValue && Model.Filter.FocusID != lane.Asset.StoryAssetID ? "lane-dim" : "";
@lane.Asset.AssetName @lane.Asset.KindName / @lane.Asset.Importance
@foreach (var slot in lane.SceneSlots) {
@foreach (var assetEvent in slot.Events) { @assetEvent.MarkerText }
}
}
} 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 lane in Model.CharacterLanes) { 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.
}

Pacing

Metric shape

@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) {
@graph.MetricName
@foreach (var point in graph.Points) { @point.Value }
}
}
} @section Scripts { }