Fix onboarding scan review wizard flow
This commit is contained in:
parent
72d2438314
commit
d835bafb5c
@ -48,6 +48,22 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
|||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("scan-characters")]
|
||||||
|
public async Task<IActionResult> ScanCharacters(Guid? previewId)
|
||||||
|
{
|
||||||
|
var model = await onboarding.GetScanReviewAsync(previewId)
|
||||||
|
?? (previewId.HasValue ? await onboarding.GetScanReviewAsync() : null);
|
||||||
|
if (model is null)
|
||||||
|
{
|
||||||
|
TempData["ArchiveError"] = "The character review could not be opened. Please scan the manuscript again.";
|
||||||
|
await onboarding.SetCurrentStepAsync(OnboardingSteps.NextPathPreview);
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData["ScanReviewStep"] = "characters";
|
||||||
|
return View("ScanReview", model);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("build-complete")]
|
[HttpGet("build-complete")]
|
||||||
public async Task<IActionResult> BuildComplete(Guid previewId)
|
public async Task<IActionResult> BuildComplete(Guid previewId)
|
||||||
{
|
{
|
||||||
@ -499,6 +515,7 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
|||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> SaveScanReview(ManuscriptScanReviewForm form, string intent = "save")
|
public async Task<IActionResult> SaveScanReview(ManuscriptScanReviewForm form, string intent = "save")
|
||||||
{
|
{
|
||||||
|
var continueToCharacters = string.Equals(intent, "characters", StringComparison.OrdinalIgnoreCase);
|
||||||
var readyToImport = string.Equals(intent, "continue", StringComparison.OrdinalIgnoreCase);
|
var readyToImport = string.Equals(intent, "continue", StringComparison.OrdinalIgnoreCase);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -510,9 +527,11 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
|||||||
|
|
||||||
TempData["OnboardingReviewMessage"] = readyToImport
|
TempData["OnboardingReviewMessage"] = readyToImport
|
||||||
? "Review saved. Next: prepare chapters and analyse manuscript."
|
? "Review saved. Next: prepare chapters and analyse manuscript."
|
||||||
|
: continueToCharacters ? "Chapter and scene review saved. Next: review detected characters."
|
||||||
: "Review choices saved.";
|
: "Review choices saved.";
|
||||||
return readyToImport
|
return readyToImport
|
||||||
? RedirectToAction(nameof(StoryIntelligence))
|
? RedirectToAction(nameof(StoryIntelligence))
|
||||||
|
: continueToCharacters ? RedirectToAction(nameof(ScanCharacters), new { previewId = form.PreviewID })
|
||||||
: RedirectToAction(nameof(ScanReview), new { previewId = form.PreviewID });
|
: RedirectToAction(nameof(ScanReview), new { previewId = form.PreviewID });
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
@ -524,6 +543,11 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (readyToImport)
|
||||||
|
{
|
||||||
|
ViewData["ScanReviewStep"] = "characters";
|
||||||
|
}
|
||||||
|
|
||||||
return View("ScanReview", model);
|
return View("ScanReview", model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ public interface IOnboardingRepository
|
|||||||
Task<UserOnboardingState> SetCurrentStepAsync(int userId, string currentStep);
|
Task<UserOnboardingState> SetCurrentStepAsync(int userId, string currentStep);
|
||||||
Task<UserOnboardingState> SaveWritingJourneyAsync(int userId, string writingJourney);
|
Task<UserOnboardingState> SaveWritingJourneyAsync(int userId, string writingJourney);
|
||||||
Task<UserOnboardingState> SaveWritingSoftwareAsync(int userId, string writingSoftware);
|
Task<UserOnboardingState> SaveWritingSoftwareAsync(int userId, string writingSoftware);
|
||||||
|
Task<UserOnboardingState> ClearWritingSoftwareAsync(int userId);
|
||||||
Task<UserOnboardingState> SaveProjectAsync(int userId, int projectId);
|
Task<UserOnboardingState> SaveProjectAsync(int userId, int projectId);
|
||||||
Task<UserOnboardingState> SaveBookAsync(int userId, int bookId);
|
Task<UserOnboardingState> SaveBookAsync(int userId, int bookId);
|
||||||
Task<UserOnboardingState> MarkCompletedAsync(int userId);
|
Task<UserOnboardingState> MarkCompletedAsync(int userId);
|
||||||
@ -64,6 +65,26 @@ public sealed class OnboardingRepository(ISqlConnectionFactory connectionFactory
|
|||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<UserOnboardingState> ClearWritingSoftwareAsync(int userId)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"""
|
||||||
|
UPDATE dbo.UserOnboardingState
|
||||||
|
SET WritingSoftware = NULL,
|
||||||
|
UsesWordCompanion = 0,
|
||||||
|
CurrentStep = N'WritingSoftware',
|
||||||
|
UpdatedUtc = SYSUTCDATETIME()
|
||||||
|
WHERE UserID = @UserID;
|
||||||
|
""",
|
||||||
|
new { UserID = userId });
|
||||||
|
|
||||||
|
return await connection.QuerySingleAsync<UserOnboardingState>(
|
||||||
|
"dbo.UserOnboarding_Get",
|
||||||
|
new { UserID = userId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<UserOnboardingState> SaveProjectAsync(int userId, int projectId)
|
public async Task<UserOnboardingState> SaveProjectAsync(int userId, int projectId)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
|||||||
@ -80,7 +80,7 @@ public sealed class OnboardingService(
|
|||||||
{
|
{
|
||||||
CurrentStep = step,
|
CurrentStep = step,
|
||||||
StepNumber = StepNumber(step),
|
StepNumber = StepNumber(step),
|
||||||
TotalSteps = 9,
|
TotalSteps = 10,
|
||||||
WritingJourney = state.WritingJourney,
|
WritingJourney = state.WritingJourney,
|
||||||
WritingSoftware = state.WritingSoftware,
|
WritingSoftware = state.WritingSoftware,
|
||||||
ProjectID = state.ProjectID,
|
ProjectID = state.ProjectID,
|
||||||
@ -659,10 +659,10 @@ public sealed class OnboardingService(
|
|||||||
public async Task StartManuscriptImportSetupAsync()
|
public async Task StartManuscriptImportSetupAsync()
|
||||||
{
|
{
|
||||||
var userId = RequireUserId();
|
var userId = RequireUserId();
|
||||||
await onboarding.SaveWritingJourneyAsync(userId, WritingJourneyValues.ManuscriptMostlyComplete);
|
var state = await onboarding.SaveWritingJourneyAsync(userId, WritingJourneyValues.ManuscriptMostlyComplete);
|
||||||
var state = await onboarding.SaveWritingSoftwareAsync(userId, WritingSoftwareValues.MicrosoftWord);
|
state = await onboarding.ClearWritingSoftwareAsync(userId);
|
||||||
await scanPreviews.ClearAsync(userId, state.UserOnboardingStateID);
|
await scanPreviews.ClearAsync(userId, state.UserOnboardingStateID);
|
||||||
await onboarding.SetCurrentStepAsync(userId, OnboardingSteps.Project);
|
await onboarding.SetCurrentStepAsync(userId, OnboardingSteps.WritingSoftware);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BeginManuscriptImportForBookAsync(int projectId, int bookId)
|
public async Task BeginManuscriptImportForBookAsync(int projectId, int bookId)
|
||||||
|
|||||||
@ -229,7 +229,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="onboarding-copy">
|
<div class="onboarding-copy">
|
||||||
<p class="eyebrow">Step @currentJourneyOrder of 9</p>
|
<p class="eyebrow">Step @currentJourneyOrder of 10</p>
|
||||||
@if (Model.IsMicrosoftWordPath)
|
@if (Model.IsMicrosoftWordPath)
|
||||||
{
|
{
|
||||||
<h1 id="onboarding-title">@(currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")</h1>
|
<h1 id="onboarding-title">@(currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")</h1>
|
||||||
@ -460,16 +460,17 @@
|
|||||||
return model.CurrentStep switch
|
return model.CurrentStep switch
|
||||||
{
|
{
|
||||||
OnboardingSteps.Welcome => 1,
|
OnboardingSteps.Welcome => 1,
|
||||||
OnboardingSteps.WritingJourney or OnboardingSteps.WritingSoftware => 2,
|
OnboardingSteps.WritingJourney => 2,
|
||||||
OnboardingSteps.Project => 3,
|
OnboardingSteps.WritingSoftware => 3,
|
||||||
OnboardingSteps.Book => 4,
|
OnboardingSteps.Project => 4,
|
||||||
OnboardingSteps.NextPathPreview when !model.IsMicrosoftWordPath => 9,
|
OnboardingSteps.Book => 5,
|
||||||
OnboardingSteps.NextPathPreview when string.Equals(model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal) => 8,
|
OnboardingSteps.NextPathPreview when !model.IsMicrosoftWordPath => 10,
|
||||||
|
OnboardingSteps.NextPathPreview when string.Equals(model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal) => 9,
|
||||||
OnboardingSteps.NextPathPreview when model.ScanState.IsComplete => 7,
|
OnboardingSteps.NextPathPreview when model.ScanState.IsComplete => 7,
|
||||||
OnboardingSteps.NextPathPreview when model.ScanState.IsRunning || model.ScanState.IsFailed || model.ScanState.PercentComplete.HasValue => 6,
|
OnboardingSteps.NextPathPreview when model.ScanState.IsRunning || model.ScanState.IsFailed || model.ScanState.PercentComplete.HasValue => 6,
|
||||||
OnboardingSteps.NextPathPreview when model.CompanionPresence.IsConnected => 6,
|
OnboardingSteps.NextPathPreview when model.CompanionPresence.IsConnected => 6,
|
||||||
OnboardingSteps.NextPathPreview => 5,
|
OnboardingSteps.NextPathPreview => 6,
|
||||||
OnboardingSteps.Complete => 9,
|
OnboardingSteps.Complete => 10,
|
||||||
_ => 1
|
_ => 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,18 +18,20 @@
|
|||||||
.OrderBy(candidate => candidate.ReviewName)
|
.OrderBy(candidate => candidate.ReviewName)
|
||||||
.ToList();
|
.ToList();
|
||||||
var visibleCharacterCount = probableCharacters.Count + possibleCharacters.Count + relationshipTitles.Count;
|
var visibleCharacterCount = probableCharacters.Count + possibleCharacters.Count + relationshipTitles.Count;
|
||||||
|
var isCharacterStep = string.Equals(ViewData["ScanReviewStep"] as string, "characters", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var currentStep = isCharacterStep ? 8 : 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
<section class="onboarding-shell" aria-labelledby="scan-review-title">
|
<section class="onboarding-shell" aria-labelledby="scan-review-title">
|
||||||
<form class="onboarding-panel onboarding-review-panel" asp-controller="Onboarding" asp-action="SaveScanReview" method="post">
|
<form class="onboarding-panel onboarding-review-panel" asp-controller="Onboarding" asp-action="SaveScanReview" method="post">
|
||||||
@Html.AntiForgeryToken()
|
@Html.AntiForgeryToken()
|
||||||
<input type="hidden" asp-for="PreviewID" name="PreviewID" />
|
<input type="hidden" asp-for="PreviewID" name="PreviewID" />
|
||||||
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 7 })" />
|
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = currentStep, TotalSteps = 10 })" />
|
||||||
|
|
||||||
<div class="onboarding-copy">
|
<div class="onboarding-copy">
|
||||||
<p class="eyebrow">Step 7 of 9</p>
|
<p class="eyebrow">Step @currentStep of 10</p>
|
||||||
<h1 id="scan-review-title">Review Manuscript Scan</h1>
|
<h1 id="scan-review-title">@(isCharacterStep ? "Review Characters" : "Review Chapters")</h1>
|
||||||
<p>Confirm the chapter structure first, then review the detected character names and aliases before Story Intelligence begins.</p>
|
<p>@(isCharacterStep ? "Review detected character names, aliases and existing character matches before Story Intelligence begins." : "Confirm the chapter and scene structure before reviewing detected characters.")</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
||||||
@ -57,6 +59,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (!isCharacterStep)
|
||||||
|
{
|
||||||
<div class="onboarding-review-tools" data-review-tools>
|
<div class="onboarding-review-tools" data-review-tools>
|
||||||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bulk-target="chapter" data-bulk-state="true">Include all chapters</button>
|
<button class="btn btn-sm btn-outline-secondary" type="button" data-bulk-target="chapter" data-bulk-state="true">Include all chapters</button>
|
||||||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bulk-target="chapter" data-bulk-state="false">Exclude all chapters</button>
|
<button class="btn btn-sm btn-outline-secondary" type="button" data-bulk-target="chapter" data-bulk-state="false">Exclude all chapters</button>
|
||||||
@ -64,14 +68,10 @@
|
|||||||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bulk-target="scene" data-bulk-state="false">Exclude all scenes</button>
|
<button class="btn btn-sm btn-outline-secondary" type="button" data-bulk-target="scene" data-bulk-state="false">Exclude all scenes</button>
|
||||||
<button class="btn btn-sm btn-outline-secondary" type="reset">Restore scan defaults</button>
|
<button class="btn btn-sm btn-outline-secondary" type="reset">Restore scan defaults</button>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<section class="onboarding-review-workflow" aria-label="Scan preview" data-scan-review-tabs>
|
<section class="onboarding-review-workflow" aria-label="Scan preview">
|
||||||
<div class="onboarding-review-tabs" role="tablist" aria-label="Scan review sections">
|
<div class="onboarding-review-section" data-scan-review-panel="structure" hidden="@isCharacterStep">
|
||||||
<button class="is-active" type="button" role="tab" aria-selected="true" data-scan-review-tab="structure">1. Chapters and scenes</button>
|
|
||||||
<button type="button" role="tab" aria-selected="false" data-scan-review-tab="characters">2. Characters</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="onboarding-review-section" data-scan-review-panel="structure">
|
|
||||||
<h2>Chapters and scenes</h2>
|
<h2>Chapters and scenes</h2>
|
||||||
@if (!Model.Chapters.Any())
|
@if (!Model.Chapters.Any())
|
||||||
{
|
{
|
||||||
@ -150,17 +150,17 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="onboarding-actions onboarding-actions--inline">
|
<div class="onboarding-actions onboarding-actions--inline">
|
||||||
<button class="btn btn-primary" type="button" data-scan-review-next="characters">Continue to characters</button>
|
<button class="btn btn-primary" type="submit" name="intent" value="characters">Save and review characters</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="onboarding-review-section" data-scan-review-panel="characters" hidden>
|
<aside class="onboarding-review-section" data-scan-review-panel="characters" hidden="@(!isCharacterStep)">
|
||||||
<div class="onboarding-review-section-heading">
|
<div class="onboarding-review-section-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>Character candidates</h2>
|
<h2>Character candidates</h2>
|
||||||
<p>@visibleCharacterCount.ToString("N0") detected candidate@(visibleCharacterCount == 1 ? string.Empty : "s") before excluded names.</p>
|
<p>@visibleCharacterCount.ToString("N0") detected candidate@(visibleCharacterCount == 1 ? string.Empty : "s") before excluded names.</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-outline-secondary btn-sm" type="button" data-scan-review-next="structure">Back to chapters</button>
|
<a class="btn btn-outline-secondary btn-sm" asp-action="ScanReview" asp-route-previewId="@Model.PreviewID">Back to chapters</a>
|
||||||
</div>
|
</div>
|
||||||
@if (!probableCharacters.Any() && !possibleCharacters.Any() && !relationshipTitles.Any())
|
@if (!probableCharacters.Any() && !possibleCharacters.Any() && !relationshipTitles.Any())
|
||||||
{
|
{
|
||||||
@ -257,7 +257,7 @@
|
|||||||
<ul class="onboarding-character-candidates">
|
<ul class="onboarding-character-candidates">
|
||||||
@foreach (var candidate in excludedCharacters)
|
@foreach (var candidate in excludedCharacters)
|
||||||
{
|
{
|
||||||
<li data-character-name="@candidate.ReviewName" data-character-reason="@candidate.Reason" data-character-page-item>
|
<li data-character-name="@candidate.ReviewName" data-character-reason="@candidate.Reason" data-character-excluded-item>
|
||||||
<input type="hidden" name="Characters[@characterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
<input type="hidden" name="Characters[@characterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
||||||
<input type="hidden" name="Characters[@characterIndex].Category" value="@candidate.Category" />
|
<input type="hidden" name="Characters[@characterIndex].Category" value="@candidate.Category" />
|
||||||
<label class="onboarding-review-toggle">
|
<label class="onboarding-review-toggle">
|
||||||
@ -312,39 +312,19 @@
|
|||||||
|
|
||||||
<div class="onboarding-actions">
|
<div class="onboarding-actions">
|
||||||
<a class="btn btn-outline-secondary" asp-controller="Onboarding" asp-action="Index">Back to setup</a>
|
<a class="btn btn-outline-secondary" asp-controller="Onboarding" asp-action="Index">Back to setup</a>
|
||||||
|
@if (isCharacterStep)
|
||||||
|
{
|
||||||
<button class="btn btn-primary" type="submit" name="intent" value="continue">Save and continue</button>
|
<button class="btn btn-primary" type="submit" name="intent" value="continue">Save and continue</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button class="btn btn-primary" type="submit" name="intent" value="characters">Save and review characters</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const scanReviewRoot = document.querySelector("[data-scan-review-tabs]");
|
|
||||||
const showScanPanel = (panelName) => {
|
|
||||||
if (!scanReviewRoot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scanReviewRoot.querySelectorAll("[data-scan-review-tab]").forEach((tab) => {
|
|
||||||
const isActive = tab.dataset.scanReviewTab === panelName;
|
|
||||||
tab.classList.toggle("is-active", isActive);
|
|
||||||
tab.setAttribute("aria-selected", String(isActive));
|
|
||||||
});
|
|
||||||
scanReviewRoot.querySelectorAll("[data-scan-review-panel]").forEach((panel) => {
|
|
||||||
panel.hidden = panel.dataset.scanReviewPanel !== panelName;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
scanReviewRoot?.addEventListener("click", (event) => {
|
|
||||||
const tab = event.target.closest("[data-scan-review-tab]");
|
|
||||||
if (tab) {
|
|
||||||
showScanPanel(tab.dataset.scanReviewTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = event.target.closest("[data-scan-review-next]");
|
|
||||||
if (next) {
|
|
||||||
showScanPanel(next.dataset.scanReviewNext);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const reviewTools = document.querySelector("[data-review-tools]");
|
const reviewTools = document.querySelector("[data-review-tools]");
|
||||||
reviewTools?.addEventListener("click", (event) => {
|
reviewTools?.addEventListener("click", (event) => {
|
||||||
const button = event.target.closest("[data-bulk-target]");
|
const button = event.target.closest("[data-bulk-target]");
|
||||||
@ -368,6 +348,7 @@
|
|||||||
let characterPage = 1;
|
let characterPage = 1;
|
||||||
|
|
||||||
const characterItems = () => [...document.querySelectorAll("[data-character-page-item]")];
|
const characterItems = () => [...document.querySelectorAll("[data-character-page-item]")];
|
||||||
|
const excludedCharacterItems = () => [...document.querySelectorAll("[data-character-excluded-item]")];
|
||||||
const matchingCharacterItems = () => {
|
const matchingCharacterItems = () => {
|
||||||
const query = characterFilter?.value.trim().toLowerCase() || "";
|
const query = characterFilter?.value.trim().toLowerCase() || "";
|
||||||
return characterItems().filter((item) => {
|
return characterItems().filter((item) => {
|
||||||
@ -379,11 +360,14 @@
|
|||||||
const refreshCharacterSections = () => {
|
const refreshCharacterSections = () => {
|
||||||
document.querySelectorAll(".onboarding-character-groups section").forEach((section) => {
|
document.querySelectorAll(".onboarding-character-groups section").forEach((section) => {
|
||||||
const hasVisibleItems = [...section.querySelectorAll("[data-character-page-item]")].some((item) => !item.hidden);
|
const hasVisibleItems = [...section.querySelectorAll("[data-character-page-item]")].some((item) => !item.hidden);
|
||||||
|
if (!section.closest(".onboarding-excluded-candidates")) {
|
||||||
section.hidden = !hasVisibleItems;
|
section.hidden = !hasVisibleItems;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCharacterPage = () => {
|
const renderCharacterPage = () => {
|
||||||
|
const query = characterFilter?.value.trim().toLowerCase() || "";
|
||||||
const matches = matchingCharacterItems();
|
const matches = matchingCharacterItems();
|
||||||
const pageCount = Math.max(1, Math.ceil(matches.length / characterPageSize));
|
const pageCount = Math.max(1, Math.ceil(matches.length / characterPageSize));
|
||||||
characterPage = Math.min(Math.max(characterPage, 1), pageCount);
|
characterPage = Math.min(Math.max(characterPage, 1), pageCount);
|
||||||
@ -413,9 +397,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
refreshCharacterSections();
|
refreshCharacterSections();
|
||||||
|
excludedCharacterItems().forEach((item) => {
|
||||||
|
const text = `${item.dataset.characterName || ""} ${item.dataset.characterReason || ""}`.toLowerCase();
|
||||||
|
item.hidden = query.length > 0 && !text.includes(query);
|
||||||
|
});
|
||||||
|
|
||||||
const emptyState = document.querySelector("[data-character-empty]");
|
const emptyState = document.querySelector("[data-character-empty]");
|
||||||
if (emptyState) {
|
if (emptyState) {
|
||||||
emptyState.hidden = matches.length > 0;
|
emptyState.hidden = matches.length > 0 || excludedCharacterItems().some((item) => !item.hidden);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
<section class="onboarding-shell" aria-labelledby="story-intelligence-title">
|
<section class="onboarding-shell" aria-labelledby="story-intelligence-title">
|
||||||
<div class="onboarding-panel onboarding-analysis-panel">
|
<div class="onboarding-panel onboarding-analysis-panel">
|
||||||
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 8 })" />
|
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 9, TotalSteps = 10 })" />
|
||||||
<div class="onboarding-copy">
|
<div class="onboarding-copy">
|
||||||
<p class="eyebrow">Step 8 of 9</p>
|
<p class="eyebrow">Step 9 of 10</p>
|
||||||
<h1 id="story-intelligence-title">Analyse Manuscript</h1>
|
<h1 id="story-intelligence-title">Analyse Manuscript</h1>
|
||||||
<p>Set the analysis context, then PlotDirector will read the approved chapters and prepare scenes, characters, locations, assets, relationships and knowledge for review.</p>
|
<p>Set the analysis context, then PlotDirector will read the approved chapters and prepare scenes, characters, locations, assets, relationships and knowledge for review.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,14 +3,15 @@
|
|||||||
var steps = new[]
|
var steps = new[]
|
||||||
{
|
{
|
||||||
new { Label = "Welcome", Order = 1 },
|
new { Label = "Welcome", Order = 1 },
|
||||||
new { Label = "Writing Preferences", Order = 2 },
|
new { Label = "Writing Stage", Order = 2 },
|
||||||
new { Label = "Project", Order = 3 },
|
new { Label = "Writing Software", Order = 3 },
|
||||||
new { Label = "Book", Order = 4 },
|
new { Label = "Project", Order = 4 },
|
||||||
new { Label = "Connect Word", Order = 5 },
|
new { Label = "Book", Order = 5 },
|
||||||
new { Label = "Scan Manuscript", Order = 6 },
|
new { Label = "Connect Word", Order = 6 },
|
||||||
new { Label = "Review Chapters", Order = 7 },
|
new { Label = "Review Chapters", Order = 7 },
|
||||||
new { Label = "Analyse Manuscript", Order = 8 },
|
new { Label = "Review Characters", Order = 8 },
|
||||||
new { Label = "Complete", Order = 9 }
|
new { Label = "Analyse Manuscript", Order = 9 },
|
||||||
|
new { Label = "Complete", Order = 10 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
.onboarding-stepper {
|
.onboarding-stepper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(9, minmax(0, 1fr));
|
grid-template-columns: repeat(10, minmax(0, 1fr));
|
||||||
gap: .45rem;
|
gap: .45rem;
|
||||||
margin: 0 0 2rem;
|
margin: 0 0 2rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -482,33 +482,6 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-review-tabs {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: .35rem;
|
|
||||||
border: 1px solid rgba(31, 42, 68, .14);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: .35rem;
|
|
||||||
background: rgba(255, 255, 255, .58);
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-review-tabs button {
|
|
||||||
min-height: 2.7rem;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--bs-secondary-color);
|
|
||||||
font: inherit;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-review-tabs button.is-active {
|
|
||||||
border-color: rgba(47, 111, 99, .34);
|
|
||||||
background: rgba(47, 111, 99, .11);
|
|
||||||
color: var(--bs-body-color);
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(47, 111, 99, .08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-review-section {
|
.onboarding-review-section {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
@ -1776,10 +1749,6 @@ summary.story-review-chapter-heading {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-review-tabs {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-story-facts {
|
.onboarding-story-facts {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user