Implemented the Timeline C3 refinement.
This commit is contained in:
parent
36c214fab8
commit
e5ed45897a
@ -462,8 +462,8 @@ public sealed class ProjectTimelineSettings
|
|||||||
public bool ShowSceneCards { get; set; } = true;
|
public bool ShowSceneCards { get; set; } = true;
|
||||||
public bool ShowMetricShape { get; set; } = true;
|
public bool ShowMetricShape { get; set; } = true;
|
||||||
public bool ShowPlotLines { get; set; } = true;
|
public bool ShowPlotLines { get; set; } = true;
|
||||||
public bool ShowStoryAssets { get; set; } = true;
|
public bool ShowStoryAssets { get; set; }
|
||||||
public bool ShowCharacterAppearances { get; set; } = true;
|
public bool ShowCharacterAppearances { get; set; }
|
||||||
public bool ShowWarnings { get; set; } = true;
|
public bool ShowWarnings { get; set; } = true;
|
||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
|
|||||||
@ -1896,8 +1896,8 @@ public sealed class TimelineService(
|
|||||||
.ToList();
|
.ToList();
|
||||||
var needsPlotTimelineData = settings.ShowPlotLines || filter.PlotLineID.HasValue || filter.PlotThreadID.HasValue || filter.FocusType is "plotline" or "plotthread";
|
var needsPlotTimelineData = settings.ShowPlotLines || filter.PlotLineID.HasValue || filter.PlotThreadID.HasValue || filter.FocusType is "plotline" or "plotthread";
|
||||||
var needsMetricTimelineData = settings.ShowMetricShape && selectedMetricIds.Any();
|
var needsMetricTimelineData = settings.ShowMetricShape && selectedMetricIds.Any();
|
||||||
var needsAssetTimelineData = settings.ShowStoryAssets || filter.StoryAssetID.HasValue || filter.FocusType == "asset";
|
var needsAssetTimelineData = settings.ShowSceneCards || settings.ShowStoryAssets || filter.StoryAssetID.HasValue || filter.FocusType == "asset";
|
||||||
var needsCharacterTimelineData = settings.ShowCharacterAppearances || filter.CharacterID.HasValue || filter.FocusType == "character";
|
var needsCharacterTimelineData = settings.ShowSceneCards || settings.ShowCharacterAppearances || filter.CharacterID.HasValue || filter.FocusType == "character";
|
||||||
var needsWarnings = settings.ShowWarnings || filter.WarningSeverityID.HasValue || filter.WarningTypeID.HasValue || filter.ShowWarningsOnly;
|
var needsWarnings = settings.ShowWarnings || filter.WarningSeverityID.HasValue || filter.WarningTypeID.HasValue || filter.ShowWarningsOnly;
|
||||||
|
|
||||||
var timeline = await TimeTimelineLoadAsync(
|
var timeline = await TimeTimelineLoadAsync(
|
||||||
@ -2042,6 +2042,7 @@ public sealed class TimelineService(
|
|||||||
PlotLanes = settings.ShowPlotLines ? BuildPlotLanes(timeline, orderedScenes) : [],
|
PlotLanes = settings.ShowPlotLines ? BuildPlotLanes(timeline, orderedScenes) : [],
|
||||||
AssetLanes = settings.ShowStoryAssets ? BuildAssetLanes(assetTimeline.Assets, assetTimeline.Events, orderedScenes) : [],
|
AssetLanes = settings.ShowStoryAssets ? BuildAssetLanes(assetTimeline.Assets, assetTimeline.Events, orderedScenes) : [],
|
||||||
CharacterLanes = settings.ShowCharacterAppearances ? BuildCharacterLanes(characterTimeline.Characters, characterTimeline.Appearances, orderedScenes) : [],
|
CharacterLanes = settings.ShowCharacterAppearances ? BuildCharacterLanes(characterTimeline.Characters, characterTimeline.Appearances, orderedScenes) : [],
|
||||||
|
SceneOverviews = settings.ShowSceneCards ? BuildSceneOverviews(orderedScenes, characterTimeline.Appearances, assetTimeline.Assets, assetTimeline.Events) : new Dictionary<int, TimelineSceneOverviewViewModel>(),
|
||||||
WarningsByScene = warningsByScene,
|
WarningsByScene = warningsByScene,
|
||||||
MatchingSceneIDs = matchingSceneIds,
|
MatchingSceneIDs = matchingSceneIds,
|
||||||
FocusSceneIDs = focusSceneIds,
|
FocusSceneIDs = focusSceneIds,
|
||||||
@ -2519,6 +2520,96 @@ public sealed class TimelineService(
|
|||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyDictionary<int, TimelineSceneOverviewViewModel> BuildSceneOverviews(
|
||||||
|
IReadOnlyList<Scene> orderedScenes,
|
||||||
|
IReadOnlyList<SceneCharacter> appearances,
|
||||||
|
IReadOnlyList<StoryAsset> assets,
|
||||||
|
IReadOnlyList<AssetEvent> assetEvents)
|
||||||
|
{
|
||||||
|
var assetImportanceById = assets.ToDictionary(x => x.StoryAssetID, x => x.Importance);
|
||||||
|
var charactersByScene = appearances
|
||||||
|
.GroupBy(x => x.SceneID)
|
||||||
|
.ToDictionary(
|
||||||
|
sceneGroup => sceneGroup.Key,
|
||||||
|
sceneGroup => sceneGroup
|
||||||
|
.GroupBy(x => x.CharacterID)
|
||||||
|
.Select(characterGroup =>
|
||||||
|
{
|
||||||
|
var first = characterGroup
|
||||||
|
.OrderByDescending(x => string.Equals(x.RoleInSceneTypeName, "POV Character", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ThenBy(x => x.SceneCharacterID)
|
||||||
|
.First();
|
||||||
|
return new TimelineSceneCharacterOverviewViewModel
|
||||||
|
{
|
||||||
|
CharacterID = first.CharacterID,
|
||||||
|
CharacterName = first.CharacterName,
|
||||||
|
IsPov = string.Equals(first.RoleInSceneTypeName, "POV Character", StringComparison.OrdinalIgnoreCase)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.IsPov)
|
||||||
|
.ThenBy(x => x.CharacterName)
|
||||||
|
.ToList());
|
||||||
|
var assetsByScene = assetEvents
|
||||||
|
.GroupBy(x => x.SceneID)
|
||||||
|
.ToDictionary(
|
||||||
|
sceneGroup => sceneGroup.Key,
|
||||||
|
sceneGroup => sceneGroup
|
||||||
|
.GroupBy(x => x.StoryAssetID)
|
||||||
|
.Select(assetGroup =>
|
||||||
|
{
|
||||||
|
var first = assetGroup.OrderByDescending(x => x.UpdatedDate).First();
|
||||||
|
return new TimelineSceneAssetOverviewViewModel
|
||||||
|
{
|
||||||
|
StoryAssetID = first.StoryAssetID,
|
||||||
|
AssetName = first.AssetName,
|
||||||
|
Importance = assetImportanceById.GetValueOrDefault(first.StoryAssetID),
|
||||||
|
EventTypeName = first.AssetEventTypeName
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.Importance)
|
||||||
|
.ThenBy(x => x.AssetName)
|
||||||
|
.ToList());
|
||||||
|
var characterLocationsByScene = appearances
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.LocationPath) || !string.IsNullOrWhiteSpace(x.LocationName))
|
||||||
|
.GroupBy(x => x.SceneID)
|
||||||
|
.ToDictionary(
|
||||||
|
sceneGroup => sceneGroup.Key,
|
||||||
|
sceneGroup => sceneGroup
|
||||||
|
.Select(x => x.LocationPath ?? x.LocationName ?? string.Empty)
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(x => x)
|
||||||
|
.ToList());
|
||||||
|
|
||||||
|
return orderedScenes.ToDictionary(scene => scene.SceneID, scene =>
|
||||||
|
{
|
||||||
|
var locations = new List<string>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(scene.PrimaryLocationPath))
|
||||||
|
{
|
||||||
|
locations.Add(scene.PrimaryLocationPath);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(scene.PrimaryLocationName))
|
||||||
|
{
|
||||||
|
locations.Add(scene.PrimaryLocationName);
|
||||||
|
}
|
||||||
|
if (characterLocationsByScene.TryGetValue(scene.SceneID, out var characterLocations))
|
||||||
|
{
|
||||||
|
locations.AddRange(characterLocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TimelineSceneOverviewViewModel
|
||||||
|
{
|
||||||
|
Characters = charactersByScene.GetValueOrDefault(scene.SceneID, []),
|
||||||
|
Assets = assetsByScene.GetValueOrDefault(scene.SceneID, []),
|
||||||
|
Locations = locations
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Take(6)
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static int CharacterLanePriority(Character character, IEnumerable<SceneCharacter> appearances)
|
private static int CharacterLanePriority(Character character, IEnumerable<SceneCharacter> appearances)
|
||||||
{
|
{
|
||||||
var description = character.DefaultDescription ?? string.Empty;
|
var description = character.DefaultDescription ?? string.Empty;
|
||||||
|
|||||||
61
PlotLine/Sql/070_TimelineDefaultSceneFocusedView.sql
Normal file
61
PlotLine/Sql/070_TimelineDefaultSceneFocusedView.sql
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
DECLARE @constraintName sysname;
|
||||||
|
|
||||||
|
SELECT @constraintName = dc.name
|
||||||
|
FROM sys.default_constraints dc
|
||||||
|
INNER JOIN sys.columns c ON c.default_object_id = dc.object_id
|
||||||
|
INNER JOIN sys.tables t ON t.object_id = c.object_id
|
||||||
|
INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
|
WHERE s.name = N'dbo'
|
||||||
|
AND t.name = N'ProjectTimelineSettings'
|
||||||
|
AND c.name = N'ShowStoryAssets';
|
||||||
|
|
||||||
|
IF @constraintName IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
EXEC(N'ALTER TABLE dbo.ProjectTimelineSettings DROP CONSTRAINT ' + QUOTENAME(@constraintName));
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.ProjectTimelineSettings', N'ShowStoryAssets') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.ProjectTimelineSettings
|
||||||
|
ADD CONSTRAINT DF_ProjectTimelineSettings_ShowStoryAssets DEFAULT 0 FOR ShowStoryAssets;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
DECLARE @constraintName sysname;
|
||||||
|
|
||||||
|
SELECT @constraintName = dc.name
|
||||||
|
FROM sys.default_constraints dc
|
||||||
|
INNER JOIN sys.columns c ON c.default_object_id = dc.object_id
|
||||||
|
INNER JOIN sys.tables t ON t.object_id = c.object_id
|
||||||
|
INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
|
WHERE s.name = N'dbo'
|
||||||
|
AND t.name = N'ProjectTimelineSettings'
|
||||||
|
AND c.name = N'ShowCharacterAppearances';
|
||||||
|
|
||||||
|
IF @constraintName IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
EXEC(N'ALTER TABLE dbo.ProjectTimelineSettings DROP CONSTRAINT ' + QUOTENAME(@constraintName));
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.ProjectTimelineSettings', N'ShowCharacterAppearances') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.ProjectTimelineSettings
|
||||||
|
ADD CONSTRAINT DF_ProjectTimelineSettings_ShowCharacterAppearances DEFAULT 0 FOR ShowCharacterAppearances;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ProjectTimelineSettings_EnsureDefaults
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.ProjectTimelineSettings WHERE ProjectID = @ProjectID)
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.ProjectTimelineSettings
|
||||||
|
(ProjectID, ShowSceneCards, ShowMetricShape, ShowPlotLines, ShowStoryAssets, ShowCharacterAppearances, ShowWarnings)
|
||||||
|
VALUES
|
||||||
|
(@ProjectID, 1, 1, 1, 0, 0, 1);
|
||||||
|
END
|
||||||
|
END;
|
||||||
|
GO
|
||||||
@ -963,6 +963,7 @@ public sealed class TimelineViewModel
|
|||||||
public IReadOnlyList<PlotLineLaneViewModel> PlotLanes { get; set; } = [];
|
public IReadOnlyList<PlotLineLaneViewModel> PlotLanes { get; set; } = [];
|
||||||
public IReadOnlyList<AssetLaneViewModel> AssetLanes { get; set; } = [];
|
public IReadOnlyList<AssetLaneViewModel> AssetLanes { get; set; } = [];
|
||||||
public IReadOnlyList<CharacterLaneViewModel> CharacterLanes { get; set; } = [];
|
public IReadOnlyList<CharacterLaneViewModel> CharacterLanes { get; set; } = [];
|
||||||
|
public IReadOnlyDictionary<int, TimelineSceneOverviewViewModel> SceneOverviews { get; set; } = new Dictionary<int, TimelineSceneOverviewViewModel>();
|
||||||
public IReadOnlyDictionary<int, IReadOnlyList<ContinuityWarning>> WarningsByScene { get; set; } = new Dictionary<int, IReadOnlyList<ContinuityWarning>>();
|
public IReadOnlyDictionary<int, IReadOnlyList<ContinuityWarning>> WarningsByScene { get; set; } = new Dictionary<int, IReadOnlyList<ContinuityWarning>>();
|
||||||
public IReadOnlySet<int> MatchingSceneIDs { get; set; } = new HashSet<int>();
|
public IReadOnlySet<int> MatchingSceneIDs { get; set; } = new HashSet<int>();
|
||||||
public IReadOnlySet<int> FocusSceneIDs { get; set; } = new HashSet<int>();
|
public IReadOnlySet<int> FocusSceneIDs { get; set; } = new HashSet<int>();
|
||||||
@ -978,8 +979,8 @@ public sealed class TimelineSettingsViewModel
|
|||||||
public bool ShowSceneCards { get; set; } = true;
|
public bool ShowSceneCards { get; set; } = true;
|
||||||
public bool ShowMetricShape { get; set; } = true;
|
public bool ShowMetricShape { get; set; } = true;
|
||||||
public bool ShowPlotLines { get; set; } = true;
|
public bool ShowPlotLines { get; set; } = true;
|
||||||
public bool ShowStoryAssets { get; set; } = true;
|
public bool ShowStoryAssets { get; set; }
|
||||||
public bool ShowCharacterAppearances { get; set; } = true;
|
public bool ShowCharacterAppearances { get; set; }
|
||||||
public bool ShowWarnings { get; set; } = true;
|
public bool ShowWarnings { get; set; } = true;
|
||||||
public List<int> SelectedMetricTypeIDs { get; set; } = [];
|
public List<int> SelectedMetricTypeIDs { get; set; } = [];
|
||||||
public IReadOnlyList<TimelineMetricSettingOptionViewModel> MetricOptions { get; set; } = [];
|
public IReadOnlyList<TimelineMetricSettingOptionViewModel> MetricOptions { get; set; } = [];
|
||||||
@ -994,6 +995,28 @@ public sealed class TimelineMetricSettingOptionViewModel
|
|||||||
public bool IsSelected { get; set; }
|
public bool IsSelected { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class TimelineSceneOverviewViewModel
|
||||||
|
{
|
||||||
|
public IReadOnlyList<TimelineSceneCharacterOverviewViewModel> Characters { get; set; } = [];
|
||||||
|
public IReadOnlyList<TimelineSceneAssetOverviewViewModel> Assets { get; set; } = [];
|
||||||
|
public IReadOnlyList<string> Locations { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class TimelineSceneCharacterOverviewViewModel
|
||||||
|
{
|
||||||
|
public int CharacterID { get; set; }
|
||||||
|
public string CharacterName { get; set; } = string.Empty;
|
||||||
|
public bool IsPov { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class TimelineSceneAssetOverviewViewModel
|
||||||
|
{
|
||||||
|
public int StoryAssetID { get; set; }
|
||||||
|
public string AssetName { get; set; } = string.Empty;
|
||||||
|
public int Importance { get; set; }
|
||||||
|
public string? EventTypeName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class ProjectSceneMetricsViewModel
|
public sealed class ProjectSceneMetricsViewModel
|
||||||
{
|
{
|
||||||
public Project Project { get; set; } = new();
|
public Project Project { get; set; } = new();
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
int BookSceneColumns(BookTimelineViewModel book) => Math.Max(book.Chapters.Any() ? book.Chapters.Sum(ChapterSceneColumns) : 1, 1);
|
int BookSceneColumns(BookTimelineViewModel book) => Math.Max(book.Chapters.Any() ? book.Chapters.Sum(ChapterSceneColumns) : 1, 1);
|
||||||
var hasSelectedScene = Model.SelectedScene is not null;
|
var hasSelectedScene = Model.SelectedScene is not null;
|
||||||
int CharacterAppearanceCount(CharacterLaneViewModel lane) => lane.SceneSlots.Sum(slot => slot.Appearances.Count);
|
int CharacterAppearanceCount(CharacterLaneViewModel lane) => lane.SceneSlots.Sum(slot => slot.Appearances.Count);
|
||||||
|
int HiddenCount<T>(IReadOnlyList<T> items, int visibleCount) => Math.Max(0, items.Count - visibleCount);
|
||||||
string PlotMarkerKind(string? plotEventTypeName) => (plotEventTypeName ?? "Progress").Trim().ToLowerInvariant() switch
|
string PlotMarkerKind(string? plotEventTypeName) => (plotEventTypeName ?? "Progress").Trim().ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"start" => "start",
|
"start" => "start",
|
||||||
@ -646,6 +647,7 @@
|
|||||||
<option value="book">Book</option>
|
<option value="book">Book</option>
|
||||||
<option value="chapter">Chapter</option>
|
<option value="chapter">Chapter</option>
|
||||||
<option value="scene-detail">Scene Detail</option>
|
<option value="scene-detail">Scene Detail</option>
|
||||||
|
<option value="scene-overview">Scene Overview</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
@if (Model.Settings.ShowSceneCards) { <label><input type="checkbox" data-timeline-toggle="scene-cards" checked /> Scene cards</label> }
|
@if (Model.Settings.ShowSceneCards) { <label><input type="checkbox" data-timeline-toggle="scene-cards" checked /> Scene cards</label> }
|
||||||
@ -1041,6 +1043,7 @@ else
|
|||||||
var selectedClass = scene.SceneID == Model.SelectedSceneID ? "selected" : "";
|
var selectedClass = scene.SceneID == Model.SelectedSceneID ? "selected" : "";
|
||||||
var matchClass = Model.MatchingSceneIDs.Contains(scene.SceneID) ? "filter-match" : "filter-dim";
|
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" : "");
|
var focusClass = Model.FocusSceneIDs.Contains(scene.SceneID) ? "focus-match" : (Model.FocusSceneIDs.Any() ? "focus-dim" : "");
|
||||||
|
var overview = Model.SceneOverviews.GetValueOrDefault(scene.SceneID) ?? new TimelineSceneOverviewViewModel();
|
||||||
var povLabel = Model.ScenePovLabels.GetValueOrDefault(scene.SceneID, scene.POVCharacterID.HasValue ? "POV set" : "No POV");
|
var povLabel = Model.ScenePovLabels.GetValueOrDefault(scene.SceneID, scene.POVCharacterID.HasValue ? "POV set" : "No POV");
|
||||||
var metricSummary = string.Join(" / ", Model.MetricGraphs.Take(2).Select(graph => $"{graph.MetricName}: {graph.Points.FirstOrDefault(x => x.SceneID == scene.SceneID)?.Value ?? 0}"));
|
var metricSummary = string.Join(" / ", Model.MetricGraphs.Take(2).Select(graph => $"{graph.MetricName}: {graph.Points.FirstOrDefault(x => x.SceneID == scene.SceneID)?.Value ?? 0}"));
|
||||||
var plotSearch = string.Join(' ', Model.PlotLanes
|
var plotSearch = string.Join(' ', Model.PlotLanes
|
||||||
@ -1111,6 +1114,87 @@ else
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<span class="scene-summary">@scene.Summary</span>
|
<span class="scene-summary">@scene.Summary</span>
|
||||||
|
<div class="scene-overview-panel">
|
||||||
|
<div class="scene-overview-section scene-overview-characters">
|
||||||
|
<span class="scene-overview-label">Characters</span>
|
||||||
|
<div class="scene-overview-chip-row">
|
||||||
|
@if (overview.Characters.Any())
|
||||||
|
{
|
||||||
|
foreach (var character in overview.Characters.Take(7))
|
||||||
|
{
|
||||||
|
<span class="scene-overview-chip @(character.IsPov ? "is-pov" : "")">@character.CharacterName@(character.IsPov ? " [POV]" : "")</span>
|
||||||
|
}
|
||||||
|
if (HiddenCount(overview.Characters, 7) > 0)
|
||||||
|
{
|
||||||
|
<span class="scene-overview-chip is-more">+@HiddenCount(overview.Characters, 7) more</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="scene-overview-empty">No characters linked</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scene-overview-section scene-overview-assets">
|
||||||
|
<span class="scene-overview-label">Assets</span>
|
||||||
|
<div class="scene-overview-chip-row">
|
||||||
|
@if (overview.Assets.Any())
|
||||||
|
{
|
||||||
|
foreach (var asset in overview.Assets.Take(6))
|
||||||
|
{
|
||||||
|
<span class="scene-overview-chip @(asset.Importance >= 8 ? "is-important" : "")">@asset.AssetName</span>
|
||||||
|
}
|
||||||
|
if (HiddenCount(overview.Assets, 6) > 0)
|
||||||
|
{
|
||||||
|
<span class="scene-overview-chip is-more">+@HiddenCount(overview.Assets, 6) more</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="scene-overview-empty">No assets linked</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scene-overview-section scene-overview-locations">
|
||||||
|
<span class="scene-overview-label">Locations</span>
|
||||||
|
<div class="scene-overview-chip-row">
|
||||||
|
@if (overview.Locations.Any())
|
||||||
|
{
|
||||||
|
foreach (var location in overview.Locations.Take(4))
|
||||||
|
{
|
||||||
|
<span class="scene-overview-chip">@location</span>
|
||||||
|
}
|
||||||
|
if (HiddenCount(overview.Locations, 4) > 0)
|
||||||
|
{
|
||||||
|
<span class="scene-overview-chip is-more">+@HiddenCount(overview.Locations, 4) more</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="scene-overview-empty">No locations linked</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (scene.PurposeLabels.Any() || !string.IsNullOrWhiteSpace(scene.Summary))
|
||||||
|
{
|
||||||
|
<div class="scene-overview-section scene-overview-purpose">
|
||||||
|
<span class="scene-overview-label">Purpose</span>
|
||||||
|
@if (scene.PurposeLabels.Any())
|
||||||
|
{
|
||||||
|
<div class="scene-overview-chip-row">
|
||||||
|
@foreach (var purpose in scene.PurposeLabels.Take(4))
|
||||||
|
{
|
||||||
|
<span class="scene-overview-chip">@purpose.PurposeName</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(scene.Summary))
|
||||||
|
{
|
||||||
|
<span class="scene-overview-brief">@scene.Summary</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3078,6 +3078,73 @@ body.timeline-inspector-resizing {
|
|||||||
background: rgba(47, 111, 99, 0.08);
|
background: rgba(47, 111, 99, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scene-overview-panel {
|
||||||
|
display: none;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-section {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-label {
|
||||||
|
color: var(--plotline-muted);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-chip-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-chip,
|
||||||
|
.scene-overview-empty,
|
||||||
|
.scene-overview-brief {
|
||||||
|
display: inline-flex;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
background: rgba(47, 111, 99, 0.08);
|
||||||
|
color: var(--plotline-ink);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 750;
|
||||||
|
line-height: 1.25;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-chip.is-pov {
|
||||||
|
border: 1px solid rgba(197, 139, 43, 0.38);
|
||||||
|
background: #fff3d3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-chip.is-important {
|
||||||
|
border: 1px solid rgba(103, 80, 164, 0.3);
|
||||||
|
background: #f0edf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-chip.is-more,
|
||||||
|
.scene-overview-empty {
|
||||||
|
color: var(--plotline-muted);
|
||||||
|
background: rgba(102, 113, 123, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-overview-brief {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.62);
|
||||||
|
color: var(--plotline-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.timeline-scene-card.search-hit,
|
.timeline-scene-card.search-hit,
|
||||||
.timeline-scene-card.keyboard-focus {
|
.timeline-scene-card.keyboard-focus {
|
||||||
outline: 3px solid rgba(197, 139, 43, 0.34);
|
outline: 3px solid rgba(197, 139, 43, 0.34);
|
||||||
@ -3141,6 +3208,27 @@ body.timeline-inspector-resizing {
|
|||||||
-webkit-line-clamp: 7;
|
-webkit-line-clamp: 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-filtered-root.zoom-scene-overview {
|
||||||
|
--timeline-scene-width: 380px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-filtered-root.zoom-scene-overview .timeline-scenes {
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-filtered-root.zoom-scene-overview .timeline-scene-card {
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-filtered-root.zoom-scene-overview .scene-summary {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-filtered-root.zoom-scene-overview .scene-overview-panel {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
.timeline-filtered-root.density-expanded .timeline-scenes {
|
.timeline-filtered-root.density-expanded .timeline-scenes {
|
||||||
min-height: 230px;
|
min-height: 230px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1217,7 +1217,7 @@
|
|||||||
"[data-timeline-minimap]"
|
"[data-timeline-minimap]"
|
||||||
].join(",");
|
].join(",");
|
||||||
const densityValues = ["compact", "comfortable", "expanded"];
|
const densityValues = ["compact", "comfortable", "expanded"];
|
||||||
const zoomValues = ["book", "chapter", "scene-detail"];
|
const zoomValues = ["book", "chapter", "scene-detail", "scene-overview"];
|
||||||
const inspectorDrawer = root.querySelector("[data-inspector-drawer]");
|
const inspectorDrawer = root.querySelector("[data-inspector-drawer]");
|
||||||
const inspectorResizer = root.querySelector("[data-inspector-resizer]");
|
const inspectorResizer = root.querySelector("[data-inspector-resizer]");
|
||||||
const inspectorClose = root.querySelector("[data-inspector-close]");
|
const inspectorClose = root.querySelector("[data-inspector-close]");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user