158 lines
6.0 KiB
C#
158 lines
6.0 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using PlotLine.Models;
|
|
using PlotLine.Services;
|
|
using PlotLine.ViewModels;
|
|
|
|
namespace PlotLine.Controllers;
|
|
|
|
public sealed class TimelineController(ITimelineService timeline) : Controller
|
|
{
|
|
public async Task<IActionResult> Index([FromQuery] TimelineFilterViewModel filter)
|
|
{
|
|
if (filter.ProjectID == 0)
|
|
{
|
|
return BadRequest("ProjectID is required.");
|
|
}
|
|
|
|
var model = await timeline.GetAsync(filter);
|
|
return model is null ? NotFound() : View(model);
|
|
}
|
|
|
|
public async Task<IActionResult> Settings(int projectId)
|
|
{
|
|
var model = await timeline.GetSettingsAsync(projectId);
|
|
return model is null ? NotFound() : View(model);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Settings(TimelineSettingsViewModel model)
|
|
{
|
|
if (model.ShowMetricShape && !model.SelectedMetricTypeIDs.Any())
|
|
{
|
|
ModelState.AddModelError(nameof(model.SelectedMetricTypeIDs), "Select at least one metric or disable Metric Shape.");
|
|
}
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
var current = await timeline.GetSettingsAsync(model.ProjectID);
|
|
if (current is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
current.ShowSceneCards = model.ShowSceneCards;
|
|
current.ShowMetricShape = model.ShowMetricShape;
|
|
current.ShowPlotLines = model.ShowPlotLines;
|
|
current.ShowStoryAssets = model.ShowStoryAssets;
|
|
current.ShowCharacterAppearances = model.ShowCharacterAppearances;
|
|
current.ShowWarnings = model.ShowWarnings;
|
|
current.SelectedMetricTypeIDs = model.SelectedMetricTypeIDs;
|
|
current.MetricOptions = current.MetricOptions
|
|
.Select(x => new TimelineMetricSettingOptionViewModel
|
|
{
|
|
MetricTypeID = x.MetricTypeID,
|
|
MetricName = x.MetricName,
|
|
Description = x.Description,
|
|
SortOrder = x.SortOrder,
|
|
IsSelected = model.SelectedMetricTypeIDs.Contains(x.MetricTypeID)
|
|
})
|
|
.ToList();
|
|
return View(current);
|
|
}
|
|
|
|
await timeline.SaveSettingsAsync(model);
|
|
TempData["TimelineSettingsMessage"] = "Timeline settings saved.";
|
|
return RedirectToAction(nameof(Settings), new { projectId = model.ProjectID });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> SavePreset(TimelinePresetSaveViewModel model)
|
|
{
|
|
await timeline.SavePresetAsync(model);
|
|
return RedirectToAction(nameof(Index), new { projectId = model.ProjectID, bookId = model.BookID });
|
|
}
|
|
|
|
public async Task<IActionResult> LoadPreset(int id)
|
|
{
|
|
var preset = await timeline.GetPresetAsync(id);
|
|
if (preset is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var filter = System.Text.Json.JsonSerializer.Deserialize<TimelineFilterViewModel>(preset.FilterJson) ?? new TimelineFilterViewModel();
|
|
filter.ProjectID = preset.ProjectID;
|
|
filter.BookID = preset.BookID;
|
|
filter.FocusType = preset.FocusType;
|
|
filter.FocusID = preset.FocusID;
|
|
return RedirectToAction(nameof(Index), filter);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> DeletePreset(int id, int projectId, int? bookId)
|
|
{
|
|
await timeline.ArchivePresetAsync(id);
|
|
return RedirectToAction(nameof(Index), new { projectId, bookId });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> DropMoveScene(TimelineSceneDropMoveViewModel model)
|
|
{
|
|
var summary = await timeline.MoveSceneByDropAsync(model);
|
|
if (summary is not null)
|
|
{
|
|
TempData["TimelineDragMessage"] = $"Scene moved. Validation found {summary.WarningCount} warning{(summary.WarningCount == 1 ? "" : "s")}.";
|
|
}
|
|
return RedirectToAction(nameof(Index), new { projectId = model.ProjectID, bookId = model.BookID, selectedSceneId = model.SceneID });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> DropCharacter(TimelineSceneCharacterDropViewModel model)
|
|
{
|
|
await RunDropActionAsync(() => timeline.AddCharacterByDropAsync(model), "Character added to scene.");
|
|
return RedirectToAction(nameof(Index), new { projectId = model.ReturnProjectID, bookId = model.ReturnBookID, selectedSceneId = model.SceneID });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> DropAssetEvent(TimelineAssetEventDropViewModel model)
|
|
{
|
|
await RunDropActionAsync(() => timeline.AddAssetEventByDropAsync(model), "Asset event added to scene.");
|
|
return RedirectToAction(nameof(Index), new { projectId = model.ReturnProjectID, bookId = model.ReturnBookID, selectedSceneId = model.SceneID });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> DropAssetDependency(TimelineAssetDependencyDropViewModel model)
|
|
{
|
|
await RunDropActionAsync(() => timeline.AddAssetDependencyByDropAsync(model), "Asset dependency saved.");
|
|
return RedirectToAction(nameof(Index), new { projectId = model.ReturnProjectID, bookId = model.ReturnBookID });
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> DropLocationRelationship(TimelineLocationRelationshipDropViewModel model)
|
|
{
|
|
await RunDropActionAsync(() => timeline.AddLocationRelationshipByDropAsync(model), "Location relationship saved.");
|
|
return RedirectToAction(nameof(Index), new { projectId = model.ProjectID, bookId = model.ReturnBookID });
|
|
}
|
|
|
|
private async Task RunDropActionAsync(Func<Task> action, string successMessage)
|
|
{
|
|
try
|
|
{
|
|
await action();
|
|
TempData["TimelineDragMessage"] = successMessage;
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
TempData["TimelineDragMessage"] = ex.Message;
|
|
}
|
|
}
|
|
}
|