Review all Save, Add, Update and Delete actions used by the Scene Inspector.
This commit is contained in:
parent
d4c886ce2f
commit
59c5630950
@ -66,6 +66,11 @@ public sealed class ScenesController(
|
|||||||
return View("Edit", createModel);
|
return View("Edit", createModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
||||||
|
{
|
||||||
|
return LocalRedirect(returnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (model.ReturnToTimeline && model.ReturnProjectID.HasValue)
|
if (model.ReturnToTimeline && model.ReturnProjectID.HasValue)
|
||||||
{
|
{
|
||||||
return RedirectToAction("Index", "Timeline", new
|
return RedirectToAction("Index", "Timeline", new
|
||||||
@ -85,6 +90,11 @@ public sealed class ScenesController(
|
|||||||
{
|
{
|
||||||
await scenes.ArchiveAsync(id);
|
await scenes.ArchiveAsync(id);
|
||||||
TempData["ArchiveMessage"] = "Scene archived. You can restore it from Archived Items.";
|
TempData["ArchiveMessage"] = "Scene archived. You can restore it from Archived Items.";
|
||||||
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
||||||
|
{
|
||||||
|
return LocalRedirect(RemoveSelectedScene(returnUrl));
|
||||||
|
}
|
||||||
|
|
||||||
return RedirectToAction("Details", "Chapters", new { id = chapterId });
|
return RedirectToAction("Details", "Chapters", new { id = chapterId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,12 +151,7 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> DeleteThreadEvent(int id, int sceneId, int? projectId, int? bookId)
|
public async Task<IActionResult> DeleteThreadEvent(int id, int sceneId, int? projectId, int? bookId)
|
||||||
{
|
{
|
||||||
await plots.DeleteThreadEventAsync(id);
|
await plots.DeleteThreadEventAsync(id);
|
||||||
if (projectId.HasValue)
|
return RedirectForScene(sceneId, projectId, bookId);
|
||||||
{
|
|
||||||
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
|
||||||
}
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -154,17 +159,7 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> AddAssetEvent(AssetEventCreateViewModel model)
|
public async Task<IActionResult> AddAssetEvent(AssetEventCreateViewModel model)
|
||||||
{
|
{
|
||||||
await assets.AddAssetEventAsync(model);
|
await assets.AddAssetEventAsync(model);
|
||||||
if (model.ReturnProjectID.HasValue)
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
{
|
|
||||||
return RedirectToAction("Index", "Timeline", new
|
|
||||||
{
|
|
||||||
projectId = model.ReturnProjectID.Value,
|
|
||||||
bookId = model.ReturnBookID,
|
|
||||||
selectedSceneId = model.SceneID
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Edit), new { id = model.SceneID });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -172,12 +167,7 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> DeleteAssetEvent(int id, int sceneId, int? projectId, int? bookId)
|
public async Task<IActionResult> DeleteAssetEvent(int id, int sceneId, int? projectId, int? bookId)
|
||||||
{
|
{
|
||||||
await assets.DeleteAssetEventAsync(id);
|
await assets.DeleteAssetEventAsync(id);
|
||||||
if (projectId.HasValue)
|
return RedirectForScene(sceneId, projectId, bookId);
|
||||||
{
|
|
||||||
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
|
||||||
}
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -185,17 +175,7 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> AddAssetCustodyEvent(AssetCustodyCreateViewModel model)
|
public async Task<IActionResult> AddAssetCustodyEvent(AssetCustodyCreateViewModel model)
|
||||||
{
|
{
|
||||||
await assets.AddCustodyEventAsync(model);
|
await assets.AddCustodyEventAsync(model);
|
||||||
if (model.ReturnProjectID.HasValue)
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
{
|
|
||||||
return RedirectToAction("Index", "Timeline", new
|
|
||||||
{
|
|
||||||
projectId = model.ReturnProjectID.Value,
|
|
||||||
bookId = model.ReturnBookID,
|
|
||||||
selectedSceneId = model.SceneID
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Edit), new { id = model.SceneID });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -219,12 +199,7 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> DeleteAssetCustodyEvent(int id, int sceneId, int? projectId, int? bookId)
|
public async Task<IActionResult> DeleteAssetCustodyEvent(int id, int sceneId, int? projectId, int? bookId)
|
||||||
{
|
{
|
||||||
await assets.DeleteCustodyEventAsync(id);
|
await assets.DeleteCustodyEventAsync(id);
|
||||||
if (projectId.HasValue)
|
return RedirectForScene(sceneId, projectId, bookId);
|
||||||
{
|
|
||||||
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
|
||||||
}
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -279,6 +254,7 @@ public sealed class ScenesController(
|
|||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> PreviewMove(SceneMoveRequestViewModel model)
|
public async Task<IActionResult> PreviewMove(SceneMoveRequestViewModel model)
|
||||||
{
|
{
|
||||||
|
model.ReturnUrl = GetLocalReturnUrl();
|
||||||
var preview = await scenes.GetMovePreviewAsync(model);
|
var preview = await scenes.GetMovePreviewAsync(model);
|
||||||
return preview is null ? NotFound() : View("MovePreview", preview);
|
return preview is null ? NotFound() : View("MovePreview", preview);
|
||||||
}
|
}
|
||||||
@ -288,6 +264,11 @@ public sealed class ScenesController(
|
|||||||
public async Task<IActionResult> ConfirmMove(SceneMoveRequestViewModel model)
|
public async Task<IActionResult> ConfirmMove(SceneMoveRequestViewModel model)
|
||||||
{
|
{
|
||||||
await scenes.MoveToChapterAsync(model);
|
await scenes.MoveToChapterAsync(model);
|
||||||
|
if (TryGetLocalReturnUrl(model.ReturnUrl, out var returnUrl))
|
||||||
|
{
|
||||||
|
return LocalRedirect(returnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,6 +456,11 @@ public sealed class ScenesController(
|
|||||||
|
|
||||||
private IActionResult RedirectForScene(int sceneId, int? projectId, int? bookId)
|
private IActionResult RedirectForScene(int sceneId, int? projectId, int? bookId)
|
||||||
{
|
{
|
||||||
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
||||||
|
{
|
||||||
|
return LocalRedirect(returnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (projectId.HasValue)
|
if (projectId.HasValue)
|
||||||
{
|
{
|
||||||
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
||||||
@ -483,6 +469,61 @@ public sealed class ScenesController(
|
|||||||
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? GetLocalReturnUrl(string? returnUrl = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(returnUrl)
|
||||||
|
&& Request.HasFormContentType
|
||||||
|
&& Request.Form.TryGetValue("ReturnUrl", out var postedReturnUrl))
|
||||||
|
{
|
||||||
|
returnUrl = postedReturnUrl.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return !string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl)
|
||||||
|
? returnUrl
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetLocalReturnUrl(out string returnUrl) =>
|
||||||
|
TryGetLocalReturnUrl(null, out returnUrl);
|
||||||
|
|
||||||
|
private bool TryGetLocalReturnUrl(string? candidate, out string returnUrl)
|
||||||
|
{
|
||||||
|
var localReturnUrl = GetLocalReturnUrl(candidate);
|
||||||
|
if (!string.IsNullOrWhiteSpace(localReturnUrl))
|
||||||
|
{
|
||||||
|
returnUrl = localReturnUrl;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnUrl = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RemoveSelectedScene(string returnUrl)
|
||||||
|
{
|
||||||
|
var hashIndex = returnUrl.IndexOf('#');
|
||||||
|
var hash = hashIndex >= 0 ? returnUrl[hashIndex..] : string.Empty;
|
||||||
|
var withoutHash = hashIndex >= 0 ? returnUrl[..hashIndex] : returnUrl;
|
||||||
|
var queryIndex = withoutHash.IndexOf('?');
|
||||||
|
if (queryIndex < 0)
|
||||||
|
{
|
||||||
|
return returnUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = withoutHash[..queryIndex];
|
||||||
|
var query = withoutHash[(queryIndex + 1)..]
|
||||||
|
.Split('&', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Where(part =>
|
||||||
|
{
|
||||||
|
var equalsIndex = part.IndexOf('=');
|
||||||
|
var key = equalsIndex >= 0 ? part[..equalsIndex] : part;
|
||||||
|
return !string.Equals(Uri.UnescapeDataString(key), "selectedSceneId", StringComparison.OrdinalIgnoreCase);
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return query.Count == 0 ? path + hash : $"{path}?{string.Join("&", query)}{hash}";
|
||||||
|
}
|
||||||
|
|
||||||
private static void CopyPostedSceneFields(SceneEditViewModel source, SceneEditViewModel target)
|
private static void CopyPostedSceneFields(SceneEditViewModel source, SceneEditViewModel target)
|
||||||
{
|
{
|
||||||
target.SceneTitle = source.SceneTitle;
|
target.SceneTitle = source.SceneTitle;
|
||||||
|
|||||||
@ -49,6 +49,11 @@ public sealed class WarningsController(IContinuityValidationService validation)
|
|||||||
public async Task<IActionResult> ValidateScene(int projectId, int? bookId, int sceneId, bool returnToTimeline = false)
|
public async Task<IActionResult> ValidateScene(int projectId, int? bookId, int sceneId, bool returnToTimeline = false)
|
||||||
{
|
{
|
||||||
await validation.ValidateSceneAsync(sceneId);
|
await validation.ValidateSceneAsync(sceneId);
|
||||||
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
||||||
|
{
|
||||||
|
return LocalRedirect(returnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (returnToTimeline)
|
if (returnToTimeline)
|
||||||
{
|
{
|
||||||
return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId });
|
return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId });
|
||||||
@ -137,6 +142,11 @@ public sealed class WarningsController(IContinuityValidationService validation)
|
|||||||
|
|
||||||
private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline)
|
private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline)
|
||||||
{
|
{
|
||||||
|
if (TryGetLocalReturnUrl(out var returnUrl))
|
||||||
|
{
|
||||||
|
return LocalRedirect(returnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (returnToTimeline && sceneId.HasValue)
|
if (returnToTimeline && sceneId.HasValue)
|
||||||
{
|
{
|
||||||
return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId.Value });
|
return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId.Value });
|
||||||
@ -145,6 +155,20 @@ public sealed class WarningsController(IContinuityValidationService validation)
|
|||||||
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId });
|
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
private IActionResult RedirectToDashboard(WarningDashboardReturnFilter filter)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(Index), new
|
return RedirectToAction(nameof(Index), new
|
||||||
|
|||||||
@ -1917,6 +1917,7 @@ public sealed class SceneMoveRequestViewModel
|
|||||||
public bool RenumberScenes { get; set; }
|
public bool RenumberScenes { get; set; }
|
||||||
public int? ReturnProjectID { get; set; }
|
public int? ReturnProjectID { get; set; }
|
||||||
public int? ReturnBookID { get; set; }
|
public int? ReturnBookID { get; set; }
|
||||||
|
public string? ReturnUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SceneMovePreviewViewModel
|
public sealed class SceneMovePreviewViewModel
|
||||||
|
|||||||
@ -55,13 +55,21 @@
|
|||||||
<input type="hidden" name="RenumberScenes" value="@Model.Request.RenumberScenes.ToString().ToLowerInvariant()" />
|
<input type="hidden" name="RenumberScenes" value="@Model.Request.RenumberScenes.ToString().ToLowerInvariant()" />
|
||||||
<input type="hidden" name="ReturnProjectID" value="@Model.Request.ReturnProjectID" />
|
<input type="hidden" name="ReturnProjectID" value="@Model.Request.ReturnProjectID" />
|
||||||
<input type="hidden" name="ReturnBookID" value="@Model.Request.ReturnBookID" />
|
<input type="hidden" name="ReturnBookID" value="@Model.Request.ReturnBookID" />
|
||||||
|
<input type="hidden" name="ReturnUrl" value="@Model.Request.ReturnUrl" />
|
||||||
<button class="btn btn-primary" type="submit">Confirm move</button>
|
<button class="btn btn-primary" type="submit">Confirm move</button>
|
||||||
</form>
|
</form>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.Request.ReturnUrl))
|
||||||
|
{
|
||||||
|
<a class="btn btn-outline-secondary" href="@Model.Request.ReturnUrl">Cancel</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<a class="btn btn-outline-secondary"
|
<a class="btn btn-outline-secondary"
|
||||||
asp-controller="Timeline"
|
asp-controller="Timeline"
|
||||||
asp-action="Index"
|
asp-action="Index"
|
||||||
asp-route-projectId="@Model.Request.ReturnProjectID"
|
asp-route-projectId="@Model.Request.ReturnProjectID"
|
||||||
asp-route-bookId="@Model.Request.ReturnBookID"
|
asp-route-bookId="@Model.Request.ReturnBookID"
|
||||||
asp-route-selectedSceneId="@Model.Request.SceneID">Cancel</a>
|
asp-route-selectedSceneId="@Model.Request.SceneID">Cancel</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -63,6 +63,34 @@
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const inspectorRoots = document.querySelectorAll(".scene-inspector-root");
|
||||||
|
if (!inspectorRoots.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentReturnUrl = () => `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||||
|
|
||||||
|
inspectorRoots.forEach((root) => {
|
||||||
|
root.addEventListener("submit", (event) => {
|
||||||
|
const form = event.target;
|
||||||
|
if (!(form instanceof HTMLFormElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnUrlInput = form.querySelector("input[name='ReturnUrl']");
|
||||||
|
if (!returnUrlInput) {
|
||||||
|
returnUrlInput = document.createElement("input");
|
||||||
|
returnUrlInput.type = "hidden";
|
||||||
|
returnUrlInput.name = "ReturnUrl";
|
||||||
|
form.appendChild(returnUrlInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnUrlInput.value = currentReturnUrl();
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const modalElement = document.getElementById("plotlineConfirmModal");
|
const modalElement = document.getElementById("plotlineConfirmModal");
|
||||||
const modal = modalElement && window.bootstrap ? new bootstrap.Modal(modalElement) : null;
|
const modal = modalElement && window.bootstrap ? new bootstrap.Modal(modalElement) : null;
|
||||||
|
|||||||
2
PlotLine/wwwroot/js/site.min.js
vendored
2
PlotLine/wwwroot/js/site.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user