231 lines
12 KiB
Plaintext

@model NamesLibraryViewModel
@{
ViewData["Title"] = $"Names Library - {Model.Project.ProjectName}";
ViewData["ProjectSection"] = "Names";
var filter = Model.Filter;
var previousPage = Math.Max(1, filter.Page - 1);
var nextPage = Math.Min(Model.TotalPages, filter.Page + 1);
var genderPreset = (filter.GenderPreference, filter.GenderThreshold) switch
{
("Female", >= 0.9m) => "female-very-strong",
("Female", >= 0.7m) => "female-strong",
("Female", _) => "female-mostly",
("Male", >= 0.9m) => "male-very-strong",
("Male", >= 0.7m) => "male-strong",
("Male", _) => "male-mostly",
("Mixed", _) => "mixed-balanced",
_ => string.Empty
};
string GenderLabel(NameSearchResult row)
{
if (!row.FemaleWeight.HasValue || !row.MaleWeight.HasValue)
{
return "No profile";
}
if (row.FemaleWeight >= 0.7m)
{
return "strong female prior";
}
if (row.MaleWeight >= 0.7m)
{
return "strong male prior";
}
return "mixed prior";
}
string EraLabel(NameSearchResult row)
=> row.BirthYearFrom.HasValue && row.BirthYearTo.HasValue
? $"established for {row.BirthYearFrom}-{row.BirthYearTo}"
: "no era profile";
}
<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>
<span>Names Library</span>
</nav>
<partial name="_ProjectSectionNav" model="Model.Project" />
<div class="page-heading compact">
<div>
<p class="eyebrow">Characters</p>
<h1>Names Library</h1>
<p class="lead-text">Search established given-name forms with era, locale and current-project usage context.</p>
</div>
</div>
<section class="edit-panel names-library-panel names-library-panel--search">
<form method="get" asp-action="Index" data-name-search-form>
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
<input type="hidden" name="Page" value="1" />
<input type="hidden" id="GenderPreference" name="GenderPreference" value="@filter.GenderPreference" data-gender-preference />
<input type="hidden" id="GenderThreshold" name="GenderThreshold" value="@filter.GenderThreshold" data-gender-threshold />
<div class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label" for="Search">Search</label>
<input class="form-control" id="Search" name="Search" value="@filter.Search" autocomplete="off" />
</div>
<div class="col-md-3">
<label class="form-label" for="LocaleCode">Locale</label>
<select class="form-select" id="LocaleCode" name="LocaleCode">
@foreach (var locale in Model.Locales)
{
<option value="@locale.LocaleCode" selected="@(string.Equals(filter.LocaleCode, locale.LocaleCode, StringComparison.OrdinalIgnoreCase))">@locale.DisplayName</option>
}
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="BirthYear">Birth year</label>
<input class="form-control" id="BirthYear" name="BirthYear" value="@filter.BirthYear" inputmode="numeric" />
</div>
<div class="col-md-3">
<label class="form-label" for="GenderPreset">Gender prior</label>
<select class="form-select" id="GenderPreset" data-gender-preset>
<option value="" selected="@(genderPreset == string.Empty)">Any</option>
<option value="female-mostly" data-preference="Female" data-threshold="0.5" selected="@(genderPreset == "female-mostly")">Mostly female</option>
<option value="female-strong" data-preference="Female" data-threshold="0.7" selected="@(genderPreset == "female-strong")">Strongly female</option>
<option value="female-very-strong" data-preference="Female" data-threshold="0.9" selected="@(genderPreset == "female-very-strong")">Very strongly female</option>
<option value="male-mostly" data-preference="Male" data-threshold="0.5" selected="@(genderPreset == "male-mostly")">Mostly male</option>
<option value="male-strong" data-preference="Male" data-threshold="0.7" selected="@(genderPreset == "male-strong")">Strongly male</option>
<option value="male-very-strong" data-preference="Male" data-threshold="0.9" selected="@(genderPreset == "male-very-strong")">Very strongly male</option>
<option value="mixed-balanced" data-preference="Mixed" data-threshold="0.65" selected="@(genderPreset == "mixed-balanced")">Balanced / mixed</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="NameType">Name type</label>
<select class="form-select" id="NameType" name="NameType">
<option value="">Any</option>
@foreach (var type in new[] { "Canonical", "Independent", "Diminutive", "Nickname", "Variant" })
{
<option value="@type" selected="@(filter.NameType == type)">@type</option>
}
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="MinimumUsage">Commonness</label>
<select class="form-select" id="MinimumUsage" name="MinimumUsage">
<option value="" selected="@(!filter.MinimumUsage.HasValue)">Any</option>
<option value="0.01" selected="@(filter.MinimumUsage == 0.01m)">Rare or above</option>
<option value="0.05" selected="@(filter.MinimumUsage == 0.05m)">Familiar</option>
<option value="0.25" selected="@(filter.MinimumUsage == 0.25m)">Common</option>
<option value="0.6" selected="@(filter.MinimumUsage == 0.6m)">Very common</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="BookID">Used scope</label>
<select class="form-select" id="BookID" name="BookID">
<option value="">Project</option>
@foreach (var book in Model.Books)
{
<option value="@book.BookID" selected="@(filter.BookID == book.BookID)">@book.BookDisplayTitle</option>
}
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="UsedOnly">Usage</label>
<select class="form-select" id="UsedOnly" name="UsedOnly">
<option value="">Used and unused</option>
<option value="true" selected="@(filter.UsedOnly == true)">Already used</option>
<option value="false" selected="@(filter.UsedOnly == false)">Unused</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="PageSize">Rows</label>
<select class="form-select" id="PageSize" name="PageSize">
@foreach (var size in new[] { 25, 50, 100 })
{
<option value="@size" selected="@(filter.PageSize == size)">@size</option>
}
</select>
</div>
<div class="col-md-3">
<button class="btn btn-primary w-100" type="submit">Search</button>
</div>
</div>
</form>
</section>
<section class="story-bible-section names-library-panel names-library-panel--results">
<div class="d-flex justify-content-between align-items-center mb-2">
<h2>Results</h2>
<span class="muted">@Model.TotalRows.ToString("N0") names</span>
</div>
@if (!Model.Results.Any())
{
<p class="muted">No matching names found.</p>
}
else
{
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>Name</th>
<th>Profile</th>
<th>Related forms</th>
<th>Current use</th>
</tr>
</thead>
<tbody>
@foreach (var row in Model.Results)
{
<tr>
<td>
<strong>@row.DisplayName</strong>
<div class="muted small">@row.NameType@if (row.IsWordAmbiguous){<span> · ambiguous word</span>}</div>
</td>
<td>
<span>@GenderLabel(row); @EraLabel(row)</span>
<div class="muted small">@row.LocaleDisplayName · usage @row.UsageWeight?.ToString("0.000") · confidence @row.Confidence?.ToString("0.000")</div>
</td>
<td>@(string.IsNullOrWhiteSpace(row.RelatedForms) ? "None listed" : row.RelatedForms)</td>
<td>
@if (row.UsedCount > 0)
{
<span class="badge bg-warning text-dark">Already used</span>
<div class="muted small">@row.UsedSummary</div>
}
else
{
<span class="badge bg-success">Unused here</span>
}
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="button-row">
<a class="btn btn-outline-secondary @(filter.Page <= 1 ? "disabled" : string.Empty)" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-search="@filter.Search" asp-route-localeCode="@filter.LocaleCode" asp-route-birthYear="@filter.BirthYear" asp-route-genderPreference="@filter.GenderPreference" asp-route-genderThreshold="@filter.GenderThreshold" asp-route-nameType="@filter.NameType" asp-route-minimumUsage="@filter.MinimumUsage" asp-route-bookId="@filter.BookID" asp-route-usedOnly="@filter.UsedOnly" asp-route-page="@previousPage" asp-route-pageSize="@filter.PageSize">Previous</a>
<span class="muted">Page @filter.Page of @Model.TotalPages</span>
<a class="btn btn-outline-secondary @(filter.Page >= Model.TotalPages ? "disabled" : string.Empty)" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-search="@filter.Search" asp-route-localeCode="@filter.LocaleCode" asp-route-birthYear="@filter.BirthYear" asp-route-genderPreference="@filter.GenderPreference" asp-route-genderThreshold="@filter.GenderThreshold" asp-route-nameType="@filter.NameType" asp-route-minimumUsage="@filter.MinimumUsage" asp-route-bookId="@filter.BookID" asp-route-usedOnly="@filter.UsedOnly" asp-route-page="@nextPage" asp-route-pageSize="@filter.PageSize">Next</a>
</div>
}
</section>
@section Scripts {
<script>
(() => {
const root = document.querySelector("[data-name-search-form]");
if (!root) return;
const preset = root.querySelector("[data-gender-preset]");
const preference = root.querySelector("[data-gender-preference]");
const threshold = root.querySelector("[data-gender-threshold]");
const sync = () => {
const selected = preset?.selectedOptions?.[0];
preference.value = selected?.dataset.preference || "";
threshold.value = selected?.dataset.threshold || "";
};
preset?.addEventListener("change", sync);
sync();
})();
</script>
}