Phase 11A PlotDirector Character Age and Story Date Management feature.
This commit is contained in:
parent
cfe95a510f
commit
b5decb0dc6
109
PlotLine/Services/CharacterAgeDisplayService.cs
Normal file
109
PlotLine/Services/CharacterAgeDisplayService.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using PlotLine.Models;
|
||||
|
||||
namespace PlotLine.Services;
|
||||
|
||||
public sealed class CharacterAgeDisplayResult
|
||||
{
|
||||
public bool HasExactAge { get; init; }
|
||||
public string? Label { get; init; }
|
||||
public string? DisplayText { get; init; }
|
||||
}
|
||||
|
||||
public static class CharacterAgeDisplayService
|
||||
{
|
||||
public static CharacterAgeDisplayResult GetAgeForScene(SceneCharacter character, string? sceneTimeModeName, DateTime? sceneStartDateTime)
|
||||
{
|
||||
var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime);
|
||||
if (character.BirthDate.HasValue && effectiveDate.HasValue)
|
||||
{
|
||||
return new CharacterAgeDisplayResult
|
||||
{
|
||||
HasExactAge = true,
|
||||
Label = "Age in scene",
|
||||
DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date)
|
||||
};
|
||||
}
|
||||
|
||||
if (character.AgeAtSeriesStart.HasValue)
|
||||
{
|
||||
return new CharacterAgeDisplayResult
|
||||
{
|
||||
Label = "Approximate age",
|
||||
DisplayText = $"About {character.AgeAtSeriesStart.Value}"
|
||||
};
|
||||
}
|
||||
|
||||
return new CharacterAgeDisplayResult();
|
||||
}
|
||||
|
||||
public static CharacterAgeDisplayResult GetAgeForScene(Character character, string? sceneTimeModeName, DateTime? sceneStartDateTime)
|
||||
{
|
||||
var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime);
|
||||
if (character.BirthDate.HasValue && effectiveDate.HasValue)
|
||||
{
|
||||
return new CharacterAgeDisplayResult
|
||||
{
|
||||
HasExactAge = true,
|
||||
Label = "Age in scene",
|
||||
DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date)
|
||||
};
|
||||
}
|
||||
|
||||
if (character.AgeAtSeriesStart.HasValue)
|
||||
{
|
||||
return new CharacterAgeDisplayResult
|
||||
{
|
||||
Label = "Approximate age",
|
||||
DisplayText = $"About {character.AgeAtSeriesStart.Value}"
|
||||
};
|
||||
}
|
||||
|
||||
return new CharacterAgeDisplayResult();
|
||||
}
|
||||
|
||||
public static DateTime? ResolveEffectiveSceneDate(string? timeModeName, DateTime? startDateTime)
|
||||
{
|
||||
if (!startDateTime.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return string.Equals(timeModeName, "Exact Date", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(timeModeName, "Exact DateTime", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(timeModeName, "Exact Date and Time", StringComparison.OrdinalIgnoreCase)
|
||||
? startDateTime.Value.Date
|
||||
: null;
|
||||
}
|
||||
|
||||
public static string FormatExactAge(DateTime birthDate, DateTime sceneDate)
|
||||
{
|
||||
var years = sceneDate.Year - birthDate.Year;
|
||||
if (sceneDate < birthDate.AddYears(years))
|
||||
{
|
||||
years--;
|
||||
}
|
||||
|
||||
if (years < 0)
|
||||
{
|
||||
years = 0;
|
||||
}
|
||||
|
||||
var lastBirthday = birthDate.AddYears(years);
|
||||
var months = ((sceneDate.Year - lastBirthday.Year) * 12) + sceneDate.Month - lastBirthday.Month;
|
||||
if (sceneDate.Day < lastBirthday.Day)
|
||||
{
|
||||
months--;
|
||||
}
|
||||
|
||||
if (months < 0)
|
||||
{
|
||||
months = 0;
|
||||
}
|
||||
|
||||
return months == 0
|
||||
? $"{years} year{Pluralise(years)}"
|
||||
: $"{years} year{Pluralise(years)}, {months} month{Pluralise(months)}";
|
||||
}
|
||||
|
||||
private static string Pluralise(int value) => value == 1 ? string.Empty : "s";
|
||||
}
|
||||
@ -1001,6 +1001,7 @@ public sealed class SceneService(
|
||||
SceneNumber = existing.Count + 1,
|
||||
RevisionStatusID = ChapterService.DefaultRevisionStatusId(lookupData),
|
||||
TimeModeID = lookupData.TimeModes.FirstOrDefault(x => x.TimeModeName == "Unknown")?.TimeModeID ?? lookupData.TimeModes.First().TimeModeID,
|
||||
TimeModeName = lookupData.TimeModes.FirstOrDefault(x => x.TimeModeName == "Unknown")?.TimeModeName ?? lookupData.TimeModes.First().TimeModeName,
|
||||
TimeConfidenceID = lookupData.TimeConfidences.FirstOrDefault(x => x.TimeConfidenceName == "Unknown")?.TimeConfidenceID ?? lookupData.TimeConfidences.First().TimeConfidenceID,
|
||||
Chapter = context.Value.Chapter,
|
||||
Book = context.Value.Book,
|
||||
@ -1031,6 +1032,7 @@ public sealed class SceneService(
|
||||
SceneTitle = scene.SceneTitle,
|
||||
Summary = scene.Summary,
|
||||
TimeModeID = scene.TimeModeID,
|
||||
TimeModeName = scene.TimeModeName,
|
||||
StartDateTime = scene.StartDateTime,
|
||||
EndDateTime = scene.EndDateTime,
|
||||
DurationAmount = scene.DurationAmount,
|
||||
@ -5614,20 +5616,9 @@ public sealed class CharacterService(
|
||||
|
||||
public string DisplayAge(Character character, DateTime? sceneStartDateTime = null)
|
||||
{
|
||||
if (character.BirthDate.HasValue && sceneStartDateTime.HasValue)
|
||||
{
|
||||
var age = sceneStartDateTime.Value.Year - character.BirthDate.Value.Year;
|
||||
if (sceneStartDateTime.Value.Date < character.BirthDate.Value.Date.AddYears(age))
|
||||
{
|
||||
age--;
|
||||
}
|
||||
var age = CharacterAgeDisplayService.GetAgeForScene(character, sceneStartDateTime.HasValue ? "Exact DateTime" : null, sceneStartDateTime);
|
||||
|
||||
return age.ToString();
|
||||
}
|
||||
|
||||
return character.AgeAtSeriesStart.HasValue
|
||||
? $"About {character.AgeAtSeriesStart.Value}"
|
||||
: "Unknown";
|
||||
return age.DisplayText ?? "Unknown";
|
||||
}
|
||||
|
||||
private static IReadOnlyList<SelectListItem> ToRelationshipTypeSelectList(IEnumerable<RelationshipType> relationshipTypes)
|
||||
|
||||
@ -126,6 +126,8 @@ public sealed class SceneEditViewModel
|
||||
[Display(Name = "Time mode")]
|
||||
public int TimeModeID { get; set; }
|
||||
|
||||
public string TimeModeName { get; set; } = string.Empty;
|
||||
|
||||
[Display(Name = "Start date/time")]
|
||||
public DateTime? StartDateTime { get; set; }
|
||||
|
||||
|
||||
@ -354,10 +354,15 @@
|
||||
@foreach (var character in Model.SceneCharacters)
|
||||
{
|
||||
var editId = $"scene-character-edit-{character.SceneCharacterID}";
|
||||
var ageDisplay = CharacterAgeDisplayService.GetAgeForScene(character, Model.TimeModeName, Model.StartDateTime);
|
||||
<article class="character-appearance-card">
|
||||
<div class="character-appearance-summary">
|
||||
<div>
|
||||
<strong>@character.CharacterName</strong>
|
||||
@if (!string.IsNullOrWhiteSpace(ageDisplay.DisplayText))
|
||||
{
|
||||
<span class="muted">@ageDisplay.Label: @ageDisplay.DisplayText</span>
|
||||
}
|
||||
<span class="muted">@((character.RoleInSceneTypeName ?? "Role not set")) • @((character.PresenceTypeName ?? "Presence not set"))</span>
|
||||
<span class="muted" title="@character.LocationPath">Location: @(character.LocationName ?? "Not set")</span>
|
||||
<span class="muted">Entry: @(character.EntryLocationName ?? "Not set")</span>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@using PlotLine
|
||||
@using PlotLine.Models
|
||||
@using PlotLine.Services
|
||||
@using PlotLine.ViewModels
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, PlotLine
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user