Phase 6C Plot Line maintenance.
This commit is contained in:
parent
023ca1404e
commit
ba5960f907
@ -107,19 +107,33 @@ public sealed class ScenesController(
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> AddThreadEvent(ThreadEventCreateViewModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
await plots.AddThreadEventAsync(model);
|
||||
if (model.ReturnProjectID.HasValue)
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return RedirectToAction("Index", "Timeline", new
|
||||
{
|
||||
projectId = model.ReturnProjectID.Value,
|
||||
bookId = model.ReturnBookID,
|
||||
selectedSceneId = model.SceneID
|
||||
});
|
||||
TempData["ThreadEventAddError"] = ex.Message;
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Edit), new { id = model.SceneID });
|
||||
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> UpdateThreadEvent(ThreadEventEditViewModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
await plots.UpdateThreadEventAsync(model);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
TempData[$"ThreadEventEditError:{model.ThreadEventID}"] = ex.Message;
|
||||
}
|
||||
|
||||
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
||||
@ -126,6 +126,7 @@ public interface IPlotRepository
|
||||
Task<IReadOnlyList<ThreadEvent>> ListThreadEventsBySceneAsync(int sceneId);
|
||||
Task<IReadOnlyList<ThreadEvent>> ListThreadEventsByPlotThreadAsync(int plotThreadId);
|
||||
Task<int> SaveThreadEventAsync(ThreadEvent threadEvent);
|
||||
Task SaveThreadEventTargetsAsync(int threadEventId, IEnumerable<int> plotLineIds);
|
||||
Task DeleteThreadEventAsync(int threadEventId);
|
||||
Task<IReadOnlyList<SceneOption>> ListSceneOptionsAsync(int projectId);
|
||||
}
|
||||
@ -1950,15 +1951,47 @@ public sealed class PlotRepository(ISqlConnectionFactory connectionFactory) : IP
|
||||
public async Task<IReadOnlyList<ThreadEvent>> ListThreadEventsBySceneAsync(int sceneId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<ThreadEvent>("dbo.ThreadEvent_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
var rows = (await connection.QueryAsync<ThreadEvent>("dbo.ThreadEvent_ListByScene", new { SceneID = sceneId }, commandType: CommandType.StoredProcedure)).ToList();
|
||||
await PopulateThreadEventTargetsAsync(connection, rows);
|
||||
return rows;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ThreadEvent>> ListThreadEventsByPlotThreadAsync(int plotThreadId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<ThreadEvent>("dbo.ThreadEvent_ListByPlotThread", new { PlotThreadID = plotThreadId }, commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
var rows = (await connection.QueryAsync<ThreadEvent>("dbo.ThreadEvent_ListByPlotThread", new { PlotThreadID = plotThreadId }, commandType: CommandType.StoredProcedure)).ToList();
|
||||
await PopulateThreadEventTargetsAsync(connection, rows);
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static async Task PopulateThreadEventTargetsAsync(IDbConnection connection, IReadOnlyList<ThreadEvent> threadEvents)
|
||||
{
|
||||
var threadEventIds = threadEvents.Select(x => x.ThreadEventID).ToList();
|
||||
if (threadEventIds.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetRows = await connection.QueryAsync<ThreadEventTargetRow>(
|
||||
"SELECT PlotEventID, PlotLineID FROM dbo.PlotEventTargetPlotLines WHERE PlotEventID IN @ThreadEventIDs;",
|
||||
new { ThreadEventIDs = threadEventIds });
|
||||
var targetsByEvent = targetRows
|
||||
.GroupBy(x => x.PlotEventID)
|
||||
.ToDictionary(x => x.Key, x => (IReadOnlyList<int>)x.Select(row => row.PlotLineID).ToList());
|
||||
|
||||
foreach (var threadEvent in threadEvents)
|
||||
{
|
||||
if (targetsByEvent.TryGetValue(threadEvent.ThreadEventID, out var targets))
|
||||
{
|
||||
threadEvent.TargetPlotLineIDs = targets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ThreadEventTargetRow
|
||||
{
|
||||
public int PlotEventID { get; set; }
|
||||
public int PlotLineID { get; set; }
|
||||
}
|
||||
|
||||
public async Task<int> SaveThreadEventAsync(ThreadEvent threadEvent)
|
||||
@ -1980,6 +2013,24 @@ public sealed class PlotRepository(ISqlConnectionFactory connectionFactory) : IP
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task SaveThreadEventTargetsAsync(int threadEventId, IEnumerable<int> plotLineIds)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var targetIds = plotLineIds.Where(id => id > 0).Distinct().ToList();
|
||||
await connection.ExecuteAsync(
|
||||
"DELETE FROM dbo.PlotEventTargetPlotLines WHERE PlotEventID = @ThreadEventID;",
|
||||
new { ThreadEventID = threadEventId });
|
||||
|
||||
if (targetIds.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
"INSERT dbo.PlotEventTargetPlotLines (PlotEventID, PlotLineID) VALUES (@ThreadEventID, @PlotLineID);",
|
||||
targetIds.Select(plotLineId => new { ThreadEventID = threadEventId, PlotLineID = plotLineId }));
|
||||
}
|
||||
|
||||
public async Task DeleteThreadEventAsync(int threadEventId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
|
||||
@ -469,6 +469,7 @@ public sealed class ThreadEvent
|
||||
public string PlotEventTypeName { get; set; } = "Progress";
|
||||
public int? TargetPlotLineID { get; set; }
|
||||
public string? TargetPlotLineName { get; set; }
|
||||
public IReadOnlyList<int> TargetPlotLineIDs { get; set; } = [];
|
||||
public string EventTitle { get; set; } = string.Empty;
|
||||
public string? EventDescription { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
|
||||
@ -122,6 +122,7 @@ public interface IPlotService
|
||||
Task<int> SavePlotThreadAsync(PlotThreadEditViewModel model);
|
||||
Task ArchivePlotThreadAsync(int plotThreadId);
|
||||
Task<int> AddThreadEventAsync(ThreadEventCreateViewModel model);
|
||||
Task UpdateThreadEventAsync(ThreadEventEditViewModel model);
|
||||
Task DeleteThreadEventAsync(int threadEventId);
|
||||
}
|
||||
|
||||
@ -1081,8 +1082,11 @@ public sealed class SceneService(
|
||||
.ToList();
|
||||
model.PlotLineOptions = ToOptionalSelectList(plotLines, x => x.PlotLineID, x => x.PlotLineName);
|
||||
model.PlotThreadOptions = ToOptionalSelectList(plotThreads, x => x.PlotThreadID, x => $"{x.PlotLineName}: {x.ThreadTitle}");
|
||||
model.PlotLineEventOptions = plotLines;
|
||||
model.PlotThreadEventOptions = plotThreads;
|
||||
model.ThreadTypeOptions = ToOptionalSelectList(plotLookups.ThreadTypes, x => x.ThreadTypeID, x => x.TypeName);
|
||||
model.ThreadEventTypeOptions = ChapterService.ToSelectList(plotLookups.ThreadEventTypes, x => x.ThreadEventTypeID, x => x.TypeName);
|
||||
model.PlotEventTypeOptions = ChapterService.ToSelectList(plotLookups.PlotEventTypes, x => x.PlotEventTypeID, x => x.TypeName);
|
||||
model.AssetOptions = ToOptionalSelectList(projectAssets, x => x.StoryAssetID, x => x.AssetName);
|
||||
model.AssetKindOptions = ChapterService.ToSelectList(assetLookups.AssetKinds, x => x.AssetKindID, x => x.KindName);
|
||||
model.AssetStateOptions = ToOptionalSelectList(assetLookups.AssetStates, x => x.AssetStateID, x => x.StateName);
|
||||
@ -1107,6 +1111,9 @@ public sealed class SceneService(
|
||||
ReturnProjectID = model.ReturnProjectID,
|
||||
ReturnBookID = model.ReturnBookID,
|
||||
EventTitle = string.Empty,
|
||||
PlotEventTypeID = plotLookups.PlotEventTypes.FirstOrDefault(x => x.TypeName == "Progress")?.PlotEventTypeID
|
||||
?? plotLookups.PlotEventTypes.FirstOrDefault()?.PlotEventTypeID
|
||||
?? 0,
|
||||
EventTypeID = plotLookups.ThreadEventTypes.FirstOrDefault(x => x.TypeName == "Mentioned")?.ThreadEventTypeID
|
||||
?? plotLookups.ThreadEventTypes.FirstOrDefault()?.ThreadEventTypeID
|
||||
?? 0
|
||||
@ -2255,12 +2262,20 @@ public sealed class PlotService(IProjectRepository projects, IBookRepository boo
|
||||
{
|
||||
var plotThreadId = model.PlotThreadID.GetValueOrDefault();
|
||||
var lookupData = await plots.GetLookupsAsync();
|
||||
if (model.PlotEventTypeID <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Choose an event type.");
|
||||
}
|
||||
|
||||
var eventType = lookupData.ThreadEventTypes.FirstOrDefault(x => x.ThreadEventTypeID == model.EventTypeID)
|
||||
?? lookupData.ThreadEventTypes.First();
|
||||
var plotEventType = lookupData.PlotEventTypes.FirstOrDefault(x => x.PlotEventTypeID == model.PlotEventTypeID)
|
||||
?? throw new InvalidOperationException("Choose a valid event type.");
|
||||
var title = string.IsNullOrWhiteSpace(model.EventTitle)
|
||||
? eventType.TypeName
|
||||
: model.EventTitle.Trim();
|
||||
|
||||
var sourcePlotLineId = model.PlotLineID;
|
||||
if (plotThreadId == 0)
|
||||
{
|
||||
if (!model.PlotLineID.HasValue)
|
||||
@ -2284,16 +2299,53 @@ public sealed class PlotService(IProjectRepository projects, IBookRepository boo
|
||||
Importance = 5
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingPlotThread = await plots.GetPlotThreadAsync(plotThreadId);
|
||||
if (existingPlotThread is null)
|
||||
{
|
||||
throw new InvalidOperationException("Choose a valid plot thread.");
|
||||
}
|
||||
|
||||
sourcePlotLineId = existingPlotThread.PlotLineID;
|
||||
}
|
||||
|
||||
if (!sourcePlotLineId.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException("Choose a source plot line.");
|
||||
}
|
||||
|
||||
var projectPlotLines = await plots.ListPlotLinesAsync(model.ReturnProjectID.GetValueOrDefault());
|
||||
if (projectPlotLines.Count == 0)
|
||||
{
|
||||
var sourcePlotLine = await plots.GetPlotLineAsync(sourcePlotLineId.Value);
|
||||
projectPlotLines = sourcePlotLine is null ? [] : await plots.ListPlotLinesAsync(sourcePlotLine.ProjectID);
|
||||
}
|
||||
|
||||
ValidatePlotEventTargets(
|
||||
plotEventType.TypeName,
|
||||
sourcePlotLineId.Value,
|
||||
projectPlotLines,
|
||||
model.TargetPlotLineID,
|
||||
model.SelectedTargetPlotLineIDs);
|
||||
var targetPlotLineId = plotEventType.TypeName is "Branch" or "Merge"
|
||||
? model.TargetPlotLineID
|
||||
: null;
|
||||
var splitTargetPlotLineIds = plotEventType.TypeName == "Split"
|
||||
? model.SelectedTargetPlotLineIDs
|
||||
: [];
|
||||
|
||||
var threadEventId = await plots.SaveThreadEventAsync(new ThreadEvent
|
||||
{
|
||||
PlotThreadID = plotThreadId,
|
||||
SceneID = model.SceneID,
|
||||
EventTypeID = eventType.ThreadEventTypeID,
|
||||
PlotEventTypeID = lookupData.PlotEventTypes.FirstOrDefault(x => x.TypeName == "Progress")?.PlotEventTypeID ?? 2,
|
||||
PlotEventTypeID = plotEventType.PlotEventTypeID,
|
||||
TargetPlotLineID = targetPlotLineId,
|
||||
EventTitle = title,
|
||||
EventDescription = model.EventDescription
|
||||
});
|
||||
await plots.SaveThreadEventTargetsAsync(threadEventId, splitTargetPlotLineIds);
|
||||
var plotThread = await plots.GetPlotThreadAsync(plotThreadId);
|
||||
if (plotThread is not null)
|
||||
{
|
||||
@ -2302,6 +2354,100 @@ public sealed class PlotService(IProjectRepository projects, IBookRepository boo
|
||||
return threadEventId;
|
||||
}
|
||||
|
||||
public async Task UpdateThreadEventAsync(ThreadEventEditViewModel model)
|
||||
{
|
||||
var lookupData = await plots.GetLookupsAsync();
|
||||
if (model.PlotEventTypeID <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Choose an event type.");
|
||||
}
|
||||
|
||||
var eventType = lookupData.ThreadEventTypes.FirstOrDefault(x => x.ThreadEventTypeID == model.EventTypeID)
|
||||
?? throw new InvalidOperationException("Choose a valid marker type.");
|
||||
var plotEventType = lookupData.PlotEventTypes.FirstOrDefault(x => x.PlotEventTypeID == model.PlotEventTypeID)
|
||||
?? throw new InvalidOperationException("Choose a valid event type.");
|
||||
var plotThread = await plots.GetPlotThreadAsync(model.PlotThreadID)
|
||||
?? throw new InvalidOperationException("Choose a valid plot thread.");
|
||||
|
||||
var projectPlotLines = await plots.ListPlotLinesAsync(plotThread.ProjectID);
|
||||
ValidatePlotEventTargets(
|
||||
plotEventType.TypeName,
|
||||
plotThread.PlotLineID,
|
||||
projectPlotLines,
|
||||
model.TargetPlotLineID,
|
||||
model.SelectedTargetPlotLineIDs);
|
||||
|
||||
var targetPlotLineId = plotEventType.TypeName is "Branch" or "Merge"
|
||||
? model.TargetPlotLineID
|
||||
: null;
|
||||
var splitTargetPlotLineIds = plotEventType.TypeName == "Split"
|
||||
? model.SelectedTargetPlotLineIDs
|
||||
: [];
|
||||
|
||||
await plots.SaveThreadEventAsync(new ThreadEvent
|
||||
{
|
||||
ThreadEventID = model.ThreadEventID,
|
||||
PlotThreadID = plotThread.PlotThreadID,
|
||||
SceneID = model.SceneID,
|
||||
EventTypeID = eventType.ThreadEventTypeID,
|
||||
PlotEventTypeID = plotEventType.PlotEventTypeID,
|
||||
TargetPlotLineID = targetPlotLineId,
|
||||
EventTitle = string.IsNullOrWhiteSpace(model.EventTitle) ? eventType.TypeName : model.EventTitle.Trim(),
|
||||
EventDescription = model.EventDescription
|
||||
});
|
||||
await plots.SaveThreadEventTargetsAsync(model.ThreadEventID, splitTargetPlotLineIds);
|
||||
await activity.RecordAsync(plotThread.ProjectID, "Updated", "Thread Event", model.ThreadEventID, model.EventTitle);
|
||||
}
|
||||
|
||||
private static void ValidatePlotEventTargets(
|
||||
string plotEventTypeName,
|
||||
int sourcePlotLineId,
|
||||
IReadOnlyList<PlotLineItem> projectPlotLines,
|
||||
int? targetPlotLineId,
|
||||
IEnumerable<int> selectedTargetPlotLineIds)
|
||||
{
|
||||
var projectPlotLineIds = projectPlotLines.Select(x => x.PlotLineID).ToHashSet();
|
||||
|
||||
if (plotEventTypeName is "Branch" or "Merge")
|
||||
{
|
||||
if (!targetPlotLineId.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException(plotEventTypeName == "Branch"
|
||||
? "Choose a target plot line for the branch event."
|
||||
: "Choose the plot line this event merges into.");
|
||||
}
|
||||
|
||||
if (targetPlotLineId.Value == sourcePlotLineId)
|
||||
{
|
||||
throw new InvalidOperationException("Target plot line cannot be the source plot line.");
|
||||
}
|
||||
|
||||
if (!projectPlotLineIds.Contains(targetPlotLineId.Value))
|
||||
{
|
||||
throw new InvalidOperationException("Target plot line must belong to this project.");
|
||||
}
|
||||
}
|
||||
|
||||
if (plotEventTypeName == "Split")
|
||||
{
|
||||
var targets = selectedTargetPlotLineIds.Where(id => id > 0).Distinct().ToList();
|
||||
if (targets.Count < 2)
|
||||
{
|
||||
throw new InvalidOperationException("Choose at least two target plot lines for a split event.");
|
||||
}
|
||||
|
||||
if (targets.Contains(sourcePlotLineId))
|
||||
{
|
||||
throw new InvalidOperationException("Split target plot lines cannot include the source plot line.");
|
||||
}
|
||||
|
||||
if (targets.Any(id => !projectPlotLineIds.Contains(id)))
|
||||
{
|
||||
throw new InvalidOperationException("Split target plot lines must belong to this project.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteThreadEventAsync(int threadEventId) => plots.DeleteThreadEventAsync(threadEventId);
|
||||
|
||||
private static int GetDefaultThreadTypeId(PlotLookupData lookupData, string eventTypeName)
|
||||
|
||||
@ -178,8 +178,11 @@ public sealed class SceneEditViewModel
|
||||
public RelationshipEventCreateViewModel NewRelationshipEvent { get; set; } = new();
|
||||
public IReadOnlyList<SelectListItem> PlotLineOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> PlotThreadOptions { get; set; } = [];
|
||||
public IReadOnlyList<PlotLineItem> PlotLineEventOptions { get; set; } = [];
|
||||
public IReadOnlyList<PlotThread> PlotThreadEventOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> ThreadTypeOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> ThreadEventTypeOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> PlotEventTypeOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> AssetOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> AssetKindOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> AssetStateOptions { get; set; } = [];
|
||||
@ -801,6 +804,26 @@ public sealed class ThreadEventCreateViewModel
|
||||
public string? NewThreadTitle { get; set; }
|
||||
public int? NewThreadTypeID { get; set; }
|
||||
public int EventTypeID { get; set; }
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Choose an event type.")]
|
||||
public int PlotEventTypeID { get; set; } = 2;
|
||||
public int? TargetPlotLineID { get; set; }
|
||||
public List<int> SelectedTargetPlotLineIDs { get; set; } = [];
|
||||
public string EventTitle { get; set; } = string.Empty;
|
||||
public string? EventDescription { get; set; }
|
||||
public int? ReturnProjectID { get; set; }
|
||||
public int? ReturnBookID { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ThreadEventEditViewModel
|
||||
{
|
||||
public int ThreadEventID { get; set; }
|
||||
public int SceneID { get; set; }
|
||||
public int PlotThreadID { get; set; }
|
||||
public int EventTypeID { get; set; }
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Choose an event type.")]
|
||||
public int PlotEventTypeID { get; set; } = 2;
|
||||
public int? TargetPlotLineID { get; set; }
|
||||
public List<int> SelectedTargetPlotLineIDs { get; set; } = [];
|
||||
public string EventTitle { get; set; } = string.Empty;
|
||||
public string? EventDescription { get; set; }
|
||||
public int? ReturnProjectID { get; set; }
|
||||
|
||||
@ -695,7 +695,7 @@
|
||||
<div>
|
||||
<span class="thread-event-marker">@threadEvent.MarkerText</span>
|
||||
<strong>@threadEvent.EventTitle</strong>
|
||||
<span class="muted">@threadEvent.PlotLineName / @threadEvent.ThreadTitle</span>
|
||||
<span class="muted">@threadEvent.PlotEventTypeName / @threadEvent.PlotLineName / @threadEvent.ThreadTitle</span>
|
||||
</div>
|
||||
<form asp-controller="Scenes" asp-action="DeleteThreadEvent" method="post">
|
||||
<input type="hidden" name="id" value="@threadEvent.ThreadEventID" />
|
||||
@ -704,28 +704,149 @@
|
||||
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Remove</button>
|
||||
</form>
|
||||
<details class="mt-2">
|
||||
<summary>Edit</summary>
|
||||
<form asp-controller="Scenes" asp-action="UpdateThreadEvent" method="post" class="thread-event-create mt-2" data-thread-event-form>
|
||||
<input type="hidden" name="ThreadEventID" value="@threadEvent.ThreadEventID" />
|
||||
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||
<input type="hidden" name="PlotThreadID" value="@threadEvent.PlotThreadID" />
|
||||
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||
<input type="hidden" data-thread-event-source-plot-line value="@threadEvent.PlotLineID" />
|
||||
@if (TempData[$"ThreadEventEditError:{threadEvent.ThreadEventID}"] is string editThreadEventError)
|
||||
{
|
||||
<p class="text-danger form-helper-message">@editThreadEventError</p>
|
||||
}
|
||||
<div class="row g-2">
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="PlotEventTypeID-@threadEvent.ThreadEventID">Event type</label>
|
||||
<select class="form-select form-select-sm" id="PlotEventTypeID-@threadEvent.ThreadEventID" name="PlotEventTypeID" data-plot-event-type>
|
||||
<option value="">Choose event type</option>
|
||||
@foreach (var eventType in Model.PlotEventTypeOptions)
|
||||
{
|
||||
if (eventType.Value == threadEvent.PlotEventTypeID.ToString())
|
||||
{
|
||||
<option value="@eventType.Value" selected>@eventType.Text</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@eventType.Value">@eventType.Text</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12" data-plot-event-target="branch">
|
||||
<label class="form-label" for="TargetPlotLineID-@threadEvent.ThreadEventID">Target plot line</label>
|
||||
<select class="form-select form-select-sm" id="TargetPlotLineID-@threadEvent.ThreadEventID" name="TargetPlotLineID" data-thread-event-target-single>
|
||||
<option value="">Choose target plot line</option>
|
||||
@foreach (var plotLine in Model.PlotLineEventOptions)
|
||||
{
|
||||
if (threadEvent.TargetPlotLineID == plotLine.PlotLineID)
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option selected>@plotLine.PlotLineName</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option>@plotLine.PlotLineName</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12" data-plot-event-target="merge">
|
||||
<label class="form-label" for="MergeTargetPlotLineID-@threadEvent.ThreadEventID">Merge into plot line</label>
|
||||
<select class="form-select form-select-sm" id="MergeTargetPlotLineID-@threadEvent.ThreadEventID" data-thread-event-target-merge>
|
||||
<option value="">Choose merge target</option>
|
||||
@foreach (var plotLine in Model.PlotLineEventOptions)
|
||||
{
|
||||
if (threadEvent.TargetPlotLineID == plotLine.PlotLineID)
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option selected>@plotLine.PlotLineName</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option>@plotLine.PlotLineName</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12" data-plot-event-target="split">
|
||||
<label class="form-label" for="SelectedTargetPlotLineIDs-@threadEvent.ThreadEventID">Target plot lines</label>
|
||||
<select class="form-select form-select-sm" id="SelectedTargetPlotLineIDs-@threadEvent.ThreadEventID" name="SelectedTargetPlotLineIDs" multiple size="4" data-thread-event-target-split>
|
||||
@foreach (var plotLine in Model.PlotLineEventOptions)
|
||||
{
|
||||
if (threadEvent.TargetPlotLineIDs.Contains(plotLine.PlotLineID))
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option selected>@plotLine.PlotLineName</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option>@plotLine.PlotLineName</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="EventTypeID-@threadEvent.ThreadEventID">Marker type</label>
|
||||
<select class="form-select form-select-sm" id="EventTypeID-@threadEvent.ThreadEventID" name="EventTypeID">
|
||||
@foreach (var markerType in Model.ThreadEventTypeOptions)
|
||||
{
|
||||
if (markerType.Value == threadEvent.EventTypeID.ToString())
|
||||
{
|
||||
<option value="@markerType.Value" selected>@markerType.Text</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@markerType.Value">@markerType.Text</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="EventTitle-@threadEvent.ThreadEventID">Event title</label>
|
||||
<input class="form-control form-control-sm" id="EventTitle-@threadEvent.ThreadEventID" name="EventTitle" value="@threadEvent.EventTitle" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="EventDescription-@threadEvent.ThreadEventID">Description</label>
|
||||
<textarea class="form-control form-control-sm" id="EventDescription-@threadEvent.ThreadEventID" name="EventDescription" rows="3">@threadEvent.EventDescription</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Save event</button>
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<form asp-controller="Scenes" asp-action="AddThreadEvent" method="post" class="thread-event-create">
|
||||
<form asp-controller="Scenes" asp-action="AddThreadEvent" method="post" class="thread-event-create" data-thread-event-form>
|
||||
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||
<p class="form-text mb-2">Choose an existing thread, or choose a plot line to create one from the event title.</p>
|
||||
@if (TempData["ThreadEventAddError"] is string addThreadEventError)
|
||||
{
|
||||
<p class="text-danger form-helper-message">@addThreadEventError</p>
|
||||
}
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="PlotLineID">Plot line</label>
|
||||
<select class="form-select form-select-sm" id="PlotLineID" name="PlotLineID" asp-items="Model.PlotLineOptions">
|
||||
<select class="form-select form-select-sm" id="PlotLineID" name="PlotLineID" data-thread-event-source-plot-line>
|
||||
<option value="">Choose plot line</option>
|
||||
@foreach (var plotLine in Model.PlotLineEventOptions)
|
||||
{
|
||||
<option value="@plotLine.PlotLineID">@plotLine.PlotLineName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="PlotThreadID">Existing plot thread</label>
|
||||
<select class="form-select form-select-sm" id="PlotThreadID" name="PlotThreadID" asp-items="Model.PlotThreadOptions">
|
||||
<select class="form-select form-select-sm" id="PlotThreadID" name="PlotThreadID" data-thread-event-source-thread>
|
||||
<option value="">Create or choose a thread</option>
|
||||
@foreach (var plotThread in Model.PlotThreadEventOptions)
|
||||
{
|
||||
<option value="@plotThread.PlotThreadID" data-plot-line-id="@plotThread.PlotLineID">@plotThread.PlotLineName: @plotThread.ThreadTitle</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
@ -740,6 +861,52 @@
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="EventTypeID">Event type</label>
|
||||
<select class="form-select form-select-sm" id="PlotEventTypeID" name="PlotEventTypeID" data-plot-event-type>
|
||||
<option value="">Choose event type</option>
|
||||
@foreach (var eventType in Model.PlotEventTypeOptions)
|
||||
{
|
||||
if (eventType.Value == Model.NewThreadEvent.PlotEventTypeID.ToString())
|
||||
{
|
||||
<option value="@eventType.Value" selected>@eventType.Text</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@eventType.Value">@eventType.Text</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12" data-plot-event-target="branch">
|
||||
<label class="form-label" for="TargetPlotLineID">Target plot line</label>
|
||||
<select class="form-select form-select-sm" id="TargetPlotLineID" name="TargetPlotLineID" data-thread-event-target-single>
|
||||
<option value="">Choose target plot line</option>
|
||||
@foreach (var plotLine in Model.PlotLineEventOptions)
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option>@plotLine.PlotLineName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12" data-plot-event-target="merge">
|
||||
<label class="form-label" for="MergeTargetPlotLineID">Merge into plot line</label>
|
||||
<select class="form-select form-select-sm" id="MergeTargetPlotLineID" data-thread-event-target-merge>
|
||||
<option value="">Choose merge target</option>
|
||||
@foreach (var plotLine in Model.PlotLineEventOptions)
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option>@plotLine.PlotLineName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12" data-plot-event-target="split">
|
||||
<label class="form-label" for="SelectedTargetPlotLineIDs">Target plot lines</label>
|
||||
<select class="form-select form-select-sm" id="SelectedTargetPlotLineIDs" name="SelectedTargetPlotLineIDs" multiple size="4" data-thread-event-target-split>
|
||||
@foreach (var plotLine in Model.PlotLineEventOptions)
|
||||
{
|
||||
<option value="@plotLine.PlotLineID" data-target-option>@plotLine.PlotLineName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="EventTypeID">Marker type</label>
|
||||
<select class="form-select form-select-sm" id="EventTypeID" name="EventTypeID" asp-items="Model.ThreadEventTypeOptions"></select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
@ -754,6 +921,53 @@
|
||||
|
||||
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add thread event</button>
|
||||
</form>
|
||||
<script>
|
||||
(() => {
|
||||
const section = document.currentScript?.closest("[data-section-id='inspector-thread-events']");
|
||||
section?.querySelectorAll("[data-thread-event-form]").forEach(form => {
|
||||
const plotEventType = form.querySelector("[data-plot-event-type]");
|
||||
const sourcePlotLine = form.querySelector("[data-thread-event-source-plot-line]");
|
||||
const sourceThread = form.querySelector("[data-thread-event-source-thread]");
|
||||
const branchSection = form.querySelector("[data-plot-event-target='branch']");
|
||||
const mergeSection = form.querySelector("[data-plot-event-target='merge']");
|
||||
const splitSection = form.querySelector("[data-plot-event-target='split']");
|
||||
const branchTarget = form.querySelector("[data-thread-event-target-single]");
|
||||
const mergeTarget = form.querySelector("[data-thread-event-target-merge]");
|
||||
const splitTargets = form.querySelector("[data-thread-event-target-split]");
|
||||
|
||||
const eventTypeName = () => plotEventType?.selectedOptions?.[0]?.textContent?.trim().toLowerCase() || "";
|
||||
const sourcePlotLineId = () => sourceThread?.selectedOptions?.[0]?.dataset.plotLineId || sourcePlotLine?.value || "";
|
||||
|
||||
const syncTargetOptions = () => {
|
||||
const sourceId = sourcePlotLineId();
|
||||
form.querySelectorAll("[data-target-option]").forEach(option => {
|
||||
const isSource = sourceId && option.value === sourceId;
|
||||
option.disabled = Boolean(isSource);
|
||||
option.hidden = Boolean(isSource);
|
||||
if (isSource) option.selected = false;
|
||||
});
|
||||
};
|
||||
|
||||
const sync = () => {
|
||||
const type = eventTypeName();
|
||||
branchSection.hidden = type !== "branch";
|
||||
mergeSection.hidden = type !== "merge";
|
||||
splitSection.hidden = type !== "split";
|
||||
branchTarget.disabled = type !== "branch";
|
||||
mergeTarget.disabled = type !== "merge";
|
||||
splitTargets.disabled = type !== "split";
|
||||
branchTarget.name = type === "branch" ? "TargetPlotLineID" : "";
|
||||
mergeTarget.name = type === "merge" ? "TargetPlotLineID" : "";
|
||||
syncTargetOptions();
|
||||
};
|
||||
|
||||
plotEventType?.addEventListener("change", sync);
|
||||
sourcePlotLine?.addEventListener("change", sync);
|
||||
sourceThread?.addEventListener("change", sync);
|
||||
sync();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<section class="inspector-section asset-events-section" data-section-title="Story Assets" data-section-id="inspector-asset-events">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user