323 lines
11 KiB
C#
323 lines
11 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|
using PlotLine.Models;
|
|
using PlotLine.Services;
|
|
using PlotLine.ViewModels;
|
|
|
|
namespace PlotLine.Controllers;
|
|
|
|
[Authorize]
|
|
public sealed class WarningsController(
|
|
IContinuityValidationService validation,
|
|
ISceneService scenes,
|
|
ICompositeViewEngine viewEngine) : Controller
|
|
{
|
|
[HttpGet]
|
|
public async Task<IActionResult> SceneContinuityEditor(int sceneId)
|
|
{
|
|
var model = await BuildSceneContinuityEditorModelAsync(sceneId);
|
|
return model is null
|
|
? NotFound()
|
|
: PartialView("~/Views/Shared/SceneInspectorV2/_SceneContinuityEditor.cshtml", model);
|
|
}
|
|
|
|
public async Task<IActionResult> Index(
|
|
int projectId,
|
|
int? bookId,
|
|
int? chapterId,
|
|
int? sceneId,
|
|
int? warningTypeId,
|
|
int? warningSeverityId,
|
|
string? entityType,
|
|
string? category,
|
|
bool includeDismissed = false,
|
|
bool includeIntentional = false,
|
|
bool showAcknowledgedWarnings = false)
|
|
{
|
|
var model = await validation.GetDashboardAsync(projectId, bookId, chapterId, sceneId, warningTypeId, warningSeverityId, entityType, category, includeDismissed, includeIntentional, showAcknowledgedWarnings);
|
|
return model is null ? NotFound() : View(model);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ValidateProject(int projectId, int? bookId)
|
|
{
|
|
var summary = await validation.ValidateProjectAsync(projectId);
|
|
var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary);
|
|
return model is null ? NotFound() : View("Index", model);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ValidateBook(int projectId, int bookId)
|
|
{
|
|
var summary = await validation.ValidateBookAsync(bookId);
|
|
var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary);
|
|
return model is null ? NotFound() : View("Index", model);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ValidateScene(int projectId, int? bookId, int sceneId, bool returnToTimeline = false)
|
|
{
|
|
await validation.ValidateSceneAsync(sceneId);
|
|
if (IsSceneInspectorAjaxRequest())
|
|
{
|
|
return await SceneContinuityRefreshJsonAsync(sceneId);
|
|
}
|
|
|
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
|
{
|
|
return LocalRedirect(returnUrl);
|
|
}
|
|
|
|
if (returnToTimeline)
|
|
{
|
|
return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId });
|
|
}
|
|
|
|
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Dismiss(int id, int projectId, int? bookId, int? sceneId, bool returnToTimeline = false, int warningId = 0)
|
|
{
|
|
var continuityWarningId = warningId > 0 ? warningId : id;
|
|
await validation.DismissAsync(continuityWarningId);
|
|
if (sceneId.HasValue && IsSceneInspectorAjaxRequest())
|
|
{
|
|
return await SceneContinuityRefreshJsonAsync(sceneId.Value);
|
|
}
|
|
|
|
return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> MarkIntentional(int id, int projectId, int? bookId, int? sceneId, bool returnToTimeline = false, int warningId = 0)
|
|
{
|
|
var continuityWarningId = warningId > 0 ? warningId : id;
|
|
await validation.MarkIntentionalAsync(continuityWarningId);
|
|
if (sceneId.HasValue && IsSceneInspectorAjaxRequest())
|
|
{
|
|
return await SceneContinuityRefreshJsonAsync(sceneId.Value);
|
|
}
|
|
|
|
return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Restore(int id, int projectId, int? bookId, int? sceneId, int warningId = 0)
|
|
{
|
|
var continuityWarningId = warningId > 0 ? warningId : id;
|
|
await validation.RestoreAsync(continuityWarningId);
|
|
if (sceneId.HasValue && IsSceneInspectorAjaxRequest())
|
|
{
|
|
return await SceneContinuityRefreshJsonAsync(sceneId.Value);
|
|
}
|
|
|
|
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId, includeDismissed = true, includeIntentional = true });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> AcknowledgeGeneratedWarning(
|
|
string warningType,
|
|
int? sceneId,
|
|
int? characterId,
|
|
int? assetId,
|
|
string? notes,
|
|
[Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter)
|
|
{
|
|
if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType))
|
|
{
|
|
return BadRequest("Warning identity is required.");
|
|
}
|
|
|
|
await validation.AcknowledgeGeneratedWarningAsync(new ContinuityWarningAcknowledgement
|
|
{
|
|
ProjectID = returnFilter.ProjectID,
|
|
WarningType = warningType,
|
|
SceneID = sceneId,
|
|
CharacterID = characterId,
|
|
AssetID = assetId,
|
|
Notes = notes
|
|
});
|
|
|
|
return RedirectToDashboard(returnFilter);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> RestoreGeneratedWarning(
|
|
string warningType,
|
|
int? sceneId,
|
|
int? characterId,
|
|
int? assetId,
|
|
[Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter)
|
|
{
|
|
if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType))
|
|
{
|
|
return BadRequest("Warning identity is required.");
|
|
}
|
|
|
|
await validation.RestoreGeneratedWarningAsync(new ContinuityWarningAcknowledgement
|
|
{
|
|
ProjectID = returnFilter.ProjectID,
|
|
WarningType = warningType,
|
|
SceneID = sceneId,
|
|
CharacterID = characterId,
|
|
AssetID = assetId
|
|
});
|
|
|
|
return RedirectToDashboard(returnFilter);
|
|
}
|
|
|
|
private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline)
|
|
{
|
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
|
{
|
|
return LocalRedirect(returnUrl);
|
|
}
|
|
|
|
if (returnToTimeline && sceneId.HasValue)
|
|
{
|
|
return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId.Value });
|
|
}
|
|
|
|
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId });
|
|
}
|
|
|
|
private bool IsSceneInspectorAjaxRequest() =>
|
|
string.Equals(Request.Headers["X-Requested-With"].ToString(), "XMLHttpRequest", StringComparison.OrdinalIgnoreCase)
|
|
|| Request.Headers.Accept.ToString().Contains("application/json", StringComparison.OrdinalIgnoreCase);
|
|
|
|
private async Task<IActionResult> SceneContinuityRefreshJsonAsync(int sceneId)
|
|
{
|
|
var scene = await scenes.GetEditAsync(sceneId);
|
|
var editorModel = scene is null ? null : await BuildSceneContinuityEditorModelAsync(scene);
|
|
if (scene is null || editorModel is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
return Json(new
|
|
{
|
|
success = true,
|
|
html = new
|
|
{
|
|
health = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneInspectorHealthStrip.cshtml", scene),
|
|
continuity = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneContinuityCard.cshtml", scene)
|
|
},
|
|
editorHtml = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneContinuityEditor.cshtml", editorModel),
|
|
scene = new
|
|
{
|
|
scene.SceneID
|
|
}
|
|
});
|
|
}
|
|
|
|
private async Task<SceneContinuityEditorViewModel?> BuildSceneContinuityEditorModelAsync(int sceneId)
|
|
{
|
|
var scene = await scenes.GetEditAsync(sceneId);
|
|
return scene is null ? null : await BuildSceneContinuityEditorModelAsync(scene);
|
|
}
|
|
|
|
private async Task<SceneContinuityEditorViewModel?> BuildSceneContinuityEditorModelAsync(SceneEditViewModel scene)
|
|
{
|
|
var projectId = scene.ReturnProjectID ?? scene.Project?.ProjectID;
|
|
if (!projectId.HasValue)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var dashboard = await validation.GetDashboardAsync(
|
|
projectId.Value,
|
|
scene.ReturnBookID ?? scene.Book?.BookID,
|
|
null,
|
|
scene.SceneID,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
includeDismissed: true,
|
|
includeIntentional: true,
|
|
showAcknowledgedWarnings: true);
|
|
|
|
return dashboard is null
|
|
? null
|
|
: new SceneContinuityEditorViewModel
|
|
{
|
|
Scene = scene,
|
|
Dashboard = dashboard
|
|
};
|
|
}
|
|
|
|
private async Task<string> RenderPartialViewToStringAsync(string viewName, object model)
|
|
{
|
|
var viewResult = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: false);
|
|
if (!viewResult.Success)
|
|
{
|
|
viewResult = viewEngine.FindView(ControllerContext, viewName, isMainPage: false);
|
|
}
|
|
|
|
if (!viewResult.Success)
|
|
{
|
|
throw new InvalidOperationException($"Partial view '{viewName}' was not found.");
|
|
}
|
|
|
|
await using var writer = new StringWriter();
|
|
var viewData = new ViewDataDictionary(ViewData)
|
|
{
|
|
Model = model
|
|
};
|
|
var viewContext = new ViewContext(
|
|
ControllerContext,
|
|
viewResult.View,
|
|
viewData,
|
|
TempData,
|
|
writer,
|
|
new HtmlHelperOptions());
|
|
|
|
await viewResult.View.RenderAsync(viewContext);
|
|
return writer.ToString();
|
|
}
|
|
|
|
private bool TryGetLocalReturnUrl(out string returnUrl)
|
|
{
|
|
if (Request.HasFormContentType
|
|
&& Request.Form.TryGetValue("ReturnUrl", out var postedReturnUrl)
|
|
&& Url.IsLocalUrl(postedReturnUrl.ToString()))
|
|
{
|
|
returnUrl = postedReturnUrl.ToString();
|
|
return true;
|
|
}
|
|
|
|
returnUrl = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
private IActionResult RedirectToDashboard(WarningDashboardReturnFilter filter)
|
|
{
|
|
return RedirectToAction(nameof(Index), new
|
|
{
|
|
projectId = filter.ProjectID,
|
|
bookId = filter.BookID,
|
|
chapterId = filter.ChapterID,
|
|
sceneId = filter.SceneID,
|
|
warningTypeId = filter.WarningTypeID,
|
|
warningSeverityId = filter.WarningSeverityID,
|
|
entityType = filter.EntityType,
|
|
category = filter.Category,
|
|
includeDismissed = filter.IncludeDismissed,
|
|
includeIntentional = filter.IncludeIntentional,
|
|
showAcknowledgedWarnings = filter.ShowAcknowledgedWarnings
|
|
});
|
|
}
|
|
}
|