Phase 16O - Review and Accept Word Companion Location Suggestions
This commit is contained in:
parent
6867dfad77
commit
524515e9b6
@ -307,6 +307,38 @@ public sealed class ScenesController(
|
||||
return RedirectForScene(sceneId, projectId, bookId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> AcceptLocationSuggestion(int id, int sceneId, int? projectId, int? bookId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await locations.AcceptSceneLocationSuggestionAsync(id);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
TempData["SceneLocationSuggestionError"] = ex.Message;
|
||||
}
|
||||
|
||||
return RedirectForScene(sceneId, projectId, bookId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RejectLocationSuggestion(int id, int sceneId, int? projectId, int? bookId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await locations.RejectSceneLocationSuggestionAsync(id);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
TempData["SceneLocationSuggestionError"] = ex.Message;
|
||||
}
|
||||
|
||||
return RedirectForScene(sceneId, projectId, bookId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> AddSceneDependency(SceneDependencyCreateViewModel model)
|
||||
|
||||
@ -261,6 +261,10 @@ public interface ILocationRepository
|
||||
Task<IReadOnlyList<SceneAssetLocation>> ListSceneAssetLocationsAsync(int sceneId);
|
||||
Task<int> SaveSceneAssetLocationAsync(SceneAssetLocation sceneAssetLocation, bool updateCurrentLocation);
|
||||
Task DeleteSceneAssetLocationAsync(int sceneAssetLocationId);
|
||||
Task<IReadOnlyList<SceneLocationSuggestion>> ListSceneLocationSuggestionsAsync(int sceneId, int userId);
|
||||
Task<SceneLocationSuggestionReviewResult?> AcceptSceneLocationSuggestionAsync(int suggestionId, int userId);
|
||||
Task<SceneLocationSuggestionReviewResult?> RejectSceneLocationSuggestionAsync(int suggestionId, int userId);
|
||||
Task<IReadOnlyList<SceneLocationSuggestionCount>> CountSceneLocationSuggestionsByChapterAsync(int chapterId, int userId);
|
||||
Task<IReadOnlyList<Scene>> ListScenesByLocationAsync(int locationId);
|
||||
Task<IReadOnlyList<SceneCharacter>> ListCharactersByLocationAsync(int locationId);
|
||||
Task<IReadOnlyList<SceneAssetLocation>> ListAssetsByLocationAsync(int locationId);
|
||||
@ -625,6 +629,44 @@ public sealed class LocationRepository(ISqlConnectionFactory connectionFactory)
|
||||
await connection.ExecuteAsync("dbo.SceneAssetLocation_Delete", new { SceneAssetLocationID = sceneAssetLocationId }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<SceneLocationSuggestion>> ListSceneLocationSuggestionsAsync(int sceneId, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<SceneLocationSuggestion>(
|
||||
"dbo.SceneLocationSuggestions_ListByScene",
|
||||
new { SceneID = sceneId, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
}
|
||||
|
||||
public async Task<SceneLocationSuggestionReviewResult?> AcceptSceneLocationSuggestionAsync(int suggestionId, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleOrDefaultAsync<SceneLocationSuggestionReviewResult>(
|
||||
"dbo.SceneLocationSuggestion_Accept",
|
||||
new { SceneLocationSuggestionID = suggestionId, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<SceneLocationSuggestionReviewResult?> RejectSceneLocationSuggestionAsync(int suggestionId, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleOrDefaultAsync<SceneLocationSuggestionReviewResult>(
|
||||
"dbo.SceneLocationSuggestion_Reject",
|
||||
new { SceneLocationSuggestionID = suggestionId, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<SceneLocationSuggestionCount>> CountSceneLocationSuggestionsByChapterAsync(int chapterId, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<SceneLocationSuggestionCount>(
|
||||
"dbo.SceneLocationSuggestions_CountByChapter",
|
||||
new { ChapterID = chapterId, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Scene>> ListScenesByLocationAsync(int locationId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
|
||||
@ -403,6 +403,7 @@ public sealed class Scene
|
||||
public int DependencyCount { get; set; }
|
||||
public int PendingCharacterSuggestionCount { get; set; }
|
||||
public int PendingAssetSuggestionCount { get; set; }
|
||||
public int PendingLocationSuggestionCount { get; set; }
|
||||
|
||||
public string TimeLabel
|
||||
{
|
||||
@ -1037,6 +1038,35 @@ public sealed class SceneAssetSuggestionReviewResult
|
||||
public bool SceneAssetCreated { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SceneLocationSuggestion
|
||||
{
|
||||
public int SceneLocationSuggestionID { get; set; }
|
||||
public int SceneID { get; set; }
|
||||
public int LocationID { get; set; }
|
||||
public string LocationName { get; set; } = string.Empty;
|
||||
public string LocationPath { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public string Confidence { get; set; } = string.Empty;
|
||||
public string? Reason { get; set; }
|
||||
public DateTime DetectedUtc { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SceneLocationSuggestionCount
|
||||
{
|
||||
public int SceneID { get; set; }
|
||||
public int PendingSuggestionCount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SceneLocationSuggestionReviewResult
|
||||
{
|
||||
public int SceneLocationSuggestionID { get; set; }
|
||||
public int SceneID { get; set; }
|
||||
public int LocationID { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public bool SceneLocationCreated { get; set; }
|
||||
public bool PrimaryLocationUpdated { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CharacterAttributeType
|
||||
{
|
||||
public int CharacterAttributeTypeID { get; set; }
|
||||
|
||||
@ -195,6 +195,8 @@ public interface ILocationService
|
||||
Task ArchiveRelationshipAsync(int locationRelationshipId);
|
||||
Task<int> SaveSceneAssetLocationAsync(SceneAssetLocationCreateViewModel model);
|
||||
Task DeleteSceneAssetLocationAsync(int sceneAssetLocationId);
|
||||
Task AcceptSceneLocationSuggestionAsync(int suggestionId);
|
||||
Task RejectSceneLocationSuggestionAsync(int suggestionId);
|
||||
}
|
||||
|
||||
public interface IFloorPlanService
|
||||
@ -1293,6 +1295,7 @@ public sealed class ChapterService(
|
||||
ISceneRepository scenes,
|
||||
ICharacterRepository characters,
|
||||
IAssetRepository assets,
|
||||
ILocationRepository locations,
|
||||
ILookupRepository lookups,
|
||||
ISubscriptionService subscriptions,
|
||||
IProjectActivityService activity,
|
||||
@ -1320,10 +1323,13 @@ public sealed class ChapterService(
|
||||
var countsByScene = suggestionCounts.ToDictionary(x => x.SceneID, x => x.PendingSuggestionCount);
|
||||
var assetSuggestionCounts = await assets.CountSceneAssetSuggestionsByChapterAsync(chapterId, userId);
|
||||
var assetCountsByScene = assetSuggestionCounts.ToDictionary(x => x.SceneID, x => x.PendingSuggestionCount);
|
||||
var locationSuggestionCounts = await locations.CountSceneLocationSuggestionsByChapterAsync(chapterId, userId);
|
||||
var locationCountsByScene = locationSuggestionCounts.ToDictionary(x => x.SceneID, x => x.PendingSuggestionCount);
|
||||
foreach (var scene in sceneRows)
|
||||
{
|
||||
scene.PendingCharacterSuggestionCount = countsByScene.GetValueOrDefault(scene.SceneID);
|
||||
scene.PendingAssetSuggestionCount = assetCountsByScene.GetValueOrDefault(scene.SceneID);
|
||||
scene.PendingLocationSuggestionCount = locationCountsByScene.GetValueOrDefault(scene.SceneID);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1334,7 +1340,8 @@ public sealed class ChapterService(
|
||||
Chapter = chapter,
|
||||
Scenes = sceneRows,
|
||||
PendingCharacterSuggestionCount = sceneRows.Sum(x => x.PendingCharacterSuggestionCount),
|
||||
PendingAssetSuggestionCount = sceneRows.Sum(x => x.PendingAssetSuggestionCount)
|
||||
PendingAssetSuggestionCount = sceneRows.Sum(x => x.PendingAssetSuggestionCount),
|
||||
PendingLocationSuggestionCount = sceneRows.Sum(x => x.PendingLocationSuggestionCount)
|
||||
};
|
||||
}
|
||||
|
||||
@ -1659,6 +1666,7 @@ public sealed class SceneService(
|
||||
model.SceneCharacters = model.SceneID == 0 ? [] : await characters.ListSceneCharactersAsync(model.SceneID);
|
||||
model.CharacterSuggestions = model.SceneID == 0 || currentUser.UserId is not int userId ? [] : await characters.ListSceneCharacterSuggestionsAsync(model.SceneID, userId);
|
||||
model.AssetSuggestions = model.SceneID == 0 || currentUser.UserId is not int assetUserId ? [] : await assets.ListSceneAssetSuggestionsAsync(model.SceneID, assetUserId);
|
||||
model.LocationSuggestions = model.SceneID == 0 || currentUser.UserId is not int locationUserId ? [] : await locations.ListSceneLocationSuggestionsAsync(model.SceneID, locationUserId);
|
||||
model.CharacterAttributeEvents = model.SceneID == 0 ? [] : await characters.ListAttributeEventsBySceneAsync(model.SceneID);
|
||||
model.Warnings = model.SceneID == 0 ? [] : await warnings.ListBySceneAsync(model.SceneID);
|
||||
model.AgeContinuityWarnings = await BuildAgeContinuityWarningsAsync(model);
|
||||
@ -7772,7 +7780,11 @@ public sealed class AssetService(
|
||||
=> items.Select(x => new SelectListItem(new string(' ', x.Depth * 2) + x.LocationName, x.LocationID.ToString())).ToList();
|
||||
}
|
||||
|
||||
public sealed class LocationService(IProjectRepository projects, ILocationRepository locations, IProjectActivityService activity) : ILocationService
|
||||
public sealed class LocationService(
|
||||
IProjectRepository projects,
|
||||
ILocationRepository locations,
|
||||
IProjectActivityService activity,
|
||||
ICurrentUserService currentUser) : ILocationService
|
||||
{
|
||||
public async Task<LocationListViewModel?> GetLocationsAsync(int projectId)
|
||||
{
|
||||
@ -7924,6 +7936,34 @@ public sealed class LocationService(IProjectRepository projects, ILocationReposi
|
||||
|
||||
public Task DeleteSceneAssetLocationAsync(int sceneAssetLocationId) => locations.DeleteSceneAssetLocationAsync(sceneAssetLocationId);
|
||||
|
||||
public async Task AcceptSceneLocationSuggestionAsync(int suggestionId)
|
||||
{
|
||||
if (currentUser.UserId is not int userId)
|
||||
{
|
||||
throw new InvalidOperationException("Sign in to accept location suggestions.");
|
||||
}
|
||||
|
||||
var result = await locations.AcceptSceneLocationSuggestionAsync(suggestionId, userId);
|
||||
if (result is null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to accept this location suggestion.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RejectSceneLocationSuggestionAsync(int suggestionId)
|
||||
{
|
||||
if (currentUser.UserId is not int userId)
|
||||
{
|
||||
throw new InvalidOperationException("Sign in to reject location suggestions.");
|
||||
}
|
||||
|
||||
var result = await locations.RejectSceneLocationSuggestionAsync(suggestionId, userId);
|
||||
if (result is null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to reject this location suggestion.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<LocationEditViewModel> PopulateLocationListsAsync(LocationEditViewModel model)
|
||||
{
|
||||
var lookups = await locations.GetLookupsAsync();
|
||||
|
||||
@ -0,0 +1,193 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneLocationSuggestions_ListByScene
|
||||
@SceneID int,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT suggestion.SceneLocationSuggestionID,
|
||||
suggestion.SceneID,
|
||||
suggestion.LocationID,
|
||||
location.LocationName,
|
||||
location.LocationPath,
|
||||
suggestion.Status,
|
||||
suggestion.Confidence,
|
||||
suggestion.Reason,
|
||||
suggestion.DetectedUtc
|
||||
FROM dbo.SceneLocationSuggestions suggestion
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = suggestion.SceneID
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||
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
|
||||
INNER JOIN dbo.LocationPaths location ON location.LocationID = suggestion.LocationID
|
||||
WHERE suggestion.SceneID = @SceneID
|
||||
AND suggestion.Status = N'Pending'
|
||||
AND s.IsArchived = 0
|
||||
AND c.IsArchived = 0
|
||||
AND b.IsArchived = 0
|
||||
AND p.IsArchived = 0
|
||||
AND location.ProjectID = p.ProjectID
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1
|
||||
ORDER BY
|
||||
CASE suggestion.Confidence WHEN N'High' THEN 0 WHEN N'Medium' THEN 1 ELSE 2 END,
|
||||
location.LocationPath;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneLocationSuggestion_Accept
|
||||
@SceneLocationSuggestionID int,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @SceneID int;
|
||||
DECLARE @LocationID int;
|
||||
DECLARE @SceneLocationCreated bit = 0;
|
||||
DECLARE @PrimaryLocationUpdated bit = 0;
|
||||
|
||||
SELECT @SceneID = suggestion.SceneID,
|
||||
@LocationID = suggestion.LocationID
|
||||
FROM dbo.SceneLocationSuggestions suggestion
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = suggestion.SceneID
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||
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
|
||||
INNER JOIN dbo.Locations location ON location.LocationID = suggestion.LocationID AND location.ProjectID = p.ProjectID
|
||||
WHERE suggestion.SceneLocationSuggestionID = @SceneLocationSuggestionID
|
||||
AND suggestion.Status = N'Pending'
|
||||
AND s.IsArchived = 0
|
||||
AND c.IsArchived = 0
|
||||
AND b.IsArchived = 0
|
||||
AND p.IsArchived = 0
|
||||
AND location.IsArchived = 0
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1;
|
||||
|
||||
IF @SceneID IS NULL OR @LocationID IS NULL
|
||||
RETURN;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM dbo.WordCompanionSceneLocations WHERE SceneID = @SceneID AND LocationID = @LocationID)
|
||||
BEGIN
|
||||
INSERT dbo.WordCompanionSceneLocations (SceneID, LocationID)
|
||||
VALUES (@SceneID, @LocationID);
|
||||
|
||||
SET @SceneLocationCreated = 1;
|
||||
END;
|
||||
|
||||
UPDATE dbo.Scenes
|
||||
SET PrimaryLocationID = @LocationID,
|
||||
UpdatedDate = SYSUTCDATETIME()
|
||||
WHERE SceneID = @SceneID
|
||||
AND PrimaryLocationID IS NULL;
|
||||
|
||||
IF @@ROWCOUNT > 0
|
||||
SET @PrimaryLocationUpdated = 1;
|
||||
|
||||
UPDATE dbo.SceneLocationSuggestions
|
||||
SET Status = N'Accepted',
|
||||
ReviewedByUserID = @UserID,
|
||||
ReviewedUtc = SYSUTCDATETIME()
|
||||
WHERE SceneLocationSuggestionID = @SceneLocationSuggestionID
|
||||
AND Status = N'Pending';
|
||||
|
||||
SELECT @SceneLocationSuggestionID AS SceneLocationSuggestionID,
|
||||
@SceneID AS SceneID,
|
||||
@LocationID AS LocationID,
|
||||
N'Accepted' AS Status,
|
||||
@SceneLocationCreated AS SceneLocationCreated,
|
||||
@PrimaryLocationUpdated AS PrimaryLocationUpdated;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneLocationSuggestion_Reject
|
||||
@SceneLocationSuggestionID int,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @SceneID int;
|
||||
DECLARE @LocationID int;
|
||||
|
||||
SELECT @SceneID = suggestion.SceneID,
|
||||
@LocationID = suggestion.LocationID
|
||||
FROM dbo.SceneLocationSuggestions suggestion
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = suggestion.SceneID
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||
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
|
||||
INNER JOIN dbo.Locations location ON location.LocationID = suggestion.LocationID AND location.ProjectID = p.ProjectID
|
||||
WHERE suggestion.SceneLocationSuggestionID = @SceneLocationSuggestionID
|
||||
AND suggestion.Status = N'Pending'
|
||||
AND s.IsArchived = 0
|
||||
AND c.IsArchived = 0
|
||||
AND b.IsArchived = 0
|
||||
AND p.IsArchived = 0
|
||||
AND location.IsArchived = 0
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1;
|
||||
|
||||
IF @SceneID IS NULL OR @LocationID IS NULL
|
||||
RETURN;
|
||||
|
||||
UPDATE dbo.SceneLocationSuggestions
|
||||
SET Status = N'Rejected',
|
||||
ReviewedByUserID = @UserID,
|
||||
ReviewedUtc = SYSUTCDATETIME()
|
||||
WHERE SceneLocationSuggestionID = @SceneLocationSuggestionID
|
||||
AND Status = N'Pending';
|
||||
|
||||
SELECT @SceneLocationSuggestionID AS SceneLocationSuggestionID,
|
||||
@SceneID AS SceneID,
|
||||
@LocationID AS LocationID,
|
||||
N'Rejected' AS Status,
|
||||
CAST(0 AS bit) AS SceneLocationCreated,
|
||||
CAST(0 AS bit) AS PrimaryLocationUpdated;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneLocationSuggestions_CountByChapter
|
||||
@ChapterID int,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
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 c.ChapterID = @ChapterID
|
||||
AND c.IsArchived = 0
|
||||
AND b.IsArchived = 0
|
||||
AND p.IsArchived = 0
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1
|
||||
)
|
||||
RETURN;
|
||||
|
||||
SELECT s.SceneID,
|
||||
COUNT(suggestion.SceneLocationSuggestionID) AS PendingSuggestionCount
|
||||
FROM dbo.Scenes s
|
||||
INNER JOIN dbo.SceneLocationSuggestions suggestion ON suggestion.SceneID = s.SceneID
|
||||
INNER JOIN dbo.Locations location ON location.LocationID = suggestion.LocationID
|
||||
WHERE s.ChapterID = @ChapterID
|
||||
AND s.IsArchived = 0
|
||||
AND location.IsArchived = 0
|
||||
AND suggestion.Status = N'Pending'
|
||||
GROUP BY s.SceneID;
|
||||
END;
|
||||
GO
|
||||
@ -268,6 +268,7 @@ public sealed class ChapterDetailViewModel
|
||||
public IReadOnlyList<Scene> Scenes { get; set; } = [];
|
||||
public int PendingCharacterSuggestionCount { get; set; }
|
||||
public int PendingAssetSuggestionCount { get; set; }
|
||||
public int PendingLocationSuggestionCount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SceneEditViewModel
|
||||
@ -342,6 +343,7 @@ public sealed class SceneEditViewModel
|
||||
public IReadOnlyList<SceneCharacter> SceneCharacters { get; set; } = [];
|
||||
public IReadOnlyList<SceneCharacterSuggestion> CharacterSuggestions { get; set; } = [];
|
||||
public IReadOnlyList<SceneAssetSuggestion> AssetSuggestions { get; set; } = [];
|
||||
public IReadOnlyList<SceneLocationSuggestion> LocationSuggestions { get; set; } = [];
|
||||
public IReadOnlyList<CharacterAttributeEvent> CharacterAttributeEvents { get; set; } = [];
|
||||
public IReadOnlyList<CharacterKnowledgeItem> CharacterKnowledge { get; set; } = [];
|
||||
public IReadOnlyList<RelationshipEvent> RelationshipEvents { get; set; } = [];
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.PendingCharacterSuggestionCount > 0 || Model.PendingAssetSuggestionCount > 0)
|
||||
@if (Model.PendingCharacterSuggestionCount > 0 || Model.PendingAssetSuggestionCount > 0 || Model.PendingLocationSuggestionCount > 0)
|
||||
{
|
||||
<section class="list-section word-companion-suggestion-summary">
|
||||
<h2>Word Companion Suggestions</h2>
|
||||
@ -63,6 +63,10 @@
|
||||
{
|
||||
<p class="muted">@Model.PendingAssetSuggestionCount asset suggestion@(Model.PendingAssetSuggestionCount == 1 ? "" : "s") awaiting review in this chapter.</p>
|
||||
}
|
||||
@if (Model.PendingLocationSuggestionCount > 0)
|
||||
{
|
||||
<p class="muted">@Model.PendingLocationSuggestionCount location suggestion@(Model.PendingLocationSuggestionCount == 1 ? "" : "s") awaiting review in this chapter.</p>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
|
||||
@ -95,7 +99,7 @@
|
||||
<td class="narrow">@scene.SceneNumber</td>
|
||||
<td>
|
||||
<a asp-controller="Scenes" asp-action="Edit" asp-route-id="@scene.SceneID">@scene.SceneTitle</a>
|
||||
@if (scene.PendingCharacterSuggestionCount > 0 || scene.PendingAssetSuggestionCount > 0)
|
||||
@if (scene.PendingCharacterSuggestionCount > 0 || scene.PendingAssetSuggestionCount > 0 || scene.PendingLocationSuggestionCount > 0)
|
||||
{
|
||||
var suggestionParts = new List<string>();
|
||||
if (scene.PendingCharacterSuggestionCount > 0)
|
||||
@ -106,6 +110,10 @@
|
||||
{
|
||||
suggestionParts.Add($"{scene.PendingAssetSuggestionCount} Asset{(scene.PendingAssetSuggestionCount == 1 ? "" : "s")}");
|
||||
}
|
||||
if (scene.PendingLocationSuggestionCount > 0)
|
||||
{
|
||||
suggestionParts.Add($"{scene.PendingLocationSuggestionCount} Location{(scene.PendingLocationSuggestionCount == 1 ? "" : "s")}");
|
||||
}
|
||||
<span class="status-pill word-companion-suggestion-pill">@string.Join(" • ", suggestionParts)</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
}
|
||||
</section>
|
||||
|
||||
@if (Model.CharacterSuggestions.Any() || Model.AssetSuggestions.Any())
|
||||
@if (Model.CharacterSuggestions.Any() || Model.AssetSuggestions.Any() || Model.LocationSuggestions.Any())
|
||||
{
|
||||
<section class="inspector-section word-companion-suggestion-section" data-section-title="Word Companion Suggestions" data-section-id="inspector-word-companion-suggestions">
|
||||
<h3>Word Companion Suggestions</h3>
|
||||
@ -81,6 +81,10 @@
|
||||
{
|
||||
<p class="text-danger form-helper-message">@assetSuggestionError</p>
|
||||
}
|
||||
@if (TempData["SceneLocationSuggestionError"] is string locationSuggestionError)
|
||||
{
|
||||
<p class="text-danger form-helper-message">@locationSuggestionError</p>
|
||||
}
|
||||
<div class="word-companion-suggestion-panel">
|
||||
<div class="word-companion-suggestion-list">
|
||||
@if (Model.CharacterSuggestions.Any())
|
||||
@ -151,6 +155,40 @@
|
||||
</article>
|
||||
}
|
||||
}
|
||||
@if (Model.LocationSuggestions.Any())
|
||||
{
|
||||
<h4 class="inspector-subheading">Locations</h4>
|
||||
@foreach (var suggestion in Model.LocationSuggestions)
|
||||
{
|
||||
<article class="word-companion-suggestion-item">
|
||||
<div>
|
||||
<strong>@(!string.IsNullOrWhiteSpace(suggestion.LocationPath) ? suggestion.LocationPath : suggestion.LocationName)</strong>
|
||||
<span class="muted">@suggestion.Confidence confidence</span>
|
||||
@if (!string.IsNullOrWhiteSpace(suggestion.Reason))
|
||||
{
|
||||
<span class="muted">@suggestion.Reason</span>
|
||||
}
|
||||
<span class="muted">Detected @suggestion.DetectedUtc.ToLocalTime().ToString("d MMM yyyy HH:mm")</span>
|
||||
</div>
|
||||
<div class="character-appearance-actions">
|
||||
<form asp-controller="Scenes" asp-action="AcceptLocationSuggestion" method="post">
|
||||
<input type="hidden" name="id" value="@suggestion.SceneLocationSuggestionID" />
|
||||
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
|
||||
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||
<button class="btn btn-outline-primary btn-sm" type="submit">Accept</button>
|
||||
</form>
|
||||
<form asp-controller="Scenes" asp-action="RejectLocationSuggestion" method="post">
|
||||
<input type="hidden" name="id" value="@suggestion.SceneLocationSuggestionID" />
|
||||
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
|
||||
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Reject</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -160,7 +198,7 @@
|
||||
<input class="form-control form-control-sm inspector-search" type="search" placeholder="Find in scene..." data-inspector-search />
|
||||
<div class="inspector-nav-links">
|
||||
<a href="#inspector-overview">Overview</a>
|
||||
@if (Model.CharacterSuggestions.Any() || Model.AssetSuggestions.Any())
|
||||
@if (Model.CharacterSuggestions.Any() || Model.AssetSuggestions.Any() || Model.LocationSuggestions.Any())
|
||||
{
|
||||
<a href="#inspector-word-companion-suggestions">Word Companion</a>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user