Implemented the targeted Timeline performance pass and applied the SQL locally.
This commit is contained in:
parent
a3ce37c66c
commit
b5b353e80c
@ -102,6 +102,7 @@ public interface ILookupRepository
|
|||||||
public interface ITimelineRepository
|
public interface ITimelineRepository
|
||||||
{
|
{
|
||||||
Task<TimelineData> GetByProjectAsync(int projectId, int? bookId, bool includeMetrics, IEnumerable<int> metricTypeIds, bool includePlotLines);
|
Task<TimelineData> GetByProjectAsync(int projectId, int? bookId, bool includeMetrics, IEnumerable<int> metricTypeIds, bool includePlotLines);
|
||||||
|
Task<IReadOnlyList<TimelineSceneOverviewRow>> GetSceneOverviewsAsync(int projectId, int? bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ITimelinePresetRepository
|
public interface ITimelinePresetRepository
|
||||||
@ -154,7 +155,7 @@ public interface IPlotRepository
|
|||||||
|
|
||||||
public interface IAssetRepository
|
public interface IAssetRepository
|
||||||
{
|
{
|
||||||
Task<AssetLookupData> GetLookupsAsync();
|
Task<AssetLookupData> GetLookupsAsync(int? projectId = null);
|
||||||
Task<IReadOnlyList<StoryAsset>> ListAssetsAsync(int projectId);
|
Task<IReadOnlyList<StoryAsset>> ListAssetsAsync(int projectId);
|
||||||
Task<StoryAsset?> GetAssetAsync(int storyAssetId);
|
Task<StoryAsset?> GetAssetAsync(int storyAssetId);
|
||||||
Task<int> SaveAssetAsync(StoryAsset asset);
|
Task<int> SaveAssetAsync(StoryAsset asset);
|
||||||
@ -178,7 +179,7 @@ public interface IAssetRepository
|
|||||||
|
|
||||||
public interface ICharacterRepository
|
public interface ICharacterRepository
|
||||||
{
|
{
|
||||||
Task<CharacterLookupData> GetLookupsAsync();
|
Task<CharacterLookupData> GetLookupsAsync(int? projectId = null);
|
||||||
Task<IReadOnlyList<Character>> ListCharactersAsync(int projectId);
|
Task<IReadOnlyList<Character>> ListCharactersAsync(int projectId);
|
||||||
Task<Character?> GetCharacterAsync(int characterId);
|
Task<Character?> GetCharacterAsync(int characterId);
|
||||||
Task<int> SaveCharacterAsync(Character character);
|
Task<int> SaveCharacterAsync(Character character);
|
||||||
@ -419,6 +420,19 @@ public sealed class TimelineData
|
|||||||
public IReadOnlyList<ThreadEvent> ThreadEvents { get; init; } = [];
|
public IReadOnlyList<ThreadEvent> ThreadEvents { get; init; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class TimelineSceneOverviewRow
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int? CharacterID { get; set; }
|
||||||
|
public string? CharacterName { get; set; }
|
||||||
|
public bool IsPov { get; set; }
|
||||||
|
public int? StoryAssetID { get; set; }
|
||||||
|
public string? AssetName { get; set; }
|
||||||
|
public int? AssetImportance { get; set; }
|
||||||
|
public string? AssetEventTypeName { get; set; }
|
||||||
|
public string? LocationName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class LocationRepository(ISqlConnectionFactory connectionFactory) : ILocationRepository
|
public sealed class LocationRepository(ISqlConnectionFactory connectionFactory) : ILocationRepository
|
||||||
{
|
{
|
||||||
public async Task<LocationLookupData> GetLookupsAsync()
|
public async Task<LocationLookupData> GetLookupsAsync()
|
||||||
@ -2520,6 +2534,16 @@ public sealed class TimelineRepository(ISqlConnectionFactory connectionFactory)
|
|||||||
public int PlotEventID { get; set; }
|
public int PlotEventID { get; set; }
|
||||||
public int PlotLineID { get; set; }
|
public int PlotLineID { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<TimelineSceneOverviewRow>> GetSceneOverviewsAsync(int projectId, int? bookId)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
var rows = await connection.QueryAsync<TimelineSceneOverviewRow>(
|
||||||
|
"dbo.Timeline_SceneOverviews",
|
||||||
|
new { ProjectID = projectId, BookID = bookId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
return rows.ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TimelineSettingsRepository(ISqlConnectionFactory connectionFactory) : ITimelineSettingsRepository
|
public sealed class TimelineSettingsRepository(ISqlConnectionFactory connectionFactory) : ITimelineSettingsRepository
|
||||||
@ -2902,10 +2926,10 @@ public sealed class PlotRepository(ISqlConnectionFactory connectionFactory) : IP
|
|||||||
|
|
||||||
public sealed class AssetRepository(ISqlConnectionFactory connectionFactory) : IAssetRepository
|
public sealed class AssetRepository(ISqlConnectionFactory connectionFactory) : IAssetRepository
|
||||||
{
|
{
|
||||||
public async Task<AssetLookupData> GetLookupsAsync()
|
public async Task<AssetLookupData> GetLookupsAsync(int? projectId = null)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
using var result = await connection.QueryMultipleAsync("dbo.AssetLookup_All", commandType: CommandType.StoredProcedure);
|
using var result = await connection.QueryMultipleAsync("dbo.AssetLookup_All", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
|
||||||
return new AssetLookupData
|
return new AssetLookupData
|
||||||
{
|
{
|
||||||
AssetKinds = (await result.ReadAsync<AssetKind>()).ToList(),
|
AssetKinds = (await result.ReadAsync<AssetKind>()).ToList(),
|
||||||
@ -3148,10 +3172,10 @@ public sealed class AssetRepository(ISqlConnectionFactory connectionFactory) : I
|
|||||||
|
|
||||||
public sealed class CharacterRepository(ISqlConnectionFactory connectionFactory) : ICharacterRepository
|
public sealed class CharacterRepository(ISqlConnectionFactory connectionFactory) : ICharacterRepository
|
||||||
{
|
{
|
||||||
public async Task<CharacterLookupData> GetLookupsAsync()
|
public async Task<CharacterLookupData> GetLookupsAsync(int? projectId = null)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
using var result = await connection.QueryMultipleAsync("dbo.CharacterLookup_All", commandType: CommandType.StoredProcedure);
|
using var result = await connection.QueryMultipleAsync("dbo.CharacterLookup_All", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
|
||||||
return new CharacterLookupData
|
return new CharacterLookupData
|
||||||
{
|
{
|
||||||
RoleTypes = (await result.ReadAsync<CharacterRoleInSceneType>()).ToList(),
|
RoleTypes = (await result.ReadAsync<CharacterRoleInSceneType>()).ToList(),
|
||||||
|
|||||||
@ -2134,8 +2134,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.ShowSceneCards || settings.ShowStoryAssets || filter.StoryAssetID.HasValue || filter.FocusType == "asset";
|
var needsAssetTimelineData = settings.ShowStoryAssets || filter.StoryAssetID.HasValue || filter.FocusType == "asset";
|
||||||
var needsCharacterTimelineData = settings.ShowSceneCards || settings.ShowCharacterAppearances || filter.CharacterID.HasValue || filter.FocusType == "character";
|
var needsCharacterTimelineData = 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(
|
||||||
@ -2150,8 +2150,8 @@ public sealed class TimelineService(
|
|||||||
|
|
||||||
var allBooks = await TimeTimelineLoadAsync("dbo.Book_ListByProject", () => books.ListByProjectAsync(projectId), projectId, bookId);
|
var allBooks = await TimeTimelineLoadAsync("dbo.Book_ListByProject", () => books.ListByProjectAsync(projectId), projectId, bookId);
|
||||||
var lookupData = await TimeTimelineLoadAsync("dbo.Lookup_All", lookups.GetAllAsync, projectId, bookId);
|
var lookupData = await TimeTimelineLoadAsync("dbo.Lookup_All", lookups.GetAllAsync, projectId, bookId);
|
||||||
var assetLookupData = await TimeTimelineLoadAsync("dbo.AssetLookup_All", assets.GetLookupsAsync, projectId, bookId);
|
var assetLookupData = await TimeTimelineLoadAsync("dbo.AssetLookup_All", () => assets.GetLookupsAsync(projectId), projectId, bookId);
|
||||||
var characterLookupData = await TimeTimelineLoadAsync("dbo.CharacterLookup_All", characters.GetLookupsAsync, projectId, bookId);
|
var characterLookupData = await TimeTimelineLoadAsync("dbo.CharacterLookup_All", () => characters.GetLookupsAsync(projectId), projectId, bookId);
|
||||||
var locationLookupData = await TimeTimelineLoadAsync("dbo.LocationLookup_All", locations.GetLookupsAsync, projectId, bookId);
|
var locationLookupData = await TimeTimelineLoadAsync("dbo.LocationLookup_All", locations.GetLookupsAsync, projectId, bookId);
|
||||||
var plotLines = await TimeTimelineLoadAsync("dbo.PlotLine_ListByProject", () => plots.ListPlotLinesAsync(projectId), projectId, bookId);
|
var plotLines = await TimeTimelineLoadAsync("dbo.PlotLine_ListByProject", () => plots.ListPlotLinesAsync(projectId), projectId, bookId);
|
||||||
var plotThreads = await TimeTimelineLoadAsync("dbo.PlotThread_ListByProject", () => plots.ListPlotThreadsByProjectAsync(projectId), projectId, bookId);
|
var plotThreads = await TimeTimelineLoadAsync("dbo.PlotThread_ListByProject", () => plots.ListPlotThreadsByProjectAsync(projectId), projectId, bookId);
|
||||||
@ -2166,10 +2166,14 @@ public sealed class TimelineService(
|
|||||||
var timelineWarnings = needsWarnings
|
var timelineWarnings = needsWarnings
|
||||||
? await TimeTimelineLoadAsync("dbo.ContinuityWarning_List", () => warnings.ListAsync(projectId, bookId, null, filter.WarningTypeID, filter.WarningSeverityID, null, filter.IncludeDismissedWarnings, filter.IncludeIntentionalWarnings), projectId, bookId)
|
? await TimeTimelineLoadAsync("dbo.ContinuityWarning_List", () => warnings.ListAsync(projectId, bookId, null, filter.WarningTypeID, filter.WarningSeverityID, null, filter.IncludeDismissedWarnings, filter.IncludeIntentionalWarnings), projectId, bookId)
|
||||||
: [];
|
: [];
|
||||||
var warningsByScene = timelineWarnings
|
var warningsByScene = await TimeTimelineLoadAsync(
|
||||||
.Where(x => x.SceneID.HasValue)
|
"TimelineService.PrepareWarnings",
|
||||||
.GroupBy(x => x.SceneID!.Value)
|
() => Task.FromResult(timelineWarnings
|
||||||
.ToDictionary(x => x.Key, x => (IReadOnlyList<ContinuityWarning>)x.ToList());
|
.Where(x => x.SceneID.HasValue)
|
||||||
|
.GroupBy(x => x.SceneID!.Value)
|
||||||
|
.ToDictionary(x => x.Key, x => (IReadOnlyList<ContinuityWarning>)x.ToList())),
|
||||||
|
projectId,
|
||||||
|
bookId);
|
||||||
var warningCounts = warningsByScene.ToDictionary(x => x.Key, x => x.Value.Count);
|
var warningCounts = warningsByScene.ToDictionary(x => x.Key, x => x.Value.Count);
|
||||||
var dependencyCounts = (await TimeTimelineLoadAsync("dbo.SceneDependency_Counts", () => sceneDependencies.GetCountsAsync(projectId, bookId), projectId, bookId)).ToDictionary(x => x.SceneID, x => x.DependencyCount);
|
var dependencyCounts = (await TimeTimelineLoadAsync("dbo.SceneDependency_Counts", () => sceneDependencies.GetCountsAsync(projectId, bookId), projectId, bookId)).ToDictionary(x => x.SceneID, x => x.DependencyCount);
|
||||||
|
|
||||||
@ -2187,6 +2191,9 @@ public sealed class TimelineService(
|
|||||||
? await TimeTimelineLoadAsync("dbo.CharacterTimeline_GetByProject", () => characters.GetTimelineAsync(projectId, bookId), projectId, bookId)
|
? await TimeTimelineLoadAsync("dbo.CharacterTimeline_GetByProject", () => characters.GetTimelineAsync(projectId, bookId), projectId, bookId)
|
||||||
: ([], []);
|
: ([], []);
|
||||||
ApplyTimelineCharacterAvatars(characterTimeline, allCharacters);
|
ApplyTimelineCharacterAvatars(characterTimeline, allCharacters);
|
||||||
|
var sceneOverviewRows = settings.ShowSceneCards
|
||||||
|
? await TimeTimelineLoadAsync("dbo.Timeline_SceneOverviews", () => timelineRepository.GetSceneOverviewsAsync(projectId, bookId), projectId, bookId)
|
||||||
|
: [];
|
||||||
var plotThreadDetailsById = plotThreads.ToDictionary(x => x.PlotThreadID);
|
var plotThreadDetailsById = plotThreads.ToDictionary(x => x.PlotThreadID);
|
||||||
foreach (var threadEvent in timeline.ThreadEvents)
|
foreach (var threadEvent in timeline.ThreadEvents)
|
||||||
{
|
{
|
||||||
@ -2202,7 +2209,22 @@ public sealed class TimelineService(
|
|||||||
.ToDictionary(x => x.Key, x => x.First().CharacterName);
|
.ToDictionary(x => x.Key, x => x.First().CharacterName);
|
||||||
var matchingSceneIds = await TimeTimelineLoadAsync("TimelineService.BuildMatchingSceneIds", () => BuildMatchingSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId), projectId, bookId);
|
var matchingSceneIds = await TimeTimelineLoadAsync("TimelineService.BuildMatchingSceneIds", () => BuildMatchingSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId), projectId, bookId);
|
||||||
var focusSceneIds = await TimeTimelineLoadAsync("TimelineService.BuildFocusSceneIds", () => BuildFocusSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId), projectId, bookId);
|
var focusSceneIds = await TimeTimelineLoadAsync("TimelineService.BuildFocusSceneIds", () => BuildFocusSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId), projectId, bookId);
|
||||||
var focusLabel = BuildFocusLabel(filter, plotLines, plotThreads, allAssets, allCharacters, allLocations);
|
var focusLabel = await TimeTimelineLoadAsync("TimelineService.BuildFocusLabel", () => Task.FromResult(BuildFocusLabel(filter, plotLines, plotThreads, allAssets, allCharacters, allLocations)), projectId, bookId);
|
||||||
|
var sceneOverviews = settings.ShowSceneCards
|
||||||
|
? await TimeTimelineLoadAsync("TimelineService.BuildSceneOverviews", () => Task.FromResult(BuildSceneOverviews(orderedScenes, sceneOverviewRows)), projectId, bookId)
|
||||||
|
: new Dictionary<int, TimelineSceneOverviewViewModel>();
|
||||||
|
var metricGraphs = settings.ShowMetricShape
|
||||||
|
? await TimeTimelineLoadAsync("TimelineService.BuildMetricGraphs", () => Task.FromResult(BuildMetricGraphs(timeline, orderedScenes)), projectId, bookId)
|
||||||
|
: [];
|
||||||
|
var plotLanes = settings.ShowPlotLines
|
||||||
|
? await TimeTimelineLoadAsync("TimelineService.BuildPlotLanes", () => Task.FromResult(BuildPlotLanes(timeline, orderedScenes)), projectId, bookId)
|
||||||
|
: [];
|
||||||
|
var assetLanes = settings.ShowStoryAssets
|
||||||
|
? await TimeTimelineLoadAsync("TimelineService.BuildAssetLanes", () => Task.FromResult(BuildAssetLanes(assetTimeline.Assets, assetTimeline.Events, orderedScenes)), projectId, bookId)
|
||||||
|
: [];
|
||||||
|
var characterLanes = settings.ShowCharacterAppearances
|
||||||
|
? await TimeTimelineLoadAsync("TimelineService.BuildCharacterLanes", () => Task.FromResult(BuildCharacterLanes(characterTimeline.Characters, characterTimeline.Appearances, orderedScenes)), projectId, bookId)
|
||||||
|
: [];
|
||||||
var selectedScene = selectedSceneId.HasValue
|
var selectedScene = selectedSceneId.HasValue
|
||||||
? await TimeTimelineLoadAsync("SceneService.GetEditAsync", () => scenes.GetEditAsync(selectedSceneId.Value), projectId, bookId)
|
? await TimeTimelineLoadAsync("SceneService.GetEditAsync", () => scenes.GetEditAsync(selectedSceneId.Value), projectId, bookId)
|
||||||
: null;
|
: null;
|
||||||
@ -2277,11 +2299,11 @@ public sealed class TimelineService(
|
|||||||
.ToList()
|
.ToList()
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
OrderedScenes = orderedScenes,
|
OrderedScenes = orderedScenes,
|
||||||
MetricGraphs = settings.ShowMetricShape ? BuildMetricGraphs(timeline, orderedScenes) : [],
|
MetricGraphs = metricGraphs,
|
||||||
PlotLanes = settings.ShowPlotLines ? BuildPlotLanes(timeline, orderedScenes) : [],
|
PlotLanes = plotLanes,
|
||||||
AssetLanes = settings.ShowStoryAssets ? BuildAssetLanes(assetTimeline.Assets, assetTimeline.Events, orderedScenes) : [],
|
AssetLanes = assetLanes,
|
||||||
CharacterLanes = settings.ShowCharacterAppearances ? BuildCharacterLanes(characterTimeline.Characters, characterTimeline.Appearances, orderedScenes) : [],
|
CharacterLanes = characterLanes,
|
||||||
SceneOverviews = settings.ShowSceneCards ? BuildSceneOverviews(orderedScenes, characterTimeline.Appearances, assetTimeline.Assets, assetTimeline.Events) : new Dictionary<int, TimelineSceneOverviewViewModel>(),
|
SceneOverviews = sceneOverviews,
|
||||||
WarningsByScene = warningsByScene,
|
WarningsByScene = warningsByScene,
|
||||||
MatchingSceneIDs = matchingSceneIds,
|
MatchingSceneIDs = matchingSceneIds,
|
||||||
FocusSceneIDs = focusSceneIds,
|
FocusSceneIDs = focusSceneIds,
|
||||||
@ -2792,60 +2814,59 @@ public sealed class TimelineService(
|
|||||||
|
|
||||||
private static IReadOnlyDictionary<int, TimelineSceneOverviewViewModel> BuildSceneOverviews(
|
private static IReadOnlyDictionary<int, TimelineSceneOverviewViewModel> BuildSceneOverviews(
|
||||||
IReadOnlyList<Scene> orderedScenes,
|
IReadOnlyList<Scene> orderedScenes,
|
||||||
IReadOnlyList<SceneCharacter> appearances,
|
IReadOnlyList<TimelineSceneOverviewRow> overviewRows)
|
||||||
IReadOnlyList<StoryAsset> assets,
|
|
||||||
IReadOnlyList<AssetEvent> assetEvents)
|
|
||||||
{
|
{
|
||||||
var assetImportanceById = assets.ToDictionary(x => x.StoryAssetID, x => x.Importance);
|
var charactersByScene = overviewRows
|
||||||
var charactersByScene = appearances
|
.Where(x => x.CharacterID.HasValue && !string.IsNullOrWhiteSpace(x.CharacterName))
|
||||||
.GroupBy(x => x.SceneID)
|
.GroupBy(x => x.SceneID)
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
sceneGroup => sceneGroup.Key,
|
sceneGroup => sceneGroup.Key,
|
||||||
sceneGroup => sceneGroup
|
sceneGroup => sceneGroup
|
||||||
.GroupBy(x => x.CharacterID)
|
.GroupBy(x => x.CharacterID!.Value)
|
||||||
.Select(characterGroup =>
|
.Select(characterGroup =>
|
||||||
{
|
{
|
||||||
var first = characterGroup
|
var first = characterGroup
|
||||||
.OrderByDescending(x => string.Equals(x.RoleInSceneTypeName, "POV Character", StringComparison.OrdinalIgnoreCase))
|
.OrderByDescending(x => x.IsPov)
|
||||||
.ThenBy(x => x.SceneCharacterID)
|
.ThenBy(x => x.CharacterName)
|
||||||
.First();
|
.First();
|
||||||
return new TimelineSceneCharacterOverviewViewModel
|
return new TimelineSceneCharacterOverviewViewModel
|
||||||
{
|
{
|
||||||
CharacterID = first.CharacterID,
|
CharacterID = first.CharacterID!.Value,
|
||||||
CharacterName = first.CharacterName,
|
CharacterName = first.CharacterName ?? string.Empty,
|
||||||
IsPov = string.Equals(first.RoleInSceneTypeName, "POV Character", StringComparison.OrdinalIgnoreCase)
|
IsPov = first.IsPov
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.OrderByDescending(x => x.IsPov)
|
.OrderByDescending(x => x.IsPov)
|
||||||
.ThenBy(x => x.CharacterName)
|
.ThenBy(x => x.CharacterName)
|
||||||
.ToList());
|
.ToList());
|
||||||
var assetsByScene = assetEvents
|
var assetsByScene = overviewRows
|
||||||
|
.Where(x => x.StoryAssetID.HasValue && !string.IsNullOrWhiteSpace(x.AssetName))
|
||||||
.GroupBy(x => x.SceneID)
|
.GroupBy(x => x.SceneID)
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
sceneGroup => sceneGroup.Key,
|
sceneGroup => sceneGroup.Key,
|
||||||
sceneGroup => sceneGroup
|
sceneGroup => sceneGroup
|
||||||
.GroupBy(x => x.StoryAssetID)
|
.GroupBy(x => x.StoryAssetID!.Value)
|
||||||
.Select(assetGroup =>
|
.Select(assetGroup =>
|
||||||
{
|
{
|
||||||
var first = assetGroup.OrderByDescending(x => x.UpdatedDate).First();
|
var first = assetGroup.First();
|
||||||
return new TimelineSceneAssetOverviewViewModel
|
return new TimelineSceneAssetOverviewViewModel
|
||||||
{
|
{
|
||||||
StoryAssetID = first.StoryAssetID,
|
StoryAssetID = first.StoryAssetID!.Value,
|
||||||
AssetName = first.AssetName,
|
AssetName = first.AssetName ?? string.Empty,
|
||||||
Importance = assetImportanceById.GetValueOrDefault(first.StoryAssetID),
|
Importance = first.AssetImportance.GetValueOrDefault(),
|
||||||
EventTypeName = first.AssetEventTypeName
|
EventTypeName = first.AssetEventTypeName
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.OrderByDescending(x => x.Importance)
|
.OrderByDescending(x => x.Importance)
|
||||||
.ThenBy(x => x.AssetName)
|
.ThenBy(x => x.AssetName)
|
||||||
.ToList());
|
.ToList());
|
||||||
var characterLocationsByScene = appearances
|
var locationsByScene = overviewRows
|
||||||
.Where(x => !string.IsNullOrWhiteSpace(x.LocationPath) || !string.IsNullOrWhiteSpace(x.LocationName))
|
.Where(x => !string.IsNullOrWhiteSpace(x.LocationName))
|
||||||
.GroupBy(x => x.SceneID)
|
.GroupBy(x => x.SceneID)
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
sceneGroup => sceneGroup.Key,
|
sceneGroup => sceneGroup.Key,
|
||||||
sceneGroup => sceneGroup
|
sceneGroup => sceneGroup
|
||||||
.Select(x => x.LocationPath ?? x.LocationName ?? string.Empty)
|
.Select(x => x.LocationName ?? string.Empty)
|
||||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.OrderBy(x => x)
|
.OrderBy(x => x)
|
||||||
@ -2862,7 +2883,7 @@ public sealed class TimelineService(
|
|||||||
{
|
{
|
||||||
locations.Add(scene.PrimaryLocationName);
|
locations.Add(scene.PrimaryLocationName);
|
||||||
}
|
}
|
||||||
if (characterLocationsByScene.TryGetValue(scene.SceneID, out var characterLocations))
|
if (locationsByScene.TryGetValue(scene.SceneID, out var characterLocations))
|
||||||
{
|
{
|
||||||
locations.AddRange(characterLocations);
|
locations.AddRange(characterLocations);
|
||||||
}
|
}
|
||||||
|
|||||||
444
PlotLine/Sql/089_TimelineLoadPerformance.sql
Normal file
444
PlotLine/Sql/089_TimelineLoadPerformance.sql
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
/*
|
||||||
|
Timeline-focused performance pass.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Keep the initial Timeline page from loading full character/asset lane data
|
||||||
|
just to render scene-card summaries.
|
||||||
|
- Keep project/book filters as early as possible inside heavy Timeline queries.
|
||||||
|
- Add narrow supporting indexes for the Timeline warning, character, asset,
|
||||||
|
and scene-overview paths.
|
||||||
|
*/
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ContinuityWarnings_TimelineActiveList' AND object_id = OBJECT_ID(N'dbo.ContinuityWarnings'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_ContinuityWarnings_TimelineActiveList
|
||||||
|
ON dbo.ContinuityWarnings(ProjectID, BookID, WarningSeverityID, WarningTypeID, SceneID, LastDetectedDate DESC, ContinuityWarningID DESC)
|
||||||
|
INCLUDE (ValidationRunID, ChapterID, EntityType, EntityID, Message, Details, CreatedDate)
|
||||||
|
WHERE IsDismissed = 0 AND IsIntentional = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ContinuityWarnings_TimelineList' AND object_id = OBJECT_ID(N'dbo.ContinuityWarnings'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_ContinuityWarnings_TimelineList
|
||||||
|
ON dbo.ContinuityWarnings(ProjectID, BookID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID)
|
||||||
|
INCLUDE (IsDismissed, IsIntentional, LastDetectedDate, ValidationRunID, ChapterID, Message, Details, CreatedDate);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneCharacters_SceneID_TimelineOverview' AND object_id = OBJECT_ID(N'dbo.SceneCharacters'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_SceneCharacters_SceneID_TimelineOverview
|
||||||
|
ON dbo.SceneCharacters(SceneID, CharacterID)
|
||||||
|
INCLUDE (SceneCharacterID, RoleInSceneTypeID, PresenceTypeID, LocationID, EntryLocationID, ExitLocationID, AppearanceNotes, OutfitDescription, PhysicalCondition, EmotionalState, KnowledgeNotes, CreatedDate, UpdatedDate);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Books_Project_Active_Timeline' AND object_id = OBJECT_ID(N'dbo.Books'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Books_Project_Active_Timeline
|
||||||
|
ON dbo.Books(ProjectID, IsArchived, BookID)
|
||||||
|
INCLUDE (BookTitle, Subtitle, SortOrder, BookNumber);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Chapters_Book_Active_Timeline' AND object_id = OBJECT_ID(N'dbo.Chapters'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Chapters_Book_Active_Timeline
|
||||||
|
ON dbo.Chapters(BookID, IsArchived, ChapterID)
|
||||||
|
INCLUDE (ChapterNumber, ChapterTitle, SortOrder);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Scenes_Chapter_Active_Timeline' AND object_id = OBJECT_ID(N'dbo.Scenes'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Scenes_Chapter_Active_Timeline
|
||||||
|
ON dbo.Scenes(ChapterID, IsArchived, SceneID)
|
||||||
|
INCLUDE (SceneNumber, SceneTitle, SortOrder, POVCharacterID, PrimaryLocationID, RevisionStatusID);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_StoryAssets_Project_TimelineList' AND object_id = OBJECT_ID(N'dbo.StoryAssets'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_StoryAssets_Project_TimelineList
|
||||||
|
ON dbo.StoryAssets(ProjectID, IsArchived, Importance DESC, AssetName)
|
||||||
|
INCLUDE (AssetKindID, CurrentStateID, CurrentLocationID, IsResolved, ShowInQuickAddBar, ImagePath, ThumbnailPath, CreatedDate, UpdatedDate);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetEvents_SceneID_TimelineOverview' AND object_id = OBJECT_ID(N'dbo.AssetEvents'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_AssetEvents_SceneID_TimelineOverview
|
||||||
|
ON dbo.AssetEvents(SceneID, StoryAssetID, UpdatedDate DESC)
|
||||||
|
INCLUDE (AssetEventTypeID, EventTitle);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetStates_Active_Project_Sort' AND object_id = OBJECT_ID(N'dbo.AssetStates'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_AssetStates_Active_Project_Sort
|
||||||
|
ON dbo.AssetStates(IsActive, ProjectID, SortOrder)
|
||||||
|
INCLUDE (AssetKindID, StateName, Description, IsResolvedState);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_CharacterAttributeTypes_Active_Project_Sort' AND object_id = OBJECT_ID(N'dbo.CharacterAttributeTypes'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_CharacterAttributeTypes_Active_Project_Sort
|
||||||
|
ON dbo.CharacterAttributeTypes(IsActive, ProjectID, SortOrder)
|
||||||
|
INCLUDE (AttributeName, DataType, IsNormallyStable);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_List
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@SceneID int = NULL,
|
||||||
|
@EntityType nvarchar(80) = NULL,
|
||||||
|
@EntityID int = NULL,
|
||||||
|
@WarningTypeID int = NULL,
|
||||||
|
@WarningSeverityID int = NULL,
|
||||||
|
@IncludeDismissed bit = 0,
|
||||||
|
@IncludeIntentional bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF @IncludeDismissed = 0 AND @IncludeIntentional = 0
|
||||||
|
BEGIN
|
||||||
|
SELECT cw.ContinuityWarningID, cw.ValidationRunID, cw.ProjectID, cw.BookID, b.BookTitle, b.Subtitle AS BookSubtitle,
|
||||||
|
cw.ChapterID, c.ChapterNumber, c.ChapterTitle, cw.SceneID, s.SceneNumber, s.SceneTitle, cw.EntityType,
|
||||||
|
cw.EntityID, cw.WarningTypeID, wt.TypeName AS WarningTypeName, cw.WarningSeverityID, ws.SeverityName,
|
||||||
|
cw.Message, cw.Details, cw.IsDismissed, cw.IsIntentional, cw.CreatedDate, cw.LastDetectedDate
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||||
|
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = cw.BookID
|
||||||
|
LEFT JOIN dbo.Chapters c ON c.ChapterID = cw.ChapterID
|
||||||
|
LEFT JOIN dbo.Scenes s ON s.SceneID = cw.SceneID
|
||||||
|
WHERE cw.ProjectID = @ProjectID
|
||||||
|
AND cw.IsDismissed = 0
|
||||||
|
AND cw.IsIntentional = 0
|
||||||
|
AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
AND (@SceneID IS NULL OR cw.SceneID = @SceneID)
|
||||||
|
AND (@EntityType IS NULL OR cw.EntityType = @EntityType)
|
||||||
|
AND (@EntityID IS NULL OR cw.EntityID = @EntityID)
|
||||||
|
AND (@WarningTypeID IS NULL OR cw.WarningTypeID = @WarningTypeID)
|
||||||
|
AND (@WarningSeverityID IS NULL OR cw.WarningSeverityID = @WarningSeverityID)
|
||||||
|
ORDER BY ws.SortOrder DESC, cw.LastDetectedDate DESC, cw.ContinuityWarningID DESC
|
||||||
|
OPTION (RECOMPILE);
|
||||||
|
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
SELECT cw.ContinuityWarningID, cw.ValidationRunID, cw.ProjectID, cw.BookID, b.BookTitle, b.Subtitle AS BookSubtitle,
|
||||||
|
cw.ChapterID, c.ChapterNumber, c.ChapterTitle, cw.SceneID, s.SceneNumber, s.SceneTitle, cw.EntityType,
|
||||||
|
cw.EntityID, cw.WarningTypeID, wt.TypeName AS WarningTypeName, cw.WarningSeverityID, ws.SeverityName,
|
||||||
|
cw.Message, cw.Details, cw.IsDismissed, cw.IsIntentional, cw.CreatedDate, cw.LastDetectedDate
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||||
|
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = cw.BookID
|
||||||
|
LEFT JOIN dbo.Chapters c ON c.ChapterID = cw.ChapterID
|
||||||
|
LEFT JOIN dbo.Scenes s ON s.SceneID = cw.SceneID
|
||||||
|
WHERE cw.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
AND (@SceneID IS NULL OR cw.SceneID = @SceneID)
|
||||||
|
AND (@EntityType IS NULL OR cw.EntityType = @EntityType)
|
||||||
|
AND (@EntityID IS NULL OR cw.EntityID = @EntityID)
|
||||||
|
AND (@WarningTypeID IS NULL OR cw.WarningTypeID = @WarningTypeID)
|
||||||
|
AND (@WarningSeverityID IS NULL OR cw.WarningSeverityID = @WarningSeverityID)
|
||||||
|
AND (@IncludeDismissed = 1 OR cw.IsDismissed = 0)
|
||||||
|
AND (@IncludeIntentional = 1 OR cw.IsIntentional = 0)
|
||||||
|
ORDER BY ws.SortOrder DESC, cw.LastDetectedDate DESC, cw.ContinuityWarningID DESC
|
||||||
|
OPTION (RECOMPILE);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
CREATE TABLE #ProjectLocationPaths
|
||||||
|
(
|
||||||
|
LocationID int NOT NULL PRIMARY KEY,
|
||||||
|
LocationName nvarchar(200) NOT NULL,
|
||||||
|
LocationPath nvarchar(max) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
;WITH ProjectLocationTree AS
|
||||||
|
(
|
||||||
|
SELECT LocationID, LocationName, CAST(LocationName AS nvarchar(max)) AS LocationPath
|
||||||
|
FROM dbo.Locations
|
||||||
|
WHERE ProjectID = @ProjectID AND ParentLocationID IS NULL AND IsArchived = 0
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT child.LocationID, child.LocationName, CAST(parent.LocationPath + N' > ' + child.LocationName AS nvarchar(max)) AS LocationPath
|
||||||
|
FROM dbo.Locations child
|
||||||
|
INNER JOIN ProjectLocationTree parent ON parent.LocationID = child.ParentLocationID
|
||||||
|
WHERE child.ProjectID = @ProjectID AND child.IsArchived = 0
|
||||||
|
)
|
||||||
|
INSERT #ProjectLocationPaths (LocationID, LocationName, LocationPath)
|
||||||
|
SELECT LocationID, LocationName, LocationPath
|
||||||
|
FROM ProjectLocationTree;
|
||||||
|
|
||||||
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
|
||||||
|
sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
|
||||||
|
location.LocationName AS CurrentLocationName, location.LocationPath AS CurrentLocationPath,
|
||||||
|
sa.IsResolved, sa.ShowInQuickAddBar, sa.ImagePath, sa.ThumbnailPath, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
LEFT JOIN #ProjectLocationPaths location ON location.LocationID = sa.CurrentLocationID
|
||||||
|
WHERE sa.ProjectID = @ProjectID
|
||||||
|
AND sa.IsArchived = 0
|
||||||
|
ORDER BY sa.Importance DESC, sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetLookup_All
|
||||||
|
@ProjectID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT AssetKindID, KindName, SortOrder, IsActive FROM dbo.AssetKinds WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT AssetStateID, ProjectID, AssetKindID, StateName, SortOrder, Description, IsResolvedState, IsActive
|
||||||
|
FROM dbo.AssetStates
|
||||||
|
WHERE IsActive = 1
|
||||||
|
AND (@ProjectID IS NULL OR ProjectID IS NULL OR ProjectID = @ProjectID)
|
||||||
|
ORDER BY SortOrder;
|
||||||
|
SELECT AssetEventTypeID, TypeName, MarkerText, SortOrder, IsActive FROM dbo.AssetEventTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT AssetDependencyTypeID, TypeName, SortOrder, IsValidatingDependency, IsActive FROM dbo.AssetDependencyTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT AssetCustodyEventTypeID, TypeName, SortOrder, IsActive FROM dbo.AssetCustodyEventTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT CustodyRoleID, RoleName, SortOrder, IsActive FROM dbo.CustodyRoles WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterLookup_All
|
||||||
|
@ProjectID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT CharacterRoleInSceneTypeID, TypeName, SortOrder, IsActive FROM dbo.CharacterRoleInSceneTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT PresenceTypeID, TypeName, SortOrder, IsActive FROM dbo.PresenceTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT CharacterAttributeTypeID, ProjectID, AttributeName, DataType, IsNormallyStable, SortOrder, IsActive
|
||||||
|
FROM dbo.CharacterAttributeTypes
|
||||||
|
WHERE IsActive = 1
|
||||||
|
AND (@ProjectID IS NULL OR ProjectID IS NULL OR ProjectID = @ProjectID)
|
||||||
|
ORDER BY SortOrder;
|
||||||
|
SELECT KnowledgeStateID, StateName, SortOrder, IsActive FROM dbo.KnowledgeStates WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT rt.RelationshipTypeID, rt.TypeName, rt.IsPermanentDefault, rt.SortOrder, rt.IsActive,
|
||||||
|
rt.RelationshipCategoryID, rc.CategoryName AS RelationshipCategoryName, rc.SortOrder AS CategorySortOrder
|
||||||
|
FROM dbo.RelationshipTypes rt
|
||||||
|
LEFT JOIN dbo.RelationshipCategories rc ON rc.RelationshipCategoryID = rt.RelationshipCategoryID
|
||||||
|
WHERE rt.IsActive = 1
|
||||||
|
ORDER BY COALESCE(rc.SortOrder, 900), rt.SortOrder, rt.TypeName;
|
||||||
|
SELECT RelationshipStateID, StateName, SortOrder, IsActive FROM dbo.RelationshipStates WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT RelationshipCategoryID, CategoryName, SortOrder, IsActive FROM dbo.RelationshipCategories WHERE IsActive = 1 ORDER BY SortOrder, CategoryName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterTimeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
WITH RECOMPILE
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
CREATE TABLE #TimelineScenes
|
||||||
|
(
|
||||||
|
SceneID int NOT NULL PRIMARY KEY,
|
||||||
|
SceneNumber decimal(9,2) NOT NULL,
|
||||||
|
SceneTitle nvarchar(200) NOT NULL,
|
||||||
|
ChapterNumber decimal(9,2) NOT NULL,
|
||||||
|
BookTitle nvarchar(200) NOT NULL,
|
||||||
|
BookSubtitle nvarchar(200) NULL,
|
||||||
|
BookSortOrder int NOT NULL,
|
||||||
|
ChapterSortOrder int NOT NULL,
|
||||||
|
SceneSortOrder int NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT #TimelineScenes (SceneID, SceneNumber, SceneTitle, ChapterNumber, BookTitle, BookSubtitle, BookSortOrder, ChapterSortOrder, SceneSortOrder)
|
||||||
|
SELECT s.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle, b.Subtitle, b.SortOrder, ch.SortOrder, s.SortOrder
|
||||||
|
FROM dbo.Books b
|
||||||
|
INNER JOIN dbo.Chapters ch ON ch.BookID = b.BookID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.ChapterID = ch.ChapterID
|
||||||
|
WHERE b.ProjectID = @ProjectID
|
||||||
|
AND b.IsArchived = 0
|
||||||
|
AND ch.IsArchived = 0
|
||||||
|
AND s.IsArchived = 0
|
||||||
|
AND (@BookID IS NULL OR b.BookID = @BookID);
|
||||||
|
|
||||||
|
CREATE TABLE #TimelineCharacters
|
||||||
|
(
|
||||||
|
CharacterID int NOT NULL PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
CharacterName nvarchar(200) NOT NULL,
|
||||||
|
ShortName nvarchar(100) NULL,
|
||||||
|
Sex nvarchar(50) NULL,
|
||||||
|
BirthDate date NULL,
|
||||||
|
AgeAtSeriesStart int NULL,
|
||||||
|
AgeReferenceSceneID int NULL,
|
||||||
|
Height nvarchar(50) NULL,
|
||||||
|
EyeColour nvarchar(50) NULL,
|
||||||
|
CharacterImportance int NULL,
|
||||||
|
ShowInQuickAddBar bit NOT NULL,
|
||||||
|
DefaultDescription nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL,
|
||||||
|
UpdatedDate datetime2 NOT NULL,
|
||||||
|
IsArchived bit NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT #TimelineCharacters (CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID, Height, EyeColour, CharacterImportance, ShowInQuickAddBar, DefaultDescription, CreatedDate, UpdatedDate, IsArchived)
|
||||||
|
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||||
|
Height, EyeColour, CharacterImportance, ShowInQuickAddBar, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Characters
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0;
|
||||||
|
|
||||||
|
CREATE TABLE #ProjectLocationPaths
|
||||||
|
(
|
||||||
|
LocationID int NOT NULL PRIMARY KEY,
|
||||||
|
LocationName nvarchar(200) NOT NULL,
|
||||||
|
LocationPath nvarchar(max) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
;WITH ProjectLocationTree AS
|
||||||
|
(
|
||||||
|
SELECT LocationID, LocationName, CAST(LocationName AS nvarchar(max)) AS LocationPath
|
||||||
|
FROM dbo.Locations
|
||||||
|
WHERE ProjectID = @ProjectID AND ParentLocationID IS NULL AND IsArchived = 0
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT child.LocationID, child.LocationName, CAST(parent.LocationPath + N' > ' + child.LocationName AS nvarchar(max)) AS LocationPath
|
||||||
|
FROM dbo.Locations child
|
||||||
|
INNER JOIN ProjectLocationTree parent ON parent.LocationID = child.ParentLocationID
|
||||||
|
WHERE child.ProjectID = @ProjectID AND child.IsArchived = 0
|
||||||
|
)
|
||||||
|
INSERT #ProjectLocationPaths (LocationID, LocationName, LocationPath)
|
||||||
|
SELECT LocationID, LocationName, LocationPath
|
||||||
|
FROM ProjectLocationTree;
|
||||||
|
|
||||||
|
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||||
|
Height, EyeColour, CharacterImportance, ShowInQuickAddBar, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM #TimelineCharacters
|
||||||
|
ORDER BY ISNULL(CharacterImportance, 0) DESC, CharacterName;
|
||||||
|
|
||||||
|
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
|
||||||
|
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
|
||||||
|
sc.LocationID, location.LocationName, location.LocationPath, sc.EntryLocationID, entry.LocationName AS EntryLocationName,
|
||||||
|
sc.ExitLocationID, exitLocation.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
|
||||||
|
sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, s.SceneNumber, s.SceneTitle, s.ChapterNumber, s.BookTitle, s.BookSubtitle,
|
||||||
|
sc.CreatedDate, sc.UpdatedDate
|
||||||
|
FROM #TimelineScenes s
|
||||||
|
INNER JOIN dbo.SceneCharacters sc ON sc.SceneID = s.SceneID
|
||||||
|
INNER JOIN #TimelineCharacters c ON c.CharacterID = sc.CharacterID
|
||||||
|
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||||
|
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||||
|
LEFT JOIN #ProjectLocationPaths location ON location.LocationID = sc.LocationID
|
||||||
|
LEFT JOIN #ProjectLocationPaths entry ON entry.LocationID = sc.EntryLocationID
|
||||||
|
LEFT JOIN #ProjectLocationPaths exitLocation ON exitLocation.LocationID = sc.ExitLocationID
|
||||||
|
ORDER BY s.BookSortOrder, s.ChapterSortOrder, s.SceneSortOrder, c.CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Timeline_SceneOverviews
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
CREATE TABLE #TimelineScenes
|
||||||
|
(
|
||||||
|
SceneID int NOT NULL PRIMARY KEY,
|
||||||
|
BookSortOrder int NOT NULL,
|
||||||
|
ChapterSortOrder int NOT NULL,
|
||||||
|
SceneSortOrder int NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT #TimelineScenes (SceneID, BookSortOrder, ChapterSortOrder, SceneSortOrder)
|
||||||
|
SELECT s.SceneID, b.SortOrder, c.SortOrder, s.SortOrder
|
||||||
|
FROM dbo.Books b
|
||||||
|
INNER JOIN dbo.Chapters c ON c.BookID = b.BookID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID
|
||||||
|
WHERE b.ProjectID = @ProjectID
|
||||||
|
AND b.IsArchived = 0
|
||||||
|
AND c.IsArchived = 0
|
||||||
|
AND s.IsArchived = 0
|
||||||
|
AND (@BookID IS NULL OR b.BookID = @BookID);
|
||||||
|
|
||||||
|
CREATE TABLE #ProjectLocationPaths
|
||||||
|
(
|
||||||
|
LocationID int NOT NULL PRIMARY KEY,
|
||||||
|
LocationName nvarchar(200) NOT NULL,
|
||||||
|
LocationPath nvarchar(max) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
;WITH ProjectLocationTree AS
|
||||||
|
(
|
||||||
|
SELECT LocationID, LocationName, CAST(LocationName AS nvarchar(max)) AS LocationPath
|
||||||
|
FROM dbo.Locations
|
||||||
|
WHERE ProjectID = @ProjectID AND ParentLocationID IS NULL AND IsArchived = 0
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT child.LocationID, child.LocationName, CAST(parent.LocationPath + N' > ' + child.LocationName AS nvarchar(max)) AS LocationPath
|
||||||
|
FROM dbo.Locations child
|
||||||
|
INNER JOIN ProjectLocationTree parent ON parent.LocationID = child.ParentLocationID
|
||||||
|
WHERE child.ProjectID = @ProjectID AND child.IsArchived = 0
|
||||||
|
)
|
||||||
|
INSERT #ProjectLocationPaths (LocationID, LocationName, LocationPath)
|
||||||
|
SELECT LocationID, LocationName, LocationPath
|
||||||
|
FROM ProjectLocationTree;
|
||||||
|
|
||||||
|
SELECT ts.SceneID,
|
||||||
|
sc.CharacterID,
|
||||||
|
ch.CharacterName,
|
||||||
|
CONVERT(bit, CASE WHEN role.TypeName LIKE N'%POV%' THEN 1 ELSE 0 END) AS IsPov,
|
||||||
|
CONVERT(int, NULL) AS StoryAssetID,
|
||||||
|
CONVERT(nvarchar(200), NULL) AS AssetName,
|
||||||
|
CONVERT(int, NULL) AS AssetImportance,
|
||||||
|
CONVERT(nvarchar(100), NULL) AS AssetEventTypeName,
|
||||||
|
location.LocationPath AS LocationName
|
||||||
|
FROM #TimelineScenes ts
|
||||||
|
INNER JOIN dbo.SceneCharacters sc ON sc.SceneID = ts.SceneID
|
||||||
|
INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID AND ch.ProjectID = @ProjectID AND ch.IsArchived = 0
|
||||||
|
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||||
|
LEFT JOIN #ProjectLocationPaths location ON location.LocationID = sc.LocationID
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT SceneID, CharacterID, CharacterName, IsPov, StoryAssetID, AssetName, AssetImportance, AssetEventTypeName, LocationName
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT ts.SceneID,
|
||||||
|
CONVERT(int, NULL) AS CharacterID,
|
||||||
|
CONVERT(nvarchar(200), NULL) AS CharacterName,
|
||||||
|
CONVERT(bit, 0) AS IsPov,
|
||||||
|
sa.StoryAssetID,
|
||||||
|
sa.AssetName,
|
||||||
|
sa.Importance AS AssetImportance,
|
||||||
|
aet.TypeName AS AssetEventTypeName,
|
||||||
|
CONVERT(nvarchar(max), NULL) AS LocationName,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY ts.SceneID, sa.StoryAssetID ORDER BY ae.UpdatedDate DESC, ae.AssetEventID DESC) AS rn
|
||||||
|
FROM #TimelineScenes ts
|
||||||
|
INNER JOIN dbo.AssetEvents ae ON ae.SceneID = ts.SceneID
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0
|
||||||
|
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
|
||||||
|
) assetRows
|
||||||
|
WHERE rn = 1
|
||||||
|
ORDER BY SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
Loading…
x
Reference in New Issue
Block a user