using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [Authorize] public sealed class FloorPlansController(IFloorPlanService floorPlans) : Controller { public async Task Index(int projectId) { var model = await floorPlans.ListAsync(projectId); return model is null ? NotFound() : View(model); } public async Task Edit(int id) { var model = await floorPlans.GetEditorAsync(id); return model is null ? NotFound() : View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task Create(FloorPlanEditViewModel model) { if (!ModelState.IsValid) { return RedirectToAction(nameof(Index), new { projectId = model.ProjectID }); } var floorPlanId = await floorPlans.SavePlanAsync(model); return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task SavePlan(FloorPlanEditViewModel model) { if (!ModelState.IsValid) { var editor = await floorPlans.GetEditorAsync(model.FloorPlanID); if (editor is null) { return NotFound(); } editor.FloorPlan = model; return View("Edit", editor); } var floorPlanId = await floorPlans.SavePlanAsync(model); TempData["FloorPlanMessage"] = "Floor plan saved."; return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task SaveLayout(FloorPlanEditorViewModel model) { var isAsyncSave = string.Equals(Request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.OrdinalIgnoreCase); try { await floorPlans.SaveLayoutAsync(model); if (isAsyncSave) { return Json(new { ok = true, message = "Saved" }); } TempData["FloorPlanMessage"] = "Layout saved."; } catch (InvalidOperationException ex) { if (isAsyncSave) { Response.StatusCode = StatusCodes.Status400BadRequest; return Json(new { ok = false, message = ex.Message }); } TempData["FloorPlanError"] = ex.Message; } return RedirectToAction(nameof(Edit), new { id = model.FloorPlan.FloorPlanID }); } [HttpPost] [ValidateAntiForgeryToken] public async Task DeletePlan(int id, int projectId) { await floorPlans.DeletePlanAsync(id); TempData["FloorPlanMessage"] = "Floor plan deleted."; return RedirectToAction(nameof(Index), new { projectId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task AddFloor(FloorPlanFloorEditViewModel model) { if (ModelState.IsValid) { await floorPlans.SaveFloorAsync(model); TempData["FloorPlanMessage"] = "Floor added."; } return RedirectToAction(nameof(Edit), new { id = model.FloorPlanID }); } [HttpPost] [ValidateAntiForgeryToken] public async Task DeleteFloor(int floorPlanId, int floorPlanFloorId) { await floorPlans.DeleteFloorAsync(floorPlanFloorId); TempData["FloorPlanMessage"] = "Floor deleted."; return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task UploadBackground(int floorPlanId, int floorPlanFloorId, IFormFile? backgroundImage) { if (backgroundImage is null) { TempData["FloorPlanError"] = "Choose a background image to upload."; return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } var result = await floorPlans.UploadBackgroundAsync(floorPlanId, floorPlanFloorId, backgroundImage); TempData[result.Succeeded ? "FloorPlanMessage" : "FloorPlanError"] = result.Succeeded ? "Background image uploaded." : result.ErrorMessage ?? "Background image could not be uploaded."; return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task SaveBackgroundSettings(FloorPlanFloorBackgroundSettingsViewModel model) { var isAsyncSave = string.Equals(Request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.OrdinalIgnoreCase); try { await floorPlans.SaveBackgroundSettingsAsync(model); if (isAsyncSave) { return Json(new { ok = true, message = "Saved" }); } TempData["FloorPlanMessage"] = "Background settings saved."; } catch (InvalidOperationException ex) { if (isAsyncSave) { Response.StatusCode = StatusCodes.Status400BadRequest; return Json(new { ok = false, message = ex.Message }); } TempData["FloorPlanError"] = ex.Message; } return RedirectToAction(nameof(Edit), new { id = model.FloorPlanID }); } [HttpPost] [ValidateAntiForgeryToken] public async Task ResetBackground(int floorPlanId, int floorPlanFloorId) { await floorPlans.ResetBackgroundAsync(floorPlanId, floorPlanFloorId); TempData["FloorPlanMessage"] = "Background position reset."; return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task RemoveBackground(int floorPlanId, int floorPlanFloorId) { await floorPlans.RemoveBackgroundAsync(floorPlanId, floorPlanFloorId); TempData["FloorPlanMessage"] = "Background image removed."; return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task SaveTransition(int floorPlanId, FloorPlanTransitionEditViewModel model) { try { if (ModelState.IsValid) { await floorPlans.SaveTransitionAsync(floorPlanId, model); TempData["FloorPlanMessage"] = model.FloorPlanTransitionID == 0 ? "Transition created." : "Transition updated."; } } catch (InvalidOperationException ex) { TempData["FloorPlanError"] = ex.Message; } return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task DeleteTransition(int floorPlanId, int floorPlanTransitionId) { try { await floorPlans.DeleteTransitionAsync(floorPlanId, floorPlanTransitionId); TempData["FloorPlanMessage"] = "Transition deleted."; } catch (InvalidOperationException ex) { TempData["FloorPlanError"] = ex.Message; } return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task AddBlock(int floorPlanId, FloorPlanBlockEditViewModel model) { var isAsyncSave = string.Equals(Request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.OrdinalIgnoreCase); try { if (ModelState.IsValid) { var floorPlanBlockId = await floorPlans.SaveBlockAsync(model); if (isAsyncSave) { var block = await floorPlans.GetBlockAsync(floorPlanBlockId); if (block is null) { Response.StatusCode = StatusCodes.Status400BadRequest; return Json(new { ok = false, message = "Block was saved but could not be loaded." }); } return Json(new { ok = true, message = "Saved", block = new { block.FloorPlanBlockID, block.FloorPlanFloorID, block.LocationID, block.LocationName, block.LocationPath, block.DisplayLabel, block.X, block.Y, block.WidthCells, block.HeightCells, block.BlockType, block.LabelOverride, block.Notes } }); } TempData["FloorPlanMessage"] = "Block added."; return RedirectToAction(nameof(Edit), new { id = floorPlanId, selectedBlockId = floorPlanBlockId }); } } catch (InvalidOperationException ex) { if (isAsyncSave) { Response.StatusCode = StatusCodes.Status400BadRequest; return Json(new { ok = false, message = ex.Message }); } TempData["FloorPlanError"] = ex.Message; } if (isAsyncSave) { Response.StatusCode = StatusCodes.Status400BadRequest; return Json(new { ok = false, message = "Block could not be added." }); } return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task DeleteBlock(int floorPlanId, int floorPlanBlockId) { await floorPlans.DeleteBlockAsync(floorPlanBlockId); TempData["FloorPlanMessage"] = "Block deleted."; return RedirectToAction(nameof(Edit), new { id = floorPlanId }); } }