Add explicit Word save confirmation for import
This commit is contained in:
parent
e0c3171329
commit
f76e5bc388
@ -1926,10 +1926,14 @@ static void WordSyncRequiresSaveAcknowledgement()
|
|||||||
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
var companion = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-host.js"));
|
var companion = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-host.js"));
|
||||||
var hub = File.ReadAllText(Path.Combine(root, "Hubs/WordCompanionFollowHub.cs"));
|
var hub = File.ReadAllText(Path.Combine(root, "Hubs/WordCompanionFollowHub.cs"));
|
||||||
|
var models = File.ReadAllText(Path.Combine(root, "Models/ManuscriptScanModels.cs"));
|
||||||
var presence = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-presence.js"));
|
var presence = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-presence.js"));
|
||||||
|
var onboardingView = File.ReadAllText(Path.Combine(root, "Views/Onboarding/Index.cshtml"));
|
||||||
|
var wordHostView = File.ReadAllText(Path.Combine(root, "Views/WordCompanionHost/Index.cshtml"));
|
||||||
var migration = File.ReadAllText(Path.Combine(root, "Sql/158_Phase25C_OnboardingMarkerSaveStates.sql"));
|
var migration = File.ReadAllText(Path.Combine(root, "Sql/158_Phase25C_OnboardingMarkerSaveStates.sql"));
|
||||||
|
|
||||||
Assert(companion.Contains("saveWordDocumentIfAvailable", StringComparison.Ordinal), "Companion should save Word before acknowledging marker sync.");
|
Assert(!companion.Contains("window.confirm(`${manualMessage}", StringComparison.Ordinal), "Companion should not use a blocking browser confirmation for manual Word saves.");
|
||||||
|
Assert(companion.Contains("setOnboardingManualSavePrompt", StringComparison.Ordinal), "Companion should show an explicit manual-save prompt after adding markers.");
|
||||||
Assert(companion.Contains("markersChanged", StringComparison.Ordinal), "Companion should distinguish marker insertion from an idempotent no-change retry.");
|
Assert(companion.Contains("markersChanged", StringComparison.Ordinal), "Companion should distinguish marker insertion from an idempotent no-change retry.");
|
||||||
Assert(companion.Contains("SaveNotRequired", StringComparison.Ordinal), "Companion should acknowledge marker sync when no Word save is required.");
|
Assert(companion.Contains("SaveNotRequired", StringComparison.Ordinal), "Companion should acknowledge marker sync when no Word save is required.");
|
||||||
Assert(companion.Contains("UserConfirmedSave", StringComparison.Ordinal), "Companion should support an explicit manual save acknowledgement.");
|
Assert(companion.Contains("UserConfirmedSave", StringComparison.Ordinal), "Companion should support an explicit manual save acknowledgement.");
|
||||||
@ -1940,7 +1944,14 @@ static void WordSyncRequiresSaveAcknowledgement()
|
|||||||
Assert(hub.Contains("MarkerSyncClientResult", StringComparison.Ordinal), "Server should accept marker sync diagnostics.");
|
Assert(hub.Contains("MarkerSyncClientResult", StringComparison.Ordinal), "Server should accept marker sync diagnostics.");
|
||||||
Assert(hub.Contains("MarkMarkerSyncCompletedAsync", StringComparison.Ordinal), "Server should persist completed marker sync.");
|
Assert(hub.Contains("MarkMarkerSyncCompletedAsync", StringComparison.Ordinal), "Server should persist completed marker sync.");
|
||||||
Assert(hub.Contains("MarkMarkerSyncUserSaveRequiredAsync", StringComparison.Ordinal), "Server should persist manual-save-required marker sync.");
|
Assert(hub.Contains("MarkMarkerSyncUserSaveRequiredAsync", StringComparison.Ordinal), "Server should persist manual-save-required marker sync.");
|
||||||
|
Assert(hub.Contains("OnboardingBuildManualSaveRequired", StringComparison.Ordinal), "Server should notify the browser when Word needs a manual save.");
|
||||||
|
Assert(models.Contains("public int? BuildID", StringComparison.Ordinal), "Build progress should identify the build awaiting manual save.");
|
||||||
|
Assert(models.Contains("public string? MarkerStatus", StringComparison.Ordinal), "Build progress should carry marker sync state to the browser.");
|
||||||
Assert(presence.Contains("OnboardingBuildMarkerSyncCompleted", StringComparison.Ordinal), "Browser should wait for marker sync completion before redirecting.");
|
Assert(presence.Contains("OnboardingBuildMarkerSyncCompleted", StringComparison.Ordinal), "Browser should wait for marker sync completion before redirecting.");
|
||||||
|
Assert(presence.Contains("OnboardingBuildManualSaveRequired", StringComparison.Ordinal), "Browser should render manual-save-required progress events.");
|
||||||
|
Assert(presence.Contains("CompleteOnboardingBuildMarkerSync\", pendingManualSaveBuildId", StringComparison.Ordinal), "Browser should confirm manual Word saves back to the server.");
|
||||||
|
Assert(onboardingView.Contains("data-onboarding-build-save-confirm", StringComparison.Ordinal), "Onboarding page should expose a manual Word save confirmation action.");
|
||||||
|
Assert(wordHostView.Contains("data-onboarding-manual-save-confirm", StringComparison.Ordinal), "Word Companion should expose a manual Word save confirmation action.");
|
||||||
Assert(migration.Contains("MarkerSyncCompletedUtc", StringComparison.Ordinal), "Database should persist marker sync completion time.");
|
Assert(migration.Contains("MarkerSyncCompletedUtc", StringComparison.Ordinal), "Database should persist marker sync completion time.");
|
||||||
Assert(migration.Contains("UserSaveRequired", StringComparison.Ordinal), "Database should persist manual-save-required marker sync state.");
|
Assert(migration.Contains("UserSaveRequired", StringComparison.Ordinal), "Database should persist manual-save-required marker sync state.");
|
||||||
Assert(migration.Contains("SaveNotRequired", StringComparison.Ordinal), "Database should persist no-change marker sync state.");
|
Assert(migration.Contains("SaveNotRequired", StringComparison.Ordinal), "Database should persist no-change marker sync state.");
|
||||||
|
|||||||
@ -294,9 +294,12 @@ public sealed class WordCompanionFollowHub(
|
|||||||
{
|
{
|
||||||
Status = "Complete",
|
Status = "Complete",
|
||||||
Message = "Manuscript structure has been created and saved. Word may now be closed; keep this browser workflow open to review characters.",
|
Message = "Manuscript structure has been created and saved. Word may now be closed; keep this browser workflow open to review characters.",
|
||||||
PercentComplete = 100
|
PercentComplete = 100,
|
||||||
|
PreviewID = result?.PreviewID,
|
||||||
|
BuildID = buildId,
|
||||||
|
MarkerStatus = markerStatus
|
||||||
});
|
});
|
||||||
await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildMarkerSyncCompleted", new { buildId, markerStatus });
|
await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildMarkerSyncCompleted", new { buildId, markerStatus, previewID = result?.PreviewID });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RequireOnboardingBuildManualSave(int buildId, string message, MarkerSyncClientResult? result = null)
|
public async Task RequireOnboardingBuildManualSave(int buildId, string message, MarkerSyncClientResult? result = null)
|
||||||
@ -310,14 +313,19 @@ public sealed class WordCompanionFollowHub(
|
|||||||
SerialiseMarkerDiagnostics(result));
|
SerialiseMarkerDiagnostics(result));
|
||||||
|
|
||||||
await onboardingBuilds.MarkMarkerSyncUserSaveRequiredAsync(buildId, message);
|
await onboardingBuilds.MarkMarkerSyncUserSaveRequiredAsync(buildId, message);
|
||||||
await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildProgress", new OnboardingBuildProgress
|
var progress = new OnboardingBuildProgress
|
||||||
{
|
{
|
||||||
Status = "Running",
|
Status = "Running",
|
||||||
Message = string.IsNullOrWhiteSpace(message)
|
Message = string.IsNullOrWhiteSpace(message)
|
||||||
? "Scene markers have been added. Save the document in Word, then continue the import again."
|
? "Scene markers have been added. Save the document in Word, then continue the import again."
|
||||||
: message,
|
: message,
|
||||||
PercentComplete = 90
|
PercentComplete = 90,
|
||||||
});
|
PreviewID = result?.PreviewID,
|
||||||
|
BuildID = buildId,
|
||||||
|
MarkerStatus = "UserSaveRequired"
|
||||||
|
};
|
||||||
|
await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildProgress", progress);
|
||||||
|
await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildManualSaveRequired", progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FailOnboardingBuildMarkerSync(int buildId, string message, MarkerSyncClientResult? result = null)
|
public async Task FailOnboardingBuildMarkerSync(int buildId, string message, MarkerSyncClientResult? result = null)
|
||||||
@ -337,7 +345,9 @@ public sealed class WordCompanionFollowHub(
|
|||||||
Message = string.IsNullOrWhiteSpace(message)
|
Message = string.IsNullOrWhiteSpace(message)
|
||||||
? "Word marker sync failed. Keep Word open and try again."
|
? "Word marker sync failed. Keep Word open and try again."
|
||||||
: message,
|
: message,
|
||||||
PercentComplete = 90
|
PercentComplete = 90,
|
||||||
|
BuildID = buildId,
|
||||||
|
MarkerStatus = "Failed"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +437,7 @@ public sealed class WordCompanionFollowHub(
|
|||||||
|
|
||||||
public sealed class MarkerSyncClientResult
|
public sealed class MarkerSyncClientResult
|
||||||
{
|
{
|
||||||
|
public Guid? PreviewID { get; init; }
|
||||||
public string? MarkerStatus { get; init; }
|
public string? MarkerStatus { get; init; }
|
||||||
public string? Message { get; init; }
|
public string? Message { get; init; }
|
||||||
public bool? MarkersChanged { get; init; }
|
public bool? MarkersChanged { get; init; }
|
||||||
|
|||||||
@ -262,4 +262,6 @@ public sealed class OnboardingBuildProgress
|
|||||||
public int CharactersCreated { get; init; }
|
public int CharactersCreated { get; init; }
|
||||||
public int SceneAppearancesCreated { get; init; }
|
public int SceneAppearancesCreated { get; init; }
|
||||||
public Guid? PreviewID { get; init; }
|
public Guid? PreviewID { get; init; }
|
||||||
|
public int? BuildID { get; init; }
|
||||||
|
public string? MarkerStatus { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -395,7 +395,11 @@ public sealed class OnboardingService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OnboardingManuscriptBuildResult?> GetBuildResultAsync(Guid previewId)
|
public async Task<OnboardingManuscriptBuildResult?> GetBuildResultAsync(Guid previewId)
|
||||||
=> await scanPreviews.GetBuildResultAsync(RequireUserId(), previewId);
|
{
|
||||||
|
var userId = RequireUserId();
|
||||||
|
return await scanPreviews.GetBuildResultAsync(userId, previewId)
|
||||||
|
?? await builds.GetByPreviewAsync(userId, previewId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<OnboardingManuscriptBuildResult?> FinaliseCharacterReviewAsync(ManuscriptScanReviewForm form)
|
public async Task<OnboardingManuscriptBuildResult?> FinaliseCharacterReviewAsync(ManuscriptScanReviewForm form)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -317,6 +317,11 @@
|
|||||||
<div class="onboarding-scan-progress onboarding-build-progress" aria-hidden="true">
|
<div class="onboarding-scan-progress onboarding-build-progress" aria-hidden="true">
|
||||||
<span data-onboarding-build-progress></span>
|
<span data-onboarding-build-progress></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="onboarding-manual-save-actions" data-onboarding-manual-save-actions hidden>
|
||||||
|
<button class="btn btn-primary btn-sm" type="button" data-onboarding-build-save-confirm disabled>
|
||||||
|
I've saved the Word document
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
@ -339,6 +344,7 @@
|
|||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
type="button"
|
type="button"
|
||||||
data-onboarding-build-start
|
data-onboarding-build-start
|
||||||
|
data-onboarding-build-action
|
||||||
data-preview-id="@Model.ScanState.PreviewID">
|
data-preview-id="@Model.ScanState.PreviewID">
|
||||||
Detect Scenes
|
Detect Scenes
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -102,6 +102,16 @@
|
|||||||
<p>Linked to PlotDirector</p>
|
<p>Linked to PlotDirector</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="word-companion-section word-companion-manual-save" data-onboarding-manual-save hidden aria-label="Save Word document">
|
||||||
|
<div class="word-companion-section-header">
|
||||||
|
<span>Save Required</span>
|
||||||
|
</div>
|
||||||
|
<p class="word-companion-message" data-onboarding-manual-save-message>Scene markers have been added. Save this Word document to continue.</p>
|
||||||
|
<div class="word-companion-actions">
|
||||||
|
<button type="button" class="word-companion-primary-action" data-onboarding-manual-save-confirm disabled>I've saved it</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="word-companion-section word-companion-writing-workspace" aria-label="Writing workspace">
|
<section class="word-companion-section word-companion-writing-workspace" aria-label="Writing workspace">
|
||||||
<div class="word-companion-section-header">
|
<div class="word-companion-section-header">
|
||||||
<span>Writing Workspace</span>
|
<span>Writing Workspace</span>
|
||||||
|
|||||||
@ -454,6 +454,19 @@
|
|||||||
border-color: rgba(47, 111, 99, .34);
|
border-color: rgba(47, 111, 99, .34);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-build-panel.is-manual-save {
|
||||||
|
border-color: rgba(182, 138, 70, .5);
|
||||||
|
background: rgba(182, 138, 70, .12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-manual-save-actions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-top: .2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-review-panel {
|
.onboarding-review-panel {
|
||||||
width: min(1120px, 100%);
|
width: min(1120px, 100%);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -308,6 +308,15 @@ body {
|
|||||||
box-shadow: 0 1px 2px rgba(74, 50, 31, 0.05);
|
box-shadow: 0 1px 2px rgba(74, 50, 31, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-companion-manual-save {
|
||||||
|
border-color: rgba(182, 138, 70, .55);
|
||||||
|
background: #fff7e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-manual-save .word-companion-section-header span {
|
||||||
|
color: var(--companion-accent-dark);
|
||||||
|
}
|
||||||
|
|
||||||
.word-companion-section span,
|
.word-companion-section span,
|
||||||
.word-companion-section label,
|
.word-companion-section label,
|
||||||
.word-companion-summary span {
|
.word-companion-summary span {
|
||||||
|
|||||||
@ -19,6 +19,9 @@
|
|||||||
const linkedManuscriptCard = document.querySelector("[data-linked-manuscript-card]");
|
const linkedManuscriptCard = document.querySelector("[data-linked-manuscript-card]");
|
||||||
const linkedProjectTitle = document.querySelector("[data-linked-project-title]");
|
const linkedProjectTitle = document.querySelector("[data-linked-project-title]");
|
||||||
const linkedBookTitle = document.querySelector("[data-linked-book-title]");
|
const linkedBookTitle = document.querySelector("[data-linked-book-title]");
|
||||||
|
const onboardingManualSavePanel = document.querySelector("[data-onboarding-manual-save]");
|
||||||
|
const onboardingManualSaveMessage = document.querySelector("[data-onboarding-manual-save-message]");
|
||||||
|
const onboardingManualSaveConfirmButton = document.querySelector("[data-onboarding-manual-save-confirm]");
|
||||||
const firstRunWizard = document.querySelector("[data-first-run-wizard]");
|
const firstRunWizard = document.querySelector("[data-first-run-wizard]");
|
||||||
const firstRunProjectSelect = document.querySelector("[data-first-run-project-select]");
|
const firstRunProjectSelect = document.querySelector("[data-first-run-project-select]");
|
||||||
const firstRunBookSelect = document.querySelector("[data-first-run-book-select]");
|
const firstRunBookSelect = document.querySelector("[data-first-run-book-select]");
|
||||||
@ -215,6 +218,7 @@
|
|||||||
let firstRunImportStructure = null;
|
let firstRunImportStructure = null;
|
||||||
let firstRunCharacterCandidatesModel = [];
|
let firstRunCharacterCandidatesModel = [];
|
||||||
let lastOnboardingScanPreview = null;
|
let lastOnboardingScanPreview = null;
|
||||||
|
let pendingOnboardingManualSave = null;
|
||||||
let firstRunStructureImported = false;
|
let firstRunStructureImported = false;
|
||||||
let firstRunCharacterStepActive = false;
|
let firstRunCharacterStepActive = false;
|
||||||
let firstRunCharacterStepActivatedAt = 0;
|
let firstRunCharacterStepActivatedAt = 0;
|
||||||
@ -1840,10 +1844,68 @@
|
|||||||
message,
|
message,
|
||||||
...diagnostics
|
...diagnostics
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Unable to report onboarding marker sync completion.", error);
|
console.warn("Unable to report onboarding marker sync completion.", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setOnboardingManualSavePrompt = (buildId, message, diagnostics = {}) => {
|
||||||
|
pendingOnboardingManualSave = { buildId, diagnostics };
|
||||||
|
setHidden(onboardingManualSavePanel, false);
|
||||||
|
setText(onboardingManualSaveMessage, message);
|
||||||
|
if (onboardingManualSaveConfirmButton) {
|
||||||
|
onboardingManualSaveConfirmButton.disabled = false;
|
||||||
|
onboardingManualSaveConfirmButton.textContent = "I've saved it";
|
||||||
|
}
|
||||||
|
setDocumentMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearOnboardingManualSavePrompt = () => {
|
||||||
|
pendingOnboardingManualSave = null;
|
||||||
|
setHidden(onboardingManualSavePanel, true);
|
||||||
|
if (onboardingManualSaveConfirmButton) {
|
||||||
|
onboardingManualSaveConfirmButton.disabled = true;
|
||||||
|
onboardingManualSaveConfirmButton.textContent = "I've saved it";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmOnboardingManualSave = async () => {
|
||||||
|
if (!pendingOnboardingManualSave?.buildId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onboardingManualSaveConfirmButton) {
|
||||||
|
onboardingManualSaveConfirmButton.disabled = true;
|
||||||
|
onboardingManualSaveConfirmButton.textContent = "Confirming...";
|
||||||
|
}
|
||||||
|
|
||||||
|
const completed = await invokeMarkerSyncCompleted(
|
||||||
|
pendingOnboardingManualSave.buildId,
|
||||||
|
"UserConfirmedSave",
|
||||||
|
"Scene markers added; user confirmed the Word document was saved.",
|
||||||
|
{
|
||||||
|
...pendingOnboardingManualSave.diagnostics,
|
||||||
|
markersChanged: true,
|
||||||
|
saveState: "UserConfirmedSave",
|
||||||
|
saveApi: pendingOnboardingManualSave.diagnostics?.saveApi || "manual-companion",
|
||||||
|
...officeRuntimeDiagnostics()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!completed) {
|
||||||
|
setDocumentMessage("PlotDirector could not confirm the save yet. Keep Word open and try again.");
|
||||||
|
if (onboardingManualSaveConfirmButton) {
|
||||||
|
onboardingManualSaveConfirmButton.disabled = false;
|
||||||
|
onboardingManualSaveConfirmButton.textContent = "I've saved it";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearOnboardingManualSavePrompt();
|
||||||
|
setDocumentMessage("Scene markers added and save confirmed. Word may now be closed.");
|
||||||
|
setDiagnostics({ lastError: "-" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const ensureDocumentGuid = () => {
|
const ensureDocumentGuid = () => {
|
||||||
@ -2496,6 +2558,7 @@
|
|||||||
|
|
||||||
await inspectPlotDirectorMarkers().catch(() => null);
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
if (!markersChanged) {
|
if (!markersChanged) {
|
||||||
|
clearOnboardingManualSavePrompt();
|
||||||
const diagnostics = {
|
const diagnostics = {
|
||||||
markersChanged: false,
|
markersChanged: false,
|
||||||
saveState: "NotRequired",
|
saveState: "NotRequired",
|
||||||
@ -2513,41 +2576,20 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveResult = await saveWordDocumentIfAvailable();
|
|
||||||
const saveDiagnostics = {
|
const saveDiagnostics = {
|
||||||
|
previewID: result?.previewID || result?.PreviewID || null,
|
||||||
markersChanged: true,
|
markersChanged: true,
|
||||||
...saveResult
|
saveState: "UserSaveRequired",
|
||||||
|
saveApi: "manual-companion",
|
||||||
|
...officeRuntimeDiagnostics()
|
||||||
};
|
};
|
||||||
console.info("Onboarding marker sync Word save result.", saveDiagnostics);
|
const manualMessage = "Scene markers have been added. Save this document in Word, then click I've saved it in PlotDirector or the Companion.";
|
||||||
if (saveResult.saveState === "Saved") {
|
console.info("Onboarding marker sync requires manual Word save confirmation.", saveDiagnostics);
|
||||||
await invokeMarkerSyncCompleted(
|
setOnboardingManualSavePrompt(buildId, manualMessage, saveDiagnostics);
|
||||||
buildId,
|
setDiagnostics({ lastError: "Waiting for manual Word save confirmation." });
|
||||||
"Completed",
|
|
||||||
"Scene markers added and document saved.",
|
|
||||||
saveDiagnostics);
|
|
||||||
setDocumentMessage("Scene markers added and document saved. Word may now be closed.");
|
|
||||||
setDiagnostics({ lastError: "-" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const manualMessage = "Scene markers have been added. Save the document in Word now, then click OK to continue.";
|
|
||||||
setDocumentMessage(manualMessage);
|
|
||||||
setDiagnostics({ lastError: saveResult.errorMessage || "Word document save requires manual confirmation." });
|
|
||||||
const userConfirmedSave = window.confirm(`${manualMessage}\n\nClick Cancel if the document was not saved.`);
|
|
||||||
if (userConfirmedSave) {
|
|
||||||
await invokeMarkerSyncCompleted(
|
|
||||||
buildId,
|
|
||||||
"UserConfirmedSave",
|
|
||||||
"Scene markers added; user confirmed the Word document was saved.",
|
|
||||||
saveDiagnostics);
|
|
||||||
setDocumentMessage("Scene markers added and save confirmed. Word may now be closed.");
|
|
||||||
setDiagnostics({ lastError: "-" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await invokeMarkerSyncManualSaveRequired(
|
await invokeMarkerSyncManualSaveRequired(
|
||||||
buildId,
|
buildId,
|
||||||
"Scene markers were added, but Word save still needs confirmation. Save the document in Word, then continue the import again.",
|
manualMessage,
|
||||||
saveDiagnostics);
|
saveDiagnostics);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to update onboarding Word markers.", error);
|
console.error("Unable to update onboarding Word markers.", error);
|
||||||
@ -6236,6 +6278,7 @@
|
|||||||
firstRunContinueButton?.addEventListener("click", () => finishFirstRunOnboarding());
|
firstRunContinueButton?.addEventListener("click", () => finishFirstRunOnboarding());
|
||||||
firstRunCancelButton?.addEventListener("click", () => setFirstRunMode(false));
|
firstRunCancelButton?.addEventListener("click", () => setFirstRunMode(false));
|
||||||
firstRunImportCancelButton?.addEventListener("click", () => resetFirstRunPreview("Import cancelled."));
|
firstRunImportCancelButton?.addEventListener("click", () => resetFirstRunPreview("Import cancelled."));
|
||||||
|
onboardingManualSaveConfirmButton?.addEventListener("click", confirmOnboardingManualSave);
|
||||||
|
|
||||||
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
|
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
|
||||||
analyseManuscriptStructureButton?.addEventListener("click", analyseManuscriptStructure);
|
analyseManuscriptStructureButton?.addEventListener("click", analyseManuscriptStructure);
|
||||||
|
|||||||
@ -160,6 +160,106 @@
|
|||||||
.withAutomaticReconnect()
|
.withAutomaticReconnect()
|
||||||
.build();
|
.build();
|
||||||
let pendingBuildPreviewId = "";
|
let pendingBuildPreviewId = "";
|
||||||
|
let pendingManualSaveBuildId = 0;
|
||||||
|
let pendingManualSavePreviewId = "";
|
||||||
|
|
||||||
|
const setBuildActionState = (text, disabled, mode = "") => {
|
||||||
|
document.querySelectorAll("[data-onboarding-build-action]").forEach((button) => {
|
||||||
|
button.textContent = text;
|
||||||
|
button.disabled = disabled;
|
||||||
|
if (mode) {
|
||||||
|
button.dataset.buildMode = mode;
|
||||||
|
} else {
|
||||||
|
delete button.dataset.buildMode;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setManualSaveRequired = (state) => {
|
||||||
|
const buildId = Number.parseInt(
|
||||||
|
field(state, "buildID", "BuildID")
|
||||||
|
?? field(state, "buildId", "BuildId")
|
||||||
|
?? "0",
|
||||||
|
10);
|
||||||
|
const previewId = field(state, "previewID", "PreviewID")
|
||||||
|
|| field(state, "previewId", "PreviewId")
|
||||||
|
|| pendingBuildPreviewId;
|
||||||
|
const message = field(state, "message", "Message")
|
||||||
|
|| "Scene markers have been added. Save the document in Word, then confirm here.";
|
||||||
|
|
||||||
|
pendingManualSaveBuildId = Number.isInteger(buildId) ? buildId : 0;
|
||||||
|
pendingManualSavePreviewId = previewId || pendingBuildPreviewId;
|
||||||
|
pendingBuildPreviewId = pendingManualSavePreviewId || pendingBuildPreviewId;
|
||||||
|
|
||||||
|
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||||
|
node.classList.add("is-running", "is-manual-save");
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
||||||
|
node.textContent = message;
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-build-percent]").forEach((node) => {
|
||||||
|
node.textContent = "90%";
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-build-progress]").forEach((node) => {
|
||||||
|
node.style.width = "90%";
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-manual-save-actions]").forEach((node) => {
|
||||||
|
node.hidden = false;
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-build-save-confirm]").forEach((button) => {
|
||||||
|
button.disabled = !pendingManualSaveBuildId;
|
||||||
|
button.textContent = "I've saved the Word document";
|
||||||
|
});
|
||||||
|
setBuildActionState("I've saved the Word document", !pendingManualSaveBuildId, "confirm-save");
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearManualSaveRequired = () => {
|
||||||
|
pendingManualSaveBuildId = 0;
|
||||||
|
pendingManualSavePreviewId = "";
|
||||||
|
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||||
|
node.classList.remove("is-manual-save");
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-manual-save-actions]").forEach((node) => {
|
||||||
|
node.hidden = true;
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-build-save-confirm]").forEach((button) => {
|
||||||
|
button.disabled = true;
|
||||||
|
button.textContent = "I've saved the Word document";
|
||||||
|
});
|
||||||
|
setBuildActionState("Detect Scenes", false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmManualSaveFromWeb = async () => {
|
||||||
|
if (!pendingManualSaveBuildId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBuildActionState("Confirming...", true, "confirm-save");
|
||||||
|
document.querySelectorAll("[data-onboarding-build-save-confirm]").forEach((button) => {
|
||||||
|
button.disabled = true;
|
||||||
|
button.textContent = "Confirming...";
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connection.invoke("CompleteOnboardingBuildMarkerSync", pendingManualSaveBuildId, {
|
||||||
|
previewID: pendingManualSavePreviewId || pendingBuildPreviewId || null,
|
||||||
|
markerStatus: "UserConfirmedSave",
|
||||||
|
message: "Scene markers added; user confirmed the Word document was saved.",
|
||||||
|
markersChanged: true,
|
||||||
|
saveState: "UserConfirmedSave",
|
||||||
|
saveApi: "manual-web"
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
||||||
|
node.textContent = error?.message || "PlotDirector could not confirm the save yet. Keep Word open and try again.";
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-onboarding-build-save-confirm]").forEach((button) => {
|
||||||
|
button.disabled = false;
|
||||||
|
button.textContent = "I've saved the Word document";
|
||||||
|
});
|
||||||
|
setBuildActionState("I've saved the Word document", false, "confirm-save");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
connection.on("WordCompanionPresenceChanged", applyStatus);
|
connection.on("WordCompanionPresenceChanged", applyStatus);
|
||||||
connection.on("OnboardingScanStateChanged", applyScanState);
|
connection.on("OnboardingScanStateChanged", applyScanState);
|
||||||
@ -170,8 +270,14 @@
|
|||||||
const message = field(state, "message", "Message") || "Preparing chapters...";
|
const message = field(state, "message", "Message") || "Preparing chapters...";
|
||||||
const percent = field(state, "percentComplete", "PercentComplete");
|
const percent = field(state, "percentComplete", "PercentComplete");
|
||||||
const safePercent = Math.max(0, Math.min(100, Number.parseInt(percent || "0", 10) || 0));
|
const safePercent = Math.max(0, Math.min(100, Number.parseInt(percent || "0", 10) || 0));
|
||||||
|
const markerStatus = field(state, "markerStatus", "MarkerStatus");
|
||||||
|
if (markerStatus === "UserSaveRequired") {
|
||||||
|
setManualSaveRequired(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||||
node.classList.add("is-running");
|
node.classList.add("is-running");
|
||||||
|
node.classList.remove("is-manual-save");
|
||||||
});
|
});
|
||||||
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
||||||
node.textContent = message;
|
node.textContent = message;
|
||||||
@ -189,7 +295,11 @@
|
|||||||
window.location.href = `/onboarding/build-complete?previewId=${encodeURIComponent(previewId)}`;
|
window.location.href = `/onboarding/build-complete?previewId=${encodeURIComponent(previewId)}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connection.on("OnboardingBuildMarkerSyncCompleted", () => {
|
connection.on("OnboardingBuildManualSaveRequired", setManualSaveRequired);
|
||||||
|
connection.on("OnboardingBuildMarkerSyncCompleted", (result) => {
|
||||||
|
const previewId = field(result, "previewID", "PreviewID") || field(result, "previewId", "PreviewId");
|
||||||
|
pendingBuildPreviewId = previewId || pendingBuildPreviewId || pendingManualSavePreviewId;
|
||||||
|
clearManualSaveRequired();
|
||||||
if (pendingBuildPreviewId) {
|
if (pendingBuildPreviewId) {
|
||||||
window.location.href = `/onboarding/build-complete?previewId=${encodeURIComponent(pendingBuildPreviewId)}`;
|
window.location.href = `/onboarding/build-complete?previewId=${encodeURIComponent(pendingBuildPreviewId)}`;
|
||||||
}
|
}
|
||||||
@ -221,11 +331,17 @@
|
|||||||
|
|
||||||
document.querySelectorAll("[data-onboarding-build-start]").forEach((button) => {
|
document.querySelectorAll("[data-onboarding-build-start]").forEach((button) => {
|
||||||
button.addEventListener("click", async () => {
|
button.addEventListener("click", async () => {
|
||||||
|
if (button.dataset.buildMode === "confirm-save" || pendingManualSaveBuildId) {
|
||||||
|
await confirmManualSaveFromWeb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const previewId = button.dataset.previewId;
|
const previewId = button.dataset.previewId;
|
||||||
if (!previewId) {
|
if (!previewId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pendingBuildPreviewId = previewId;
|
pendingBuildPreviewId = previewId;
|
||||||
|
clearManualSaveRequired();
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||||
node.classList.add("is-running");
|
node.classList.add("is-running");
|
||||||
@ -243,7 +359,7 @@
|
|||||||
const result = await connection.invoke("StartOnboardingProjectBuild", previewId);
|
const result = await connection.invoke("StartOnboardingProjectBuild", previewId);
|
||||||
pendingBuildPreviewId = field(result, "previewID", "PreviewID") || previewId;
|
pendingBuildPreviewId = field(result, "previewID", "PreviewID") || previewId;
|
||||||
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
||||||
node.textContent = "Updating Word document with scene markers. Keep Word open while the document is saved.";
|
node.textContent = "Updating Word document with scene markers. Keep Word open, then save the document when prompted.";
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
@ -259,4 +375,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("[data-onboarding-build-save-confirm]").forEach((button) => {
|
||||||
|
button.addEventListener("click", confirmManualSaveFromWeb);
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user