Phase 10J: Word Companion Scene Create / Link
This commit is contained in:
parent
120ee271f8
commit
a9aae3fbf9
@ -22,7 +22,9 @@
|
|||||||
<DefaultSettings>
|
<DefaultSettings>
|
||||||
<SourceLocation DefaultValue="https://localhost:7296/word-companion"/>
|
<SourceLocation DefaultValue="https://localhost:7296/word-companion"/>
|
||||||
</DefaultSettings>
|
</DefaultSettings>
|
||||||
<Permissions>ReadWriteDocument</Permissions>
|
<!-- ReadDocument is required for the companion to read Word paragraphs, selection context,
|
||||||
|
and document body content. It does not grant document write access. -->
|
||||||
|
<Permissions>ReadDocument</Permissions>
|
||||||
<VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
|
<VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
|
||||||
<Hosts>
|
<Hosts>
|
||||||
<Host xsi:type="Document">
|
<Host xsi:type="Document">
|
||||||
|
|||||||
@ -46,6 +46,13 @@ public sealed class WordCompanionController(IWordCompanionService wordCompanion)
|
|||||||
return response is null ? NotFound() : Ok(response);
|
return response is null ? NotFound() : Ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("books/{bookId:int}/chapters/{chapterId:int}/scenes")]
|
||||||
|
public async Task<IActionResult> CreateScene(int bookId, int chapterId, WordCompanionCreateSceneRequest request)
|
||||||
|
{
|
||||||
|
var response = await wordCompanion.CreateSceneAsync(bookId, chapterId, request);
|
||||||
|
return response is null ? BadRequest() : Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("books/{bookId:int}/sync-scene")]
|
[HttpPost("books/{bookId:int}/sync-scene")]
|
||||||
public async Task<IActionResult> SyncScene(int bookId, WordCompanionSyncSceneRequest request)
|
public async Task<IActionResult> SyncScene(int bookId, WordCompanionSyncSceneRequest request)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -11,6 +11,7 @@ public interface IWordCompanionRepository
|
|||||||
Task<WordCompanionBookStructureResponse?> GetBookStructureAsync(int bookId, int userId);
|
Task<WordCompanionBookStructureResponse?> GetBookStructureAsync(int bookId, int userId);
|
||||||
Task<WordCompanionResolveSceneResponse> ResolveSceneAsync(int bookId, int userId, string chapterTitle, string sceneTitle);
|
Task<WordCompanionResolveSceneResponse> ResolveSceneAsync(int bookId, int userId, string chapterTitle, string sceneTitle);
|
||||||
Task<WordCompanionSceneCompanionResponse?> GetSceneCompanionAsync(int sceneId, int userId);
|
Task<WordCompanionSceneCompanionResponse?> GetSceneCompanionAsync(int sceneId, int userId);
|
||||||
|
Task<WordCompanionCreateSceneResponse?> CreateSceneAsync(int bookId, int chapterId, int userId, WordCompanionCreateSceneRequest request);
|
||||||
Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, int userId, WordCompanionSyncSceneRequest request);
|
Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, int userId, WordCompanionSyncSceneRequest request);
|
||||||
Task<WordCompanionSyncManuscriptResponse?> SyncManuscriptAsync(int bookId, int userId, WordCompanionSyncManuscriptRequest request);
|
Task<WordCompanionSyncManuscriptResponse?> SyncManuscriptAsync(int bookId, int userId, WordCompanionSyncManuscriptRequest request);
|
||||||
Task<WordCompanionUpdateSceneLinksResponse?> UpdateSceneLinksAsync(int sceneId, int userId, WordCompanionUpdateSceneLinksRequest request);
|
Task<WordCompanionUpdateSceneLinksResponse?> UpdateSceneLinksAsync(int sceneId, int userId, WordCompanionUpdateSceneLinksRequest request);
|
||||||
@ -115,6 +116,15 @@ public sealed class WordCompanionRepository(ISqlConnectionFactory connectionFact
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<WordCompanionCreateSceneResponse?> CreateSceneAsync(int bookId, int chapterId, int userId, WordCompanionCreateSceneRequest request)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<WordCompanionCreateSceneResponse>(
|
||||||
|
"dbo.WordCompanion_Scene_Create",
|
||||||
|
new { BookID = bookId, ChapterID = chapterId, UserID = userId, request.SceneTitle },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, int userId, WordCompanionSyncSceneRequest request)
|
public async Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, int userId, WordCompanionSyncSceneRequest request)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
|||||||
@ -114,6 +114,18 @@ public sealed class WordCompanionSyncSceneResponse
|
|||||||
public string Message { get; init; } = "Scene synced successfully.";
|
public string Message { get; init; } = "Scene synced successfully.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class WordCompanionCreateSceneRequest
|
||||||
|
{
|
||||||
|
public string? SceneTitle { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class WordCompanionCreateSceneResponse
|
||||||
|
{
|
||||||
|
public int ChapterId { get; init; }
|
||||||
|
public int SceneId { get; init; }
|
||||||
|
public string Message { get; init; } = "Scene created.";
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class WordCompanionSyncManuscriptRequest
|
public sealed class WordCompanionSyncManuscriptRequest
|
||||||
{
|
{
|
||||||
public string? DocumentId { get; set; }
|
public string? DocumentId { get; set; }
|
||||||
|
|||||||
@ -10,6 +10,7 @@ public interface IWordCompanionService
|
|||||||
Task<WordCompanionBookStructureResponse?> GetBookStructureAsync(int bookId);
|
Task<WordCompanionBookStructureResponse?> GetBookStructureAsync(int bookId);
|
||||||
Task<WordCompanionResolveSceneResponse?> ResolveSceneAsync(int bookId, WordCompanionResolveSceneRequest request);
|
Task<WordCompanionResolveSceneResponse?> ResolveSceneAsync(int bookId, WordCompanionResolveSceneRequest request);
|
||||||
Task<WordCompanionSceneCompanionResponse?> GetSceneCompanionAsync(int sceneId);
|
Task<WordCompanionSceneCompanionResponse?> GetSceneCompanionAsync(int sceneId);
|
||||||
|
Task<WordCompanionCreateSceneResponse?> CreateSceneAsync(int bookId, int chapterId, WordCompanionCreateSceneRequest request);
|
||||||
Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, WordCompanionSyncSceneRequest request);
|
Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, WordCompanionSyncSceneRequest request);
|
||||||
Task<WordCompanionSyncManuscriptResponse?> SyncManuscriptAsync(int bookId, WordCompanionSyncManuscriptRequest request);
|
Task<WordCompanionSyncManuscriptResponse?> SyncManuscriptAsync(int bookId, WordCompanionSyncManuscriptRequest request);
|
||||||
Task<WordCompanionUpdateSceneLinksResponse?> UpdateSceneLinksAsync(int sceneId, WordCompanionUpdateSceneLinksRequest request);
|
Task<WordCompanionUpdateSceneLinksResponse?> UpdateSceneLinksAsync(int sceneId, WordCompanionUpdateSceneLinksRequest request);
|
||||||
@ -55,6 +56,17 @@ public sealed class WordCompanionService(IWordCompanionRepository repository, IC
|
|||||||
return sceneId <= 0 ? Task.FromResult<WordCompanionSceneCompanionResponse?>(null) : repository.GetSceneCompanionAsync(sceneId, RequireUserId());
|
return sceneId <= 0 ? Task.FromResult<WordCompanionSceneCompanionResponse?>(null) : repository.GetSceneCompanionAsync(sceneId, RequireUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<WordCompanionCreateSceneResponse?> CreateSceneAsync(int bookId, int chapterId, WordCompanionCreateSceneRequest request)
|
||||||
|
{
|
||||||
|
if (bookId <= 0 || chapterId <= 0 || string.IsNullOrWhiteSpace(request.SceneTitle))
|
||||||
|
{
|
||||||
|
return Task.FromResult<WordCompanionCreateSceneResponse?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.SceneTitle = request.SceneTitle.Trim();
|
||||||
|
return repository.CreateSceneAsync(bookId, chapterId, RequireUserId(), request);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, WordCompanionSyncSceneRequest request)
|
public Task<WordCompanionSyncSceneResponse?> SyncSceneAsync(int bookId, WordCompanionSyncSceneRequest request)
|
||||||
{
|
{
|
||||||
if (bookId <= 0
|
if (bookId <= 0
|
||||||
|
|||||||
85
PlotLine/Sql/062_Phase10J_WordCompanionSceneCreateLink.sql
Normal file
85
PlotLine/Sql/062_Phase10J_WordCompanionSceneCreateLink.sql
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.WordCompanion_Scene_Create
|
||||||
|
@BookID int,
|
||||||
|
@ChapterID int,
|
||||||
|
@UserID int,
|
||||||
|
@SceneTitle nvarchar(200)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @TrimmedSceneTitle nvarchar(200) = LTRIM(RTRIM(COALESCE(@SceneTitle, N'')));
|
||||||
|
IF NULLIF(@TrimmedSceneTitle, N'') IS NULL
|
||||||
|
RETURN;
|
||||||
|
|
||||||
|
IF NOT EXISTS
|
||||||
|
(
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
|
||||||
|
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
|
||||||
|
WHERE b.BookID = @BookID
|
||||||
|
AND c.ChapterID = @ChapterID
|
||||||
|
AND c.IsArchived = 0
|
||||||
|
AND b.IsArchived = 0
|
||||||
|
AND p.IsArchived = 0
|
||||||
|
AND pua.UserID = @UserID
|
||||||
|
AND pua.IsActive = 1
|
||||||
|
)
|
||||||
|
RETURN;
|
||||||
|
|
||||||
|
DECLARE @PlannedRevisionStatusID int = (SELECT TOP (1) RevisionStatusID FROM dbo.RevisionStatuses WHERE StatusName = N'Planned' ORDER BY SortOrder);
|
||||||
|
DECLARE @UnknownTimeModeID int = (SELECT TOP (1) TimeModeID FROM dbo.TimeModes WHERE TimeModeName = N'Unknown' ORDER BY SortOrder);
|
||||||
|
DECLARE @UnknownTimeConfidenceID int = (SELECT TOP (1) TimeConfidenceID FROM dbo.TimeConfidences WHERE TimeConfidenceName = N'Unknown' ORDER BY SortOrder);
|
||||||
|
|
||||||
|
IF @PlannedRevisionStatusID IS NULL OR @UnknownTimeModeID IS NULL OR @UnknownTimeConfidenceID IS NULL
|
||||||
|
RETURN;
|
||||||
|
|
||||||
|
DECLARE @NextSceneNumber decimal(9,2) = ISNULL((SELECT MAX(SceneNumber) FROM dbo.Scenes WHERE ChapterID = @ChapterID), 0) + 1;
|
||||||
|
DECLARE @NextSceneOrder int = ISNULL((SELECT MAX(SortOrder) FROM dbo.Scenes WHERE ChapterID = @ChapterID), 0) + 10;
|
||||||
|
DECLARE @SceneID int;
|
||||||
|
|
||||||
|
INSERT dbo.Scenes
|
||||||
|
(
|
||||||
|
ChapterID,
|
||||||
|
SceneNumber,
|
||||||
|
SceneTitle,
|
||||||
|
Summary,
|
||||||
|
SortOrder,
|
||||||
|
TimeModeID,
|
||||||
|
TimeConfidenceID,
|
||||||
|
ScenePurposeNotes,
|
||||||
|
SceneOutcomeNotes,
|
||||||
|
RevisionStatusID,
|
||||||
|
PrimaryLocationID
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@ChapterID,
|
||||||
|
@NextSceneNumber,
|
||||||
|
@TrimmedSceneTitle,
|
||||||
|
NULL,
|
||||||
|
@NextSceneOrder,
|
||||||
|
@UnknownTimeModeID,
|
||||||
|
@UnknownTimeConfidenceID,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
@PlannedRevisionStatusID,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @SceneID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
|
||||||
|
INSERT dbo.SceneWorkflow (SceneID, DraftStatus, EstimatedWordCount, ActualWordCount, IsBlocked)
|
||||||
|
VALUES (@SceneID, N'Planned', 0, 0, 0);
|
||||||
|
|
||||||
|
SELECT @ChapterID AS ChapterId,
|
||||||
|
@SceneID AS SceneId,
|
||||||
|
N'Scene created.' AS Message;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
@ -121,6 +121,27 @@
|
|||||||
<p class="word-companion-message" data-sync-note></p>
|
<p class="word-companion-message" data-sync-note></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="word-companion-section word-companion-create-link" data-scene-create-link hidden aria-label="Scene Create Link">
|
||||||
|
<div class="word-companion-section-header">
|
||||||
|
<span>Scene Create / Link</span>
|
||||||
|
</div>
|
||||||
|
<div class="word-companion-scene-card">
|
||||||
|
<div>
|
||||||
|
<span>Current Chapter</span>
|
||||||
|
<strong data-create-link-chapter>-</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Current Scene</span>
|
||||||
|
<strong data-create-link-scene>-</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="word-companion-actions">
|
||||||
|
<button type="button" data-create-plotdirector-scene disabled>Create Scene in PlotDirector</button>
|
||||||
|
<button type="button" data-link-existing-scene disabled>Link to Existing Scene</button>
|
||||||
|
</div>
|
||||||
|
<p class="word-companion-message" data-create-link-status></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="word-companion-section word-companion-brief" data-writing-brief-block hidden aria-label="Writing Brief">
|
<section class="word-companion-section word-companion-brief" data-writing-brief-block hidden aria-label="Writing Brief">
|
||||||
<span>Writing Brief</span>
|
<span>Writing Brief</span>
|
||||||
<p data-writing-brief></p>
|
<p data-writing-brief></p>
|
||||||
@ -264,6 +285,36 @@
|
|||||||
<footer class="word-companion-footer">
|
<footer class="word-companion-footer">
|
||||||
<span data-office-status>Word Companion loading</span>
|
<span data-office-status>Word Companion loading</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<dialog class="word-companion-dialog" data-link-existing-dialog>
|
||||||
|
<form method="dialog">
|
||||||
|
<div class="word-companion-section-header">
|
||||||
|
<span>Link to Existing Scene</span>
|
||||||
|
<button type="button" data-link-dialog-cancel>Cancel</button>
|
||||||
|
</div>
|
||||||
|
<label for="word-companion-scene-search">Search scenes</label>
|
||||||
|
<input id="word-companion-scene-search" type="search" data-link-scene-search autocomplete="off" />
|
||||||
|
<div class="word-companion-scene-options" data-link-scene-options></div>
|
||||||
|
<button type="button" data-link-selected-scene disabled>Link Scene</button>
|
||||||
|
<p class="word-companion-message" data-link-dialog-status></p>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<dialog class="word-companion-dialog" data-create-scene-dialog>
|
||||||
|
<form method="dialog">
|
||||||
|
<div class="word-companion-section-header">
|
||||||
|
<span>Create PlotDirector Scene</span>
|
||||||
|
</div>
|
||||||
|
<p class="word-companion-dialog-copy">Create PlotDirector scene:</p>
|
||||||
|
<strong data-create-dialog-scene>-</strong>
|
||||||
|
<p class="word-companion-dialog-copy">within chapter:</p>
|
||||||
|
<strong data-create-dialog-chapter>-</strong>
|
||||||
|
<div class="word-companion-dialog-actions">
|
||||||
|
<button type="button" data-create-dialog-confirm>Create</button>
|
||||||
|
<button type="button" data-create-dialog-cancel>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
</main>
|
</main>
|
||||||
<script src="~/js/word-companion-host.js" asp-append-version="true"></script>
|
<script src="~/js/word-companion-host.js" asp-append-version="true"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -339,6 +339,103 @@ body {
|
|||||||
color: var(--companion-muted);
|
color: var(--companion-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-companion-create-link {
|
||||||
|
border-color: rgba(47, 111, 99, 0.38);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog {
|
||||||
|
width: min(360px, calc(100vw - 24px));
|
||||||
|
border: 1px solid var(--companion-line);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--companion-panel);
|
||||||
|
color: var(--companion-ink);
|
||||||
|
font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog::backdrop {
|
||||||
|
background: rgba(30, 37, 43, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog form {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog strong {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog-copy {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--companion-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog input[type="search"] {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 36px;
|
||||||
|
border: 1px solid var(--companion-line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--companion-ink);
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog button {
|
||||||
|
min-height: 32px;
|
||||||
|
border: 1px solid var(--companion-accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 5px 9px;
|
||||||
|
background: var(--companion-accent);
|
||||||
|
color: #ffffff;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog button:disabled {
|
||||||
|
border-color: var(--companion-line);
|
||||||
|
background: var(--companion-soft);
|
||||||
|
color: var(--companion-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-scene-options {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-scene-option {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
min-height: 32px;
|
||||||
|
border: 1px solid rgba(47, 111, 99, 0.18);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 5px 7px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--companion-ink);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-scene-option input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
|
accent-color: var(--companion-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.word-companion-empty-list {
|
.word-companion-empty-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--companion-muted);
|
color: var(--companion-muted);
|
||||||
|
|||||||
@ -51,6 +51,23 @@
|
|||||||
const primaryLocationSelect = document.querySelector("[data-primary-location-select]");
|
const primaryLocationSelect = document.querySelector("[data-primary-location-select]");
|
||||||
const saveSceneLinksButton = document.querySelector("[data-save-scene-links]");
|
const saveSceneLinksButton = document.querySelector("[data-save-scene-links]");
|
||||||
const sceneLinksStatus = document.querySelector("[data-scene-links-status]");
|
const sceneLinksStatus = document.querySelector("[data-scene-links-status]");
|
||||||
|
const sceneCreateLink = document.querySelector("[data-scene-create-link]");
|
||||||
|
const createLinkChapter = document.querySelector("[data-create-link-chapter]");
|
||||||
|
const createLinkScene = document.querySelector("[data-create-link-scene]");
|
||||||
|
const createPlotDirectorSceneButton = document.querySelector("[data-create-plotdirector-scene]");
|
||||||
|
const linkExistingSceneButton = document.querySelector("[data-link-existing-scene]");
|
||||||
|
const createLinkStatus = document.querySelector("[data-create-link-status]");
|
||||||
|
const linkExistingDialog = document.querySelector("[data-link-existing-dialog]");
|
||||||
|
const linkSceneSearch = document.querySelector("[data-link-scene-search]");
|
||||||
|
const linkSceneOptions = document.querySelector("[data-link-scene-options]");
|
||||||
|
const linkSelectedSceneButton = document.querySelector("[data-link-selected-scene]");
|
||||||
|
const linkDialogCancel = document.querySelector("[data-link-dialog-cancel]");
|
||||||
|
const linkDialogStatus = document.querySelector("[data-link-dialog-status]");
|
||||||
|
const createSceneDialog = document.querySelector("[data-create-scene-dialog]");
|
||||||
|
const createDialogScene = document.querySelector("[data-create-dialog-scene]");
|
||||||
|
const createDialogChapter = document.querySelector("[data-create-dialog-chapter]");
|
||||||
|
const createDialogConfirm = document.querySelector("[data-create-dialog-confirm]");
|
||||||
|
const createDialogCancel = document.querySelector("[data-create-dialog-cancel]");
|
||||||
const resolveProjectId = document.querySelector("[data-resolve-project-id]");
|
const resolveProjectId = document.querySelector("[data-resolve-project-id]");
|
||||||
const resolveProjectTitle = document.querySelector("[data-resolve-project-title]");
|
const resolveProjectTitle = document.querySelector("[data-resolve-project-title]");
|
||||||
const resolveBookId = document.querySelector("[data-resolve-book-id]");
|
const resolveBookId = document.querySelector("[data-resolve-book-id]");
|
||||||
@ -93,6 +110,9 @@
|
|||||||
let currentResolutionMethod = "";
|
let currentResolutionMethod = "";
|
||||||
let currentResolutionAnchorType = "Title Match";
|
let currentResolutionAnchorType = "Title Match";
|
||||||
let storedAnchorInvalid = false;
|
let storedAnchorInvalid = false;
|
||||||
|
let linkDialogScenes = [];
|
||||||
|
let selectedLinkSceneId = null;
|
||||||
|
let pendingCreateSceneConfirmation = null;
|
||||||
let currentSceneLinks = {
|
let currentSceneLinks = {
|
||||||
characters: [],
|
characters: [],
|
||||||
assets: [],
|
assets: [],
|
||||||
@ -244,11 +264,48 @@
|
|||||||
setText(sceneLinksStatus, message);
|
setText(sceneLinksStatus, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setCreateLinkStatus = (message) => {
|
||||||
|
setText(createLinkStatus, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const canUseCreateLinkActions = () => !!selectedBook() && !!detectedSceneTitle;
|
||||||
|
|
||||||
|
const hideSceneCreateLink = () => {
|
||||||
|
setHidden(sceneCreateLink, true);
|
||||||
|
setCreateLinkStatus("");
|
||||||
|
if (createPlotDirectorSceneButton) {
|
||||||
|
createPlotDirectorSceneButton.disabled = true;
|
||||||
|
createPlotDirectorSceneButton.textContent = "Create Scene in PlotDirector";
|
||||||
|
}
|
||||||
|
if (linkExistingSceneButton) {
|
||||||
|
linkExistingSceneButton.disabled = true;
|
||||||
|
linkExistingSceneButton.textContent = "Link to Existing Scene";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showSceneCreateLink = (message = "") => {
|
||||||
|
setText(createLinkChapter, displayValue(detectedChapterTitle));
|
||||||
|
setText(createLinkScene, displayValue(detectedSceneTitle));
|
||||||
|
setHidden(sceneCreateLink, false);
|
||||||
|
setCreateLinkStatus(message);
|
||||||
|
|
||||||
|
const enabled = canUseCreateLinkActions();
|
||||||
|
if (createPlotDirectorSceneButton) {
|
||||||
|
createPlotDirectorSceneButton.disabled = !enabled;
|
||||||
|
createPlotDirectorSceneButton.textContent = "Create Scene in PlotDirector";
|
||||||
|
}
|
||||||
|
if (linkExistingSceneButton) {
|
||||||
|
linkExistingSceneButton.disabled = !enabled;
|
||||||
|
linkExistingSceneButton.textContent = "Link to Existing Scene";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const resetPlotDirectorScene = (message) => {
|
const resetPlotDirectorScene = (message) => {
|
||||||
setPlotDirectorSceneStatus(message);
|
setPlotDirectorSceneStatus(message);
|
||||||
setHidden(plotDirectorSceneDetails, true);
|
setHidden(plotDirectorSceneDetails, true);
|
||||||
setHidden(writingBriefBlock, true);
|
setHidden(writingBriefBlock, true);
|
||||||
setHidden(plotDirectorLinkGroups, true);
|
setHidden(plotDirectorLinkGroups, true);
|
||||||
|
hideSceneCreateLink();
|
||||||
currentResolutionMethod = "";
|
currentResolutionMethod = "";
|
||||||
currentResolutionAnchorType = detectedSceneAnchorId ? "SceneID Anchor" : detectedChapterAnchorId ? "ChapterID Anchor" : "Title Match";
|
currentResolutionAnchorType = detectedSceneAnchorId ? "SceneID Anchor" : detectedChapterAnchorId ? "ChapterID Anchor" : "Title Match";
|
||||||
storedAnchorInvalid = false;
|
storedAnchorInvalid = false;
|
||||||
@ -441,6 +498,30 @@
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getBookStructureChapters = async (bookId) => {
|
||||||
|
const structure = await fetchJson(`/api/word-companion/books/${bookId}/structure`);
|
||||||
|
return Array.isArray(structure?.chapters) ? structure.chapters : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const findCurrentChapterInChapters = (chapters) => {
|
||||||
|
const requestChapterTitle = normalizeTitleForResolve(detectedChapterTitle, "chapter").toLocaleLowerCase();
|
||||||
|
if (!requestChapterTitle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapters.find((chapter) =>
|
||||||
|
normalizeTitleForResolve(chapter.title, "chapter").toLocaleLowerCase() === requestChapterTitle) || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findCurrentChapter = async () => {
|
||||||
|
const book = selectedBook();
|
||||||
|
if (!book) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return findCurrentChapterInChapters(await getBookStructureChapters(book.bookId));
|
||||||
|
};
|
||||||
|
|
||||||
const loadResolvedCompanionData = async (bookId, sceneId, chapterId, resolutionMethod, anchorType) => {
|
const loadResolvedCompanionData = async (bookId, sceneId, chapterId, resolutionMethod, anchorType) => {
|
||||||
const companionData = await fetchJson(`/api/word-companion/scenes/${sceneId}/companion`);
|
const companionData = await fetchJson(`/api/word-companion/scenes/${sceneId}/companion`);
|
||||||
try {
|
try {
|
||||||
@ -452,6 +533,7 @@
|
|||||||
storedAnchorInvalid = false;
|
storedAnchorInvalid = false;
|
||||||
setPlotDirectorSceneStatus("Linked to PlotDirector");
|
setPlotDirectorSceneStatus("Linked to PlotDirector");
|
||||||
setDocumentMessage("");
|
setDocumentMessage("");
|
||||||
|
hideSceneCreateLink();
|
||||||
setResolvedScene(sceneId, chapterId, resolutionMethod);
|
setResolvedScene(sceneId, chapterId, resolutionMethod);
|
||||||
renderCompanionData(companionData);
|
renderCompanionData(companionData);
|
||||||
};
|
};
|
||||||
@ -1006,21 +1088,21 @@
|
|||||||
return control;
|
return control;
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachPlotDirectorIds = async () => {
|
const attachResolvedPlotDirectorIds = async (successMessage = "PlotDirector IDs attached successfully.") => {
|
||||||
if (!resolvedSceneId || !resolvedChapterId) {
|
if (!resolvedSceneId || !resolvedChapterId) {
|
||||||
setDocumentMessage("No resolved PlotDirector scene.");
|
setDocumentMessage("No resolved PlotDirector scene.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const replacingExistingAnchor = (detectedSceneAnchorId && detectedSceneAnchorId !== resolvedSceneId)
|
const replacingExistingAnchor = (detectedSceneAnchorId && detectedSceneAnchorId !== resolvedSceneId)
|
||||||
|| (detectedChapterAnchorId && detectedChapterAnchorId !== resolvedChapterId);
|
|| (detectedChapterAnchorId && detectedChapterAnchorId !== resolvedChapterId);
|
||||||
if (replacingExistingAnchor && !window.confirm("This scene is already linked. Replace the existing PlotDirector link?")) {
|
if (replacingExistingAnchor && !window.confirm("This scene is already linked. Replace the existing PlotDirector link?")) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||||
setDocumentMessage("Word document access is unavailable.");
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachPlotDirectorIdsButton) {
|
if (attachPlotDirectorIdsButton) {
|
||||||
@ -1051,14 +1133,16 @@
|
|||||||
storedAnchorInvalid = false;
|
storedAnchorInvalid = false;
|
||||||
currentResolutionAnchorType = "SceneID Anchor";
|
currentResolutionAnchorType = "SceneID Anchor";
|
||||||
currentResolutionMethod = "Anchor";
|
currentResolutionMethod = "Anchor";
|
||||||
setDocumentMessage("PlotDirector IDs attached successfully.");
|
setDocumentMessage(successMessage);
|
||||||
setDiagnostics({ lastError: "-" });
|
setDiagnostics({ lastError: "-" });
|
||||||
updateAnchorDisplay();
|
updateAnchorDisplay();
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to attach PlotDirector IDs.", error);
|
console.error("Unable to attach PlotDirector IDs.", error);
|
||||||
setDocumentMessage("Unable to attach PlotDirector IDs.");
|
setDocumentMessage("Unable to attach PlotDirector IDs.");
|
||||||
setDiagnostics({ lastError: errorText(error) });
|
setDiagnostics({ lastError: errorText(error) });
|
||||||
updateAnchorDisplay();
|
updateAnchorDisplay();
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
if (attachPlotDirectorIdsButton) {
|
if (attachPlotDirectorIdsButton) {
|
||||||
attachPlotDirectorIdsButton.textContent = detectedSceneAnchorId === resolvedSceneId
|
attachPlotDirectorIdsButton.textContent = detectedSceneAnchorId === resolvedSceneId
|
||||||
@ -1069,6 +1153,239 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const attachPlotDirectorIds = async () => {
|
||||||
|
await attachResolvedPlotDirectorIds();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveCreatedOrLinkedScene = async (sceneId, chapterId, successMessage) => {
|
||||||
|
const book = selectedBook();
|
||||||
|
if (!book) {
|
||||||
|
setCreateLinkStatus("Select a PlotDirector book first.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadResolvedCompanionData(book.bookId, sceneId, chapterId, "Manual link", "Title Match");
|
||||||
|
const anchored = await attachResolvedPlotDirectorIds(successMessage);
|
||||||
|
if (!anchored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCreateLinkStatus(successMessage);
|
||||||
|
hideSceneCreateLink();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeCreateSceneDialog = (confirmed) => {
|
||||||
|
if (pendingCreateSceneConfirmation) {
|
||||||
|
pendingCreateSceneConfirmation(confirmed);
|
||||||
|
pendingCreateSceneConfirmation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createSceneDialog?.close) {
|
||||||
|
createSceneDialog.close();
|
||||||
|
} else if (createSceneDialog) {
|
||||||
|
createSceneDialog.hidden = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmCreateScene = (sceneTitle, chapterTitle) => {
|
||||||
|
setText(createDialogScene, `"${sceneTitle}"`);
|
||||||
|
setText(createDialogChapter, `"${chapterTitle}"`);
|
||||||
|
|
||||||
|
if (!createSceneDialog) {
|
||||||
|
return Promise.resolve(window.confirm(`Create PlotDirector scene:\n\n"${sceneTitle}"\n\nwithin chapter:\n\n"${chapterTitle}"\n\n?`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
pendingCreateSceneConfirmation = resolve;
|
||||||
|
if (createSceneDialog.showModal) {
|
||||||
|
createSceneDialog.showModal();
|
||||||
|
} else {
|
||||||
|
createSceneDialog.hidden = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPlotDirectorScene = async () => {
|
||||||
|
const book = selectedBook();
|
||||||
|
if (!book || !detectedSceneTitle) {
|
||||||
|
showSceneCreateLink("Scene not found in PlotDirector.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sceneTitle = normalizeTitleForResolve(detectedSceneTitle, "scene");
|
||||||
|
const chapterTitle = normalizeTitleForResolve(detectedChapterTitle, "chapter");
|
||||||
|
const confirmed = await confirmCreateScene(sceneTitle, chapterTitle);
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createPlotDirectorSceneButton) {
|
||||||
|
createPlotDirectorSceneButton.disabled = true;
|
||||||
|
createPlotDirectorSceneButton.textContent = "Creating...";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chapter = await findCurrentChapter();
|
||||||
|
if (!chapter) {
|
||||||
|
showSceneCreateLink("This chapter does not exist in PlotDirector. Create or link the chapter first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await postJson(`/api/word-companion/books/${book.bookId}/chapters/${chapter.chapterId}/scenes`, {
|
||||||
|
sceneTitle
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response?.sceneId || !response?.chapterId) {
|
||||||
|
throw new Error("Scene creation did not return a scene ID.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await resolveCreatedOrLinkedScene(response.sceneId, response.chapterId, "Scene created and linked successfully.");
|
||||||
|
} catch (error) {
|
||||||
|
setCreateLinkStatus("Unable to create scene.");
|
||||||
|
setDiagnostics({ lastError: errorText(error) });
|
||||||
|
} finally {
|
||||||
|
if (createPlotDirectorSceneButton) {
|
||||||
|
createPlotDirectorSceneButton.disabled = !canUseCreateLinkActions();
|
||||||
|
createPlotDirectorSceneButton.textContent = "Create Scene in PlotDirector";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderLinkSceneOptions = () => {
|
||||||
|
if (!linkSceneOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = normalizeWhitespace(linkSceneSearch?.value || "").toLocaleLowerCase();
|
||||||
|
const rows = linkDialogScenes.filter((scene) => !query || String(scene.title || "").toLocaleLowerCase().includes(query));
|
||||||
|
linkSceneOptions.innerHTML = "";
|
||||||
|
selectedLinkSceneId = null;
|
||||||
|
if (linkSelectedSceneButton) {
|
||||||
|
linkSelectedSceneButton.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
const empty = document.createElement("p");
|
||||||
|
empty.className = "word-companion-empty-list";
|
||||||
|
empty.textContent = "No scenes found.";
|
||||||
|
linkSceneOptions.append(empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const scene of rows) {
|
||||||
|
const sceneId = Number.parseInt(scene.sceneId || "", 10);
|
||||||
|
if (!Number.isInteger(sceneId) || sceneId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = document.createElement("label");
|
||||||
|
label.className = "word-companion-scene-option";
|
||||||
|
|
||||||
|
const radio = document.createElement("input");
|
||||||
|
radio.type = "radio";
|
||||||
|
radio.name = "word-companion-link-scene";
|
||||||
|
radio.value = String(sceneId);
|
||||||
|
radio.addEventListener("change", () => {
|
||||||
|
selectedLinkSceneId = sceneId;
|
||||||
|
if (linkSelectedSceneButton) {
|
||||||
|
linkSelectedSceneButton.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = document.createElement("span");
|
||||||
|
text.textContent = scene.title || "Untitled scene";
|
||||||
|
|
||||||
|
label.append(radio, text);
|
||||||
|
linkSceneOptions.append(label);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openLinkExistingSceneDialog = async () => {
|
||||||
|
const book = selectedBook();
|
||||||
|
if (!book || !detectedSceneTitle) {
|
||||||
|
showSceneCreateLink("Scene not found in PlotDirector.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkExistingSceneButton) {
|
||||||
|
linkExistingSceneButton.disabled = true;
|
||||||
|
linkExistingSceneButton.textContent = "Loading...";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chapters = await getBookStructureChapters(book.bookId);
|
||||||
|
const chapter = findCurrentChapterInChapters(chapters);
|
||||||
|
if (!chapter) {
|
||||||
|
showSceneCreateLink("This chapter does not exist in PlotDirector. Create or link the chapter first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
linkDialogScenes = Array.isArray(chapter.scenes) ? chapter.scenes : [];
|
||||||
|
selectedLinkSceneId = null;
|
||||||
|
if (linkSceneSearch) {
|
||||||
|
linkSceneSearch.value = "";
|
||||||
|
}
|
||||||
|
setText(linkDialogStatus, "");
|
||||||
|
renderLinkSceneOptions();
|
||||||
|
|
||||||
|
if (linkExistingDialog?.showModal) {
|
||||||
|
linkExistingDialog.showModal();
|
||||||
|
} else if (linkExistingDialog) {
|
||||||
|
linkExistingDialog.hidden = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setCreateLinkStatus("Unable to load scenes.");
|
||||||
|
setDiagnostics({ lastError: errorText(error) });
|
||||||
|
} finally {
|
||||||
|
if (linkExistingSceneButton) {
|
||||||
|
linkExistingSceneButton.disabled = !canUseCreateLinkActions();
|
||||||
|
linkExistingSceneButton.textContent = "Link to Existing Scene";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeLinkExistingSceneDialog = () => {
|
||||||
|
if (linkExistingDialog?.close) {
|
||||||
|
linkExistingDialog.close();
|
||||||
|
} else if (linkExistingDialog) {
|
||||||
|
linkExistingDialog.hidden = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkSelectedExistingScene = async () => {
|
||||||
|
const book = selectedBook();
|
||||||
|
const scene = linkDialogScenes.find((item) => item.sceneId === selectedLinkSceneId);
|
||||||
|
if (!book || !scene) {
|
||||||
|
setText(linkDialogStatus, "Select a scene.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkSelectedSceneButton) {
|
||||||
|
linkSelectedSceneButton.disabled = true;
|
||||||
|
linkSelectedSceneButton.textContent = "Linking...";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chapter = await findCurrentChapter();
|
||||||
|
if (!chapter) {
|
||||||
|
throw new Error("This chapter does not exist in PlotDirector. Create or link the chapter first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
closeLinkExistingSceneDialog();
|
||||||
|
await resolveCreatedOrLinkedScene(scene.sceneId, chapter.chapterId, "Scene linked successfully.");
|
||||||
|
} catch (error) {
|
||||||
|
setText(linkDialogStatus, "Unable to link scene.");
|
||||||
|
setCreateLinkStatus("Unable to link scene.");
|
||||||
|
setDiagnostics({ lastError: errorText(error) });
|
||||||
|
} finally {
|
||||||
|
if (linkSelectedSceneButton) {
|
||||||
|
linkSelectedSceneButton.disabled = !selectedLinkSceneId;
|
||||||
|
linkSelectedSceneButton.textContent = "Link Scene";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const syncSceneProgress = async () => {
|
const syncSceneProgress = async () => {
|
||||||
if (!resolvedSceneId) {
|
if (!resolvedSceneId) {
|
||||||
setSyncStatus("No resolved PlotDirector scene.");
|
setSyncStatus("No resolved PlotDirector scene.");
|
||||||
@ -1237,6 +1554,9 @@
|
|||||||
chapterId: resolveResponse?.chapterId,
|
chapterId: resolveResponse?.chapterId,
|
||||||
sceneId: resolveResponse?.sceneId
|
sceneId: resolveResponse?.sceneId
|
||||||
});
|
});
|
||||||
|
if (!hadInvalidAnchor) {
|
||||||
|
showSceneCreateLink("Scene not found in PlotDirector.");
|
||||||
|
}
|
||||||
restorePlotDirectorSceneButton();
|
restorePlotDirectorSceneButton();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1406,6 +1726,17 @@
|
|||||||
syncSceneProgressButton?.addEventListener("click", syncSceneProgress);
|
syncSceneProgressButton?.addEventListener("click", syncSceneProgress);
|
||||||
saveSceneLinksButton?.addEventListener("click", saveSceneLinks);
|
saveSceneLinksButton?.addEventListener("click", saveSceneLinks);
|
||||||
attachPlotDirectorIdsButton?.addEventListener("click", attachPlotDirectorIds);
|
attachPlotDirectorIdsButton?.addEventListener("click", attachPlotDirectorIds);
|
||||||
|
createPlotDirectorSceneButton?.addEventListener("click", createPlotDirectorScene);
|
||||||
|
linkExistingSceneButton?.addEventListener("click", openLinkExistingSceneDialog);
|
||||||
|
linkSceneSearch?.addEventListener("input", renderLinkSceneOptions);
|
||||||
|
linkSelectedSceneButton?.addEventListener("click", linkSelectedExistingScene);
|
||||||
|
linkDialogCancel?.addEventListener("click", closeLinkExistingSceneDialog);
|
||||||
|
createDialogConfirm?.addEventListener("click", () => closeCreateSceneDialog(true));
|
||||||
|
createDialogCancel?.addEventListener("click", () => closeCreateSceneDialog(false));
|
||||||
|
createSceneDialog?.addEventListener("cancel", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
closeCreateSceneDialog(false);
|
||||||
|
});
|
||||||
runDiagnosticsButton?.addEventListener("click", runDiagnostics);
|
runDiagnosticsButton?.addEventListener("click", runDiagnostics);
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user