511 lines
34 KiB
Plaintext
511 lines
34 KiB
Plaintext
@model CharacterDetailViewModel
|
|
@{
|
|
ViewData["Title"] = Model.Character.CharacterName;
|
|
ViewData["ProjectSection"] = "Characters";
|
|
var firstAppearance = Model.Appearances.FirstOrDefault();
|
|
var storyImportance = Model.Character.CharacterImportance switch
|
|
{
|
|
>= 8 => "Major",
|
|
>= 4 => "Secondary / Supporting",
|
|
>= 1 => "Minor",
|
|
_ => "Timeline fallback"
|
|
};
|
|
var characterLookup = Model.ProjectCharacters.ToDictionary(x => x.CharacterID);
|
|
}
|
|
|
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
|
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
|
<span>@Model.Character.CharacterName</span>
|
|
</nav>
|
|
|
|
<partial name="_ProjectSectionNav" model="Model.Project" />
|
|
|
|
<div class="page-heading pw-page-heading">
|
|
<partial name="_Avatar" model="@(new AvatarViewModel { DisplayName = Model.Character.CharacterName, ImagePath = Model.Character.AvatarImagePath ?? Model.Character.ImagePath, ThumbnailPath = Model.Character.AvatarThumbnailPath ?? Model.Character.ThumbnailPath, Size = "xl", Lazy = false })" />
|
|
<div>
|
|
<p class="eyebrow">Character</p>
|
|
<h1>@Model.Character.CharacterName <help-icon key="characters.details" /></h1>
|
|
<p class="lead-text">@Model.Character.DefaultDescription</p>
|
|
</div>
|
|
<div class="button-row">
|
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Back to characters</a>
|
|
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@firstAppearance?.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Back to timeline</a>
|
|
<div class="dropdown pw-actions">
|
|
<button class="btn pw-actions__toggle dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
Actions
|
|
</button>
|
|
<div class="dropdown-menu dropdown-menu-end">
|
|
<a class="dropdown-item" asp-action="Edit" asp-route-id="@Model.Character.CharacterID">Edit character</a>
|
|
<a class="dropdown-item" asp-controller="Exports" asp-action="Character" asp-route-id="@Model.Character.CharacterID">Export character</a>
|
|
<a class="dropdown-item" asp-controller="Exports" asp-action="Character" asp-route-id="@Model.Character.CharacterID" asp-route-format="markdown">Markdown</a>
|
|
<div class="dropdown-divider"></div>
|
|
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this character? This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
|
|
<input type="hidden" name="id" value="@Model.Character.CharacterID" />
|
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
|
<button class="dropdown-item text-danger" type="submit">Archive Character</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="pw-character-nav" aria-label="Character sections">
|
|
<a class="pw-character-nav__link active" href="#character-overview">Overview</a>
|
|
<a class="pw-character-nav__link" href="#character-relationships">Relationships</a>
|
|
<a class="pw-character-nav__link" asp-controller="Dynamics" asp-action="Character" asp-route-id="@Model.Character.CharacterID">Arc</a>
|
|
<a class="pw-character-nav__link" asp-controller="Dynamics" asp-action="Knowledge" asp-route-id="@Model.Character.CharacterID">Knowledge</a>
|
|
<a class="pw-character-nav__link" href="#character-gallery">Gallery</a>
|
|
<a class="pw-character-nav__link" href="#character-appearances">Appearances</a>
|
|
</nav>
|
|
|
|
@if (TempData["ImageMessage"] is string imageMessage)
|
|
{
|
|
<div class="alert alert-success">@imageMessage</div>
|
|
}
|
|
@if (TempData["ImageError"] is string imageError)
|
|
{
|
|
<div class="alert alert-warning">@imageError</div>
|
|
}
|
|
@if (TempData["RelationshipMessage"] is string relationshipMessage)
|
|
{
|
|
<div class="alert alert-info">@relationshipMessage</div>
|
|
}
|
|
|
|
<section id="character-overview" class="asset-detail-grid">
|
|
<article class="plain-card">
|
|
<h2>Profile</h2>
|
|
<p><strong>Aliases:</strong> @(Model.Aliases.Any() ? string.Join(", ", Model.Aliases.Select(x => x.Alias)) : "None")</p>
|
|
<p><strong>Sex:</strong> @Model.Character.Sex</p>
|
|
<p><strong>Age:</strong> @Model.DisplayAge</p>
|
|
<p><strong>Height:</strong> @Model.Character.Height</p>
|
|
<p><strong>Eye colour:</strong> @Model.Character.EyeColour</p>
|
|
<p><strong>Story importance:</strong> @storyImportance</p>
|
|
</article>
|
|
<article class="plain-card">
|
|
<h2>Initial Relationships <help-icon key="characters.initialRelationships" /></h2>
|
|
@if (!Model.InitialRelationships.Any())
|
|
{
|
|
<p class="muted">No initial relationships yet.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="asset-mini-list">
|
|
@foreach (var categoryGroup in Model.InitialRelationships.GroupBy(x => new { x.RelationshipCategoryName, x.CategorySortOrder }).OrderBy(x => x.Key.CategorySortOrder).ThenBy(x => x.Key.RelationshipCategoryName))
|
|
{
|
|
<h3 class="eyebrow">@categoryGroup.Key.RelationshipCategoryName</h3>
|
|
@foreach (var relationship in categoryGroup.OrderBy(x => x.RelationshipTypeSortOrder).ThenBy(x => x.CharacterAName).ThenBy(x => x.CharacterBName))
|
|
{
|
|
var initialSourceId = relationship.CharacterAID;
|
|
var initialRelatedId = relationship.CharacterBID;
|
|
Character? relatedCharacter = null;
|
|
characterLookup.TryGetValue(relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBID : relationship.CharacterAID, out relatedCharacter);
|
|
<div class="asset-mini-item character-relationship-editor" data-relationship-editor>
|
|
<div class="character-relationship-read" data-relationship-read>
|
|
<div class="visual-card-heading">
|
|
<partial name="_Avatar" model="@(new AvatarViewModel { DisplayName = relatedCharacter?.CharacterName ?? relationship.CharacterBName, ImagePath = relatedCharacter?.AvatarImagePath ?? relatedCharacter?.ImagePath, ThumbnailPath = relatedCharacter?.AvatarThumbnailPath ?? relatedCharacter?.ThumbnailPath })" />
|
|
<div>
|
|
<strong>@relationship.CharacterAName -> @relationship.CharacterBName</strong>
|
|
<span>@relationship.RelationshipTypeName <span class="status-pill">@relationship.RelationshipCategoryName</span></span>
|
|
<span>Reciprocal: @(relationship.IsReciprocal ? "Yes" : "No")</span>
|
|
<span>Reader initially knows: @(relationship.ReaderInitiallyKnows ? "Yes" : "No")</span>
|
|
@if (!string.IsNullOrWhiteSpace(relationship.InitialRelationshipStateName) || relationship.InitialIntensity.HasValue)
|
|
{
|
|
<span>@relationship.InitialRelationshipStateName@if (relationship.InitialIntensity.HasValue) { <text> / Intensity @relationship.InitialIntensity</text> }</span>
|
|
}
|
|
@if (!string.IsNullOrWhiteSpace(relationship.InitialBookDisplayTitle))
|
|
{
|
|
<span>Initial book: @relationship.InitialBookDisplayTitle</span>
|
|
}
|
|
@if (!string.IsNullOrWhiteSpace(relationship.Notes))
|
|
{
|
|
<span>@relationship.Notes</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="button-row">
|
|
<a asp-action="Details" asp-route-id="@(relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBID : relationship.CharacterAID)">Open related character</a>
|
|
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
|
|
<button class="btn btn-outline-primary btn-sm" type="button" data-relationship-edit>Edit</button>
|
|
<form asp-action="ArchiveRelationship" method="post" class="archive-button-form" data-confirm-message="Remove this relationship? This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
|
|
<input type="hidden" name="id" value="@relationship.CharacterRelationshipID" />
|
|
<input type="hidden" name="characterId" value="@Model.Character.CharacterID" />
|
|
<button class="btn btn-outline-danger btn-sm" type="submit">Remove</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<form asp-action="SaveInitialRelationship" method="post" class="row g-2 mt-2 d-none character-relationship-edit-form" data-relationship-form>
|
|
<input type="hidden" name="CharacterRelationshipID" value="@relationship.CharacterRelationshipID" />
|
|
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
|
|
<input type="hidden" name="CharacterID" value="@initialSourceId" />
|
|
<input type="hidden" name="ReturnCharacterID" value="@Model.Character.CharacterID" />
|
|
<div class="col-md-6">
|
|
<label class="form-label">Related character <help-icon key="characters.relationships.relatedCharacter" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="RelatedCharacterID" required>
|
|
@foreach (var option in Model.CharacterOptions.Where(x => x.Value != initialSourceId.ToString()))
|
|
{
|
|
<option value="@option.Value" selected="@(option.Value == initialRelatedId.ToString())">@option.Text</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Relationship type <help-icon key="characters.relationships.relationshipType" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="RelationshipTypeID">
|
|
@foreach (var option in Model.RelationshipTypeOptions)
|
|
{
|
|
<option value="@option.Value" selected="@(option.Value == relationship.RelationshipTypeID.ToString())">@option.Text</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Awareness <help-icon key="characters.relationships.awareness" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="InitialRelationshipStateID">
|
|
<option value="">No awareness state</option>
|
|
@foreach (var option in Model.RelationshipStateOptions)
|
|
{
|
|
<option value="@option.Value" selected="@(option.Value == relationship.InitialRelationshipStateID?.ToString())">@option.Text</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Initial book <help-icon key="characters.relationships.initialBook" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="InitialBookID">
|
|
<option value="">Project start</option>
|
|
@foreach (var option in Model.BookOptions)
|
|
{
|
|
<option value="@option.Value" selected="@(option.Value == relationship.InitialBookID?.ToString())">@option.Text</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<label class="form-label">Confidence / intensity <output>@(relationship.InitialIntensity ?? 5)</output> <help-icon key="characters.relationships.confidenceIntensity" mode="inline" /></label>
|
|
<input class="form-range" name="InitialIntensity" type="range" min="1" max="10" step="1" value="@(relationship.InitialIntensity ?? 5)" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-check mt-4">
|
|
<input type="hidden" name="IsReciprocal" value="false" />
|
|
<input class="form-check-input" name="IsReciprocal" type="checkbox" value="true" checked="@relationship.IsReciprocal" />
|
|
<span class="form-check-label">Reciprocal <help-icon key="characters.relationships.reciprocal" mode="inline" /></span>
|
|
</label>
|
|
<label class="form-check">
|
|
<input type="hidden" name="ReaderInitiallyKnows" value="false" />
|
|
<input class="form-check-input" name="ReaderInitiallyKnows" type="checkbox" value="true" checked="@relationship.ReaderInitiallyKnows" />
|
|
<span class="form-check-label">Reader initially knows <help-icon key="characters.relationships.readerInitiallyKnows" mode="inline" /></span>
|
|
</label>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">Notes <help-icon key="characters.relationships.notes" mode="inline" /></label>
|
|
<textarea class="form-control form-control-sm" name="Notes" rows="3">@relationship.Notes</textarea>
|
|
</div>
|
|
<div class="col-12 button-row">
|
|
<button class="btn btn-outline-primary btn-sm" type="submit">Save Changes</button>
|
|
<button class="btn btn-outline-secondary btn-sm" type="button" data-relationship-cancel>Cancel</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
<details class="mt-3">
|
|
<summary>Add initial relationship</summary>
|
|
<form asp-action="SaveInitialRelationship" method="post" class="row g-2 mt-2">
|
|
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
|
|
<input type="hidden" name="CharacterID" value="@Model.Character.CharacterID" />
|
|
<input type="hidden" name="ReturnCharacterID" value="@Model.Character.CharacterID" />
|
|
<div class="col-md-6">
|
|
<label class="form-label">Related character <help-icon key="characters.relationships.relatedCharacter" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="RelatedCharacterID" required>
|
|
<option value="">Choose character</option>
|
|
@foreach (var option in Model.CharacterOptions.Where(x => x.Value != Model.Character.CharacterID.ToString()))
|
|
{
|
|
<option value="@option.Value">@option.Text</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Relationship type <help-icon key="characters.relationships.relationshipType" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="RelationshipTypeID" asp-items="Model.RelationshipTypeOptions"></select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Awareness <help-icon key="characters.relationships.awareness" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="InitialRelationshipStateID" asp-items="Model.RelationshipStateOptions">
|
|
<option value="">No awareness state</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Initial book <help-icon key="characters.relationships.initialBook" mode="inline" /></label>
|
|
<select class="form-select form-select-sm" name="InitialBookID" asp-items="Model.BookOptions">
|
|
<option value="">Project start</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<label class="form-label">Confidence / intensity <output>5</output> <help-icon key="characters.relationships.confidenceIntensity" mode="inline" /></label>
|
|
<input class="form-range" name="InitialIntensity" type="range" min="1" max="10" step="1" value="5" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-check mt-4">
|
|
<input type="hidden" name="IsReciprocal" value="false" />
|
|
<input class="form-check-input" name="IsReciprocal" type="checkbox" value="true" checked />
|
|
<span class="form-check-label">Reciprocal <help-icon key="characters.relationships.reciprocal" mode="inline" /></span>
|
|
</label>
|
|
<label class="form-check">
|
|
<input type="hidden" name="ReaderInitiallyKnows" value="false" />
|
|
<input class="form-check-input" name="ReaderInitiallyKnows" type="checkbox" value="true" checked />
|
|
<span class="form-check-label">Reader initially knows <help-icon key="characters.relationships.readerInitiallyKnows" mode="inline" /></span>
|
|
</label>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">Notes <help-icon key="characters.relationships.notes" mode="inline" /></label>
|
|
<textarea class="form-control form-control-sm" name="Notes" rows="2"></textarea>
|
|
</div>
|
|
<div class="col-12">
|
|
<button class="btn btn-outline-primary btn-sm" type="submit">Add initial relationship</button>
|
|
</div>
|
|
</form>
|
|
</details>
|
|
</article>
|
|
</section>
|
|
|
|
<section id="character-gallery" class="list-section mt-3">
|
|
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-3">
|
|
<div>
|
|
<h2>Character Images <help-icon key="characters.images" /></h2>
|
|
<p class="muted mb-0">Keep portraits, full-body references, clothing notes, expressions, and inspiration images together.</p>
|
|
</div>
|
|
@if (!string.IsNullOrWhiteSpace(Model.Character.AvatarImagePath))
|
|
{
|
|
<form asp-action="RemoveAvatar" method="post" class="archive-button-form" data-confirm-message="Remove this character avatar? The gallery images will be kept.">
|
|
<input type="hidden" name="id" value="@Model.Character.CharacterID" />
|
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Remove avatar</button>
|
|
</form>
|
|
}
|
|
</div>
|
|
|
|
<form asp-action="UploadImage" method="post" enctype="multipart/form-data" class="character-gallery-upload mb-3">
|
|
<input type="hidden" name="CharacterID" value="@Model.Character.CharacterID" />
|
|
<div>
|
|
<label class="form-label" for="ImageUpload">Add image</label>
|
|
<input class="form-control" id="ImageUpload" name="ImageUpload" type="file" accept=".jpg,.jpeg,.png,.webp,image/jpeg,image/png,image/webp" />
|
|
</div>
|
|
<div>
|
|
<label class="form-label" for="Caption">Caption</label>
|
|
<input class="form-control" id="Caption" name="Caption" maxlength="500" placeholder="Optional description" />
|
|
</div>
|
|
<button class="btn btn-primary" type="submit">Upload</button>
|
|
</form>
|
|
|
|
@if (!Model.Images.Any())
|
|
{
|
|
<p class="muted">No gallery images yet.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="character-gallery-grid">
|
|
@foreach (var image in Model.Images)
|
|
{
|
|
var isAvatarSource = Model.Character.AvatarSourceCharacterImageID == image.CharacterImageID;
|
|
<article class="character-gallery-card">
|
|
<img src="@image.ThumbnailPath" alt="" loading="lazy" />
|
|
<div class="character-gallery-card__body">
|
|
<strong>@(string.IsNullOrWhiteSpace(image.Caption) ? "Character reference" : image.Caption)</strong>
|
|
<span class="muted">Uploaded @image.UploadedDateUTC.ToLocalTime().ToString("yyyy-MM-dd")</span>
|
|
@if (isAvatarSource)
|
|
{
|
|
<span class="status-pill">Avatar source</span>
|
|
}
|
|
</div>
|
|
<div class="button-row character-gallery-card__actions">
|
|
<a class="btn btn-outline-primary btn-sm" asp-action="CropAvatar" asp-route-imageId="@image.CharacterImageID">Create Avatar</a>
|
|
<a class="btn btn-outline-secondary btn-sm" href="@image.ImagePath" target="_blank" rel="noopener">Open</a>
|
|
<form asp-action="DeleteImage" method="post" class="archive-button-form" data-confirm-message="Remove this image from the character gallery?">
|
|
<input type="hidden" name="imageId" value="@image.CharacterImageID" />
|
|
<input type="hidden" name="characterId" value="@Model.Character.CharacterID" />
|
|
<button class="btn btn-outline-danger btn-sm" type="submit">Remove</button>
|
|
</form>
|
|
</div>
|
|
</article>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
<section id="character-relationships" class="list-section mt-3">
|
|
<h2>Current Relationships <help-icon key="characters.currentRelationships" /></h2>
|
|
@if (!Model.CurrentRelationships.Any())
|
|
{
|
|
<p class="muted">No current relationship state yet.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="asset-mini-list">
|
|
@foreach (var categoryGroup in Model.CurrentRelationships.GroupBy(x => new { x.Relationship.RelationshipCategoryName, x.Relationship.CategorySortOrder }).OrderBy(x => x.Key.CategorySortOrder).ThenBy(x => x.Key.RelationshipCategoryName))
|
|
{
|
|
<h3 class="eyebrow">@categoryGroup.Key.RelationshipCategoryName</h3>
|
|
@foreach (var current in categoryGroup.OrderBy(x => x.Relationship.RelationshipTypeSortOrder).ThenBy(x => x.Relationship.CharacterAName).ThenBy(x => x.Relationship.CharacterBName))
|
|
{
|
|
var relationship = current.Relationship;
|
|
var relatedCharacterId = relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBID : relationship.CharacterAID;
|
|
var relatedCharacterName = relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBName : relationship.CharacterAName;
|
|
<div class="asset-mini-item">
|
|
<strong>@relatedCharacterName</strong>
|
|
<span>@relationship.RelationshipTypeName <span class="status-pill">@relationship.RelationshipCategoryName</span> / Current: @current.CurrentStateName</span>
|
|
<span>Reciprocal: @(relationship.IsReciprocal ? "Yes" : "No") / Confidence / intensity: @(current.CurrentIntensity?.ToString() ?? "Unknown")</span>
|
|
@if (!string.IsNullOrWhiteSpace(current.CurrentNotes))
|
|
{
|
|
<span>@current.CurrentNotes</span>
|
|
}
|
|
<a asp-action="Details" asp-route-id="@relatedCharacterId">Open related character</a>
|
|
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
|
|
<details>
|
|
<summary>View History</summary>
|
|
<div class="asset-mini-list mt-2">
|
|
<div class="asset-mini-item">
|
|
<strong>Initial: @relationship.RelationshipTypeName <span class="status-pill">@relationship.RelationshipCategoryName</span></strong>
|
|
<span>@(relationship.InitialRelationshipStateName ?? "No awareness state")@if (relationship.InitialIntensity.HasValue) { <text> / Intensity @relationship.InitialIntensity</text> }</span>
|
|
<span>Reader initially knows: @(relationship.ReaderInitiallyKnows ? "Yes" : "No")</span>
|
|
@if (!string.IsNullOrWhiteSpace(relationship.Notes))
|
|
{
|
|
<span>@relationship.Notes</span>
|
|
}
|
|
</div>
|
|
@foreach (var item in current.Events)
|
|
{
|
|
<div class="asset-mini-item">
|
|
<strong>@item.BookDisplayTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</strong>
|
|
<span>@item.RelationshipStateName <span class="status-pill">@item.RelationshipCategoryName</span> / Intensity @(item.Intensity?.ToString() ?? "Unknown")</span>
|
|
<span>Awareness: @(item.IsKnownToOtherCharacter ? "known to other character" : "not known to other character")</span>
|
|
@if (!string.IsNullOrWhiteSpace(item.Description))
|
|
{
|
|
<span>@item.Description</span>
|
|
}
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open scene</a>
|
|
</div>
|
|
}
|
|
<div class="asset-mini-item">
|
|
<strong>Current: @current.CurrentStateName</strong>
|
|
<span>Confidence / intensity: @(current.CurrentIntensity?.ToString() ?? "Unknown")</span>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
<section class="list-section mt-3">
|
|
<h2>Relationship Events <help-icon key="characters.relationshipEvents" /></h2>
|
|
@if (!Model.RelationshipEvents.Any())
|
|
{
|
|
<p class="muted">No relationship events yet.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="asset-mini-list">
|
|
@foreach (var item in Model.RelationshipEvents)
|
|
{
|
|
<div class="asset-mini-item">
|
|
<strong>@item.CharacterAName -> @item.CharacterBName: @item.RelationshipStateName</strong>
|
|
<span>@item.BookDisplayTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
|
<span>@item.RelationshipTypeName <span class="status-pill">@item.RelationshipCategoryName</span> / Awareness: @(item.IsKnownToOtherCharacter ? "known to other character" : "not known to other character")</span>
|
|
<span>Reciprocal: @(item.IsReciprocal ? "Yes" : "No") / Confidence / intensity: @(item.Intensity?.ToString() ?? "Unknown")</span>
|
|
@if (!string.IsNullOrWhiteSpace(item.Description))
|
|
{
|
|
<span>@item.Description</span>
|
|
}
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
|
|
<a asp-action="Details" asp-route-id="@(item.CharacterAID == Model.Character.CharacterID ? item.CharacterBID : item.CharacterAID)">Open related character</a>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
<section id="character-appearances" class="list-section mt-3">
|
|
<h2>Scene appearances <help-icon key="characters.sceneAppearances" /></h2>
|
|
@if (!Model.Appearances.Any())
|
|
{
|
|
<p class="muted">No scene appearances yet.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="asset-mini-list">
|
|
@foreach (var appearance in Model.Appearances)
|
|
{
|
|
<div class="asset-mini-item">
|
|
<strong>@appearance.BookDisplayTitle / Ch @appearance.ChapterNumber / Scene @appearance.SceneNumber: @appearance.SceneTitle</strong>
|
|
<span>@appearance.RoleInSceneTypeName / @appearance.PresenceTypeName</span>
|
|
<span>@appearance.OutfitDescription @appearance.PhysicalCondition @appearance.EmotionalState</span>
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@appearance.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
<section class="list-section mt-3">
|
|
<h2>Attribute history <help-icon key="characters.attributeHistory" /></h2>
|
|
@if (!Model.AttributeEvents.Any())
|
|
{
|
|
<p class="muted">No attribute events yet.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="asset-mini-list">
|
|
@foreach (var item in Model.AttributeEvents)
|
|
{
|
|
<div class="asset-mini-item">
|
|
<strong>@item.AttributeName: @item.AttributeValue</strong>
|
|
<span>@item.BookDisplayTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
|
<span>@item.Description</span>
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
<section class="list-section mt-3">
|
|
<h2>Knowledge <help-icon key="characters.knowledge" /></h2>
|
|
@if (!Model.KnowledgeItems.Any())
|
|
{
|
|
<p class="muted">No knowledge entries yet.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="asset-mini-list">
|
|
@foreach (var item in Model.KnowledgeItems)
|
|
{
|
|
<div class="asset-mini-item">
|
|
<strong>@item.KnowledgeStateName: @(item.AssetName ?? item.ThreadTitle)</strong>
|
|
<span>@item.BookDisplayTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
|
<span>@item.Description</span>
|
|
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
|
|
@section Scripts {
|
|
<script>
|
|
(() => {
|
|
document.querySelectorAll("[data-relationship-editor]").forEach((editor) => {
|
|
const read = editor.querySelector("[data-relationship-read]");
|
|
const form = editor.querySelector("[data-relationship-form]");
|
|
editor.querySelector("[data-relationship-edit]")?.addEventListener("click", () => {
|
|
read?.classList.add("d-none");
|
|
form?.classList.remove("d-none");
|
|
});
|
|
editor.querySelector("[data-relationship-cancel]")?.addEventListener("click", () => {
|
|
form?.classList.add("d-none");
|
|
read?.classList.remove("d-none");
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
}
|