Add project files.
This commit is contained in:
parent
80b268b940
commit
6e237b7287
0
.appdata/.dotnet/10.0.300.aspNetCertificateSentinel
Normal file
0
.appdata/.dotnet/10.0.300.aspNetCertificateSentinel
Normal file
0
.appdata/.dotnet/10.0.300.dotnetFirstUseSentinel
Normal file
0
.appdata/.dotnet/10.0.300.dotnetFirstUseSentinel
Normal file
0
.appdata/.dotnet/10.0.300.toolpath.sentinel
Normal file
0
.appdata/.dotnet/10.0.300.toolpath.sentinel
Normal file
@ -0,0 +1 @@
|
|||||||
|
False
|
||||||
1
.appdata/.dotnet/10.0.300_MachineId.dotnetUserLevelCache
Normal file
1
.appdata/.dotnet/10.0.300_MachineId.dotnetUserLevelCache
Normal file
@ -0,0 +1 @@
|
|||||||
|
ac804ed3-9efd-491a-9433-d35a726f772f
|
||||||
1
.appdata/.dotnet/MachineId.v1.dotnetUserLevelCache
Normal file
1
.appdata/.dotnet/MachineId.v1.dotnetUserLevelCache
Normal file
@ -0,0 +1 @@
|
|||||||
|
01e2d38e-f216-48da-a539-6636fe1c9721
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpSTT2/cNhDFv4rAsyjzn0hRN2Odogs38SJZpEVvFGcUE5apBUU5CYz97gG1GzjprRdBGOjNvPeb0SuJ7hlJT25Pp3cvGPNCapLDVhJMaMo0ZfzITN+2vZIN10YKLv4lNQn3+J30xCjvubdIJWqkClhLBzl6CoCWMw5MtKr0dF8W0r8SFxrAl+CxmZfPmJYwR9KTv0OE+WuZ7ULjp3mFJs0T7uOSXfTFzIf97p7+dXs4PhwuXy24FHETgPSEGyaEUIZ2ykuqOhhpB5ZT3Y6ghnZw3rqLLMSMKbqpWeDpbT7MOWL2vWiEbBgVlpxrAi67YnlwCx6/n4qLDdFdqddbeXvtX8kLJtKL+ifMS7sbP4Wb52VYwwQ3l2d2y1NJeUrzCVMOuDHZYk6Ty2GO1XXSbgoYM6nJw6fqMLk8zun5N1CfLvn3/yP+sUx/9w39mhF28xpz0UpDavLe+ccQsdrfkZ4wjgJkh3QUXJd2jrpWWqq11CNyb43gpCa7OeYQ13ldqn3M+CVtAUhP/nDTghfrt8k/how+r6mk+keXWzikGVafqzf+nDWskYwV0ZpPa64+IoSEPmPJd0zrtd9/JEKLTbRbU8KYq4PLj9WfbnkkPbGu89xZZ53T3o/YMhwZMKeZAdSgEZQAA8oq0Moyr5UbuALX8mEsFVITLOuutguz0jA5qIGyDhlVQijaGddSawZjOxgkUwXxG8fqYSo65zumECS1OAJVljtqlZQUZOuM0KMxYvwFyXX7nBXAgC+Xf2VzoAE6BM2pULylylpOO9saOphusIPusPXF8z2miNMvoN4Hn+ZlHnN1PZ7qN3Qf11j+92o7pK8h0m/bju5m/4SpKjt2IZbzvq71fD6ffwAAAP//AwCvA78iNgQAAA==
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpRTwW7bOBD9FYFnUSEpihJ1C5xd1Ei7MdqgLXobcUY1UYUyKCptEfjfF5RdpN3b3ohHvpk37w1fWIAnYj27PZ3+eqaQFlay5DdICWW4MFzIR9H2TdPrupKtakXTfGEl8/f0k/Ws1c5JZ4nXZIhrFA0f6tFxRLJSSBSq0bkmfF1Y/8LAV0jP3lE1Lx8pLn4OrGeffMD5e+4NvnLTvGIV54n2YUkQXBbzz353z9/eHh4fDpdXCy2ZXHlkPZOtUErplnfa1Vx3OPIOreSmGVEPzQDOwoXmQ6IYYKoW/PbaH+cUKLleVaquBFeWnUuGkCBLHmChx5+nrGKz6C7j5QZvx/6FPVNkvSp/mXkpd+Mmf+PmpycIeDP64JcjK9kpzieKydNmxzbhNEHycyiuTXaTp5BYyR4+FIcJ0jjHpz88+nAZff8/Jn8H7ugDFfs71jMhSWHdER+VNJkDHJracmNqM5J0tlWSlWw3h+TDOq9LsQ+JvsZNJevZ3zAtdNF3G93RJ3JpjVn6Z5Ozph8+7WbMgGAlO8QZV5eKV7ulqERVi3z5sKbTmor3hD6SS5Rneozrtfx/KMqojbRbY6SQigOkY/EGliPrmYXOSbBgAYxzIzWCRoECjGiRDBpCrbBFbTUabYUzGgapERo5jBnJunO6xbZQ2liDQkqOYDquW6E52LrhQ9MNDvQgqM4WvdpaPEyZB64TmrDmlkbk2krgVtc1x7qBVpmxbdX4myXXxKXIxZCeL19jU2AQO0IjudKy4dpayTvbtHxou8EOpqPGZc33FANNvxn1zrs4L/OYiuvCFH9Y934N+XsX2/J894H/2CK7m903ikWOHHzI23xN+Xw+n/8FAAD//wMAJ8SPOyUEAAA=
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACoxUy27kNhD8FYHXiDJfokjdHNuLDBw/sDNIgtxaZMtmLFMDirJ3YfjfA2ns9WMRIBdBaJJd1dXV/UQi3CNpyfF+f/aAMU+kJDmsIcGEpkxTxnesaeu6VbLiDau15n+TkoRz/E5a0ijnuLNIJWqkyrOadrJ31Hu0nHHPRK2WnHAzkfaJQKg8PgSH1Tj9gWkKYyQt+TNEPz4u2BAqN4yzr9I44CZOGaJbyFxuTs7p78fXu6vrw60Jp+VxFTxpCW+YEEI11CgnqTK+p8ZbTnXde9XVHTgLh2chZkwRhmryd2/4fswRs2tFJWTFqLDkuSQeMiyUO5hw932/sFglOl3i5Rpef9sn8oCJtKJ8FfOQ7sgN4eh+6uYw+KP1S0qyT+MeUw64qnG1Ld5IcFaxSmjBGCnJBbjbELHYnBZXw1IiOMMUekkt9p4qy4FaJSX1soZG6L5pRE9Kco4p4vAu60VwaZzGPhcvIhcfcLbHJ2cRugEXjC8wTEhKspm2GaKHYYx49g3dnA+5dmlejk/GmEOcx3kqNjHjTYKX89f3J3NKGHNxDfm2+A2mW9ISC8ZxsGABtHM91gx75hlo1njUXqNXwjdeWeW1ssxpBR1XHmre9UuElOTXRcPt7BxO0xub1STDsHIoXvp0MgSM+YOMpCWMo/DSIO0F14tLgEItLdVa6h65s43grzA7SDeYSUt603Udc64TynIHwoL0PZNe91aIpgHVy7pRQuuaNeB80wiuvGzQgewlM4KU5DqNfnb5c6vl2oDrNP6DLi9SkZZoVtfGQdcJrbgBa1BxrYy22FjVA1PSdcBrA13HGdTMWSMccG1Bdb7uf6h0Fm9CxNMw7Qf4/g7XVLqSvzgAw3tQn65/SXCPj2O6uzzYuLo825GSfJ3jshGKzWKRxxDpN71M9MU85LC7TQge/cXo8Wcf4TIuxWFCbW28YpLqpkaqhOPUcGVo4xB75TrXWU1Kcjq6O0zF4jAIcZmqH8mutsVxcrcho8tzWvj9tfK42hbXA+R+TPcfNsm7wj4LUAnJ2dIZjw+HbbRy1N4b9JpToXhNlbWcGls3tGtMZzttsHaLYldz3s+5+Io+JHR5rfjFiyvmyS26u5/FeHXBi0U5W7y2PWyxVdr/tcSeS3KPMM0J79dt3T6RTYyYVuTT+TCKm3gRhiFM6MboJ9Jq0ZjKGmZfGP7nPVnbSinJn5+fn/8FAAD//wMA8VM0JxsGAAA=
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpSU0W7cKhCGX8Xi2jiAMTa+izatukrbrJpVe3TuxjBuUBy8Apw2ivbdK+yt2lwdnRuExvzDP98MfiUenpD05Pp0eveMPkVSkuTWkGBCUaYo40fW9k3Ty7riqlGiVf+SkrhbfCE9aaUx3GikNSqk0rKGDvVoqLWoOeOWiUbmnPA9kv6VgKssPjuD1Ry/Yohu9qQn35y38498N7jKTPNiqzBPuPcxgTfZzOf97pZ+vD4c7w7bqYgxiytnSU94y4QQsqWdNDWVnR1pZzWnqhmtHJoBjIZN5nzC4GGqon38c7+dk8dkelGJumJUaHIuiYUE2fIAEY8vp+xiRXST4+UaXrf9K3nGQHpR/oa5pbsyk7t6isPiJnu1rQniY1zXEUyawwspySnMJwzJ4crnOkZ8GqaXI8TH99uZvI3vfqJZEtrdvPiUK64z1RXQNEFysy8uHneTQ5/Wjyk4H535r1w1KcndfXGYII1zeHrTj/sN8/5/UP4E5sF5LPY3pCeMo7B1h3QUXGUNUGhqTZWq1Yjc6FZwUpLd7JPzy7zEYu8Tfg9rSaQn72GKuPm7DubBJTRpCbnOf1QmcAizXUwq/vSSs4pVNWNZtKTTkoovaF1AkzAXcQzLJd/XtxKhxCraLSGgT8UB0kPxAeID6YmGznDQoAGUMSM2DEdmGSjWWlRWoZXCtlZqaZXUzCgJA5cWGj6MOUJKgnl0inValWlbVMJSQItUKom0q7uRGgVty0dRKz284VjcTVkHpmMSbU01jpZKzYFqWdfU1g20Qo1tK8a/kFzmgbMM2OLz9u42B9Z2aBWnQvKGSq057XTT0qHtBj2oDhuTPd9i8Dj9BeqTM2GO85iKy4QUb9B9WXz+dxTrtPxwnv5ce3Qzm0cMRe4xOJ+fyqWt5/P5/AsAAP//AwC9ok9gggQAAA==
|
||||||
7
.appdata/NuGet/NuGet.Config
Normal file
7
.appdata/NuGet/NuGet.Config
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
7
NuGet.Config
Normal file
7
NuGet.Config
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
3
PlotLine.slnx
Normal file
3
PlotLine.slnx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="PlotLine/PlotLine.csproj" />
|
||||||
|
</Solution>
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<key id="b800ca31-7991-402d-8017-716ba11edc31" version="1">
|
||||||
|
<creationDate>2026-05-31T13:09:43.9306091Z</creationDate>
|
||||||
|
<activationDate>2026-05-31T13:09:43.9306091Z</activationDate>
|
||||||
|
<expirationDate>2026-08-29T13:09:43.9306091Z</expirationDate>
|
||||||
|
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=10.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||||
|
<descriptor>
|
||||||
|
<encryption algorithm="AES_256_CBC" />
|
||||||
|
<validation algorithm="HMACSHA256" />
|
||||||
|
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||||
|
<!-- Warning: the key below is in an unencrypted form. -->
|
||||||
|
<value>rYC5RTgXQzx3wsUyn9wS1B53mDgBKXjX+/G96+7qkSTGiNqcMbK60Zgr/ujMcmusWpeyIXHuajhvceGxlWONQg==</value>
|
||||||
|
</masterKey>
|
||||||
|
</descriptor>
|
||||||
|
</descriptor>
|
||||||
|
</key>
|
||||||
19
PlotLine/Controllers/AnalyticsController.cs
Normal file
19
PlotLine/Controllers/AnalyticsController.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class AnalyticsController(IAnalyticsService analytics) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(
|
||||||
|
int projectId,
|
||||||
|
int? bookId,
|
||||||
|
decimal? startChapterNumber,
|
||||||
|
decimal? endChapterNumber,
|
||||||
|
decimal? startSceneNumber,
|
||||||
|
decimal? endSceneNumber)
|
||||||
|
{
|
||||||
|
var model = await analytics.GetStoryHealthAsync(projectId, bookId, startChapterNumber, endChapterNumber, startSceneNumber, endSceneNumber);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
PlotLine/Controllers/BooksController.cs
Normal file
47
PlotLine/Controllers/BooksController.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class BooksController(IBookService books) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Details(int id)
|
||||||
|
{
|
||||||
|
var model = await books.GetDetailAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Create(int projectId)
|
||||||
|
{
|
||||||
|
var model = await books.GetCreateAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await books.GetEditAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(BookEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bookId = await books.SaveAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = bookId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int projectId)
|
||||||
|
{
|
||||||
|
await books.ArchiveAsync(id);
|
||||||
|
return RedirectToAction("Details", "Projects", new { id = projectId });
|
||||||
|
}
|
||||||
|
}
|
||||||
47
PlotLine/Controllers/ChaptersController.cs
Normal file
47
PlotLine/Controllers/ChaptersController.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class ChaptersController(IChapterService chapters) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Details(int id)
|
||||||
|
{
|
||||||
|
var model = await chapters.GetDetailAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Create(int bookId)
|
||||||
|
{
|
||||||
|
var model = await chapters.GetCreateAsync(bookId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await chapters.GetEditAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(ChapterEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var chapterId = await chapters.SaveAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = chapterId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int bookId)
|
||||||
|
{
|
||||||
|
await chapters.ArchiveAsync(id);
|
||||||
|
return RedirectToAction("Details", "Books", new { id = bookId });
|
||||||
|
}
|
||||||
|
}
|
||||||
53
PlotLine/Controllers/CharactersController.cs
Normal file
53
PlotLine/Controllers/CharactersController.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class CharactersController(ICharacterService characters) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(int projectId)
|
||||||
|
{
|
||||||
|
var model = await characters.GetCharactersAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Create(int projectId)
|
||||||
|
{
|
||||||
|
var model = await characters.GetCreateCharacterAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await characters.GetEditCharacterAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Details(int id)
|
||||||
|
{
|
||||||
|
var model = await characters.GetCharacterDetailAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(CharacterEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var characterId = await characters.SaveCharacterAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = characterId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int projectId)
|
||||||
|
{
|
||||||
|
await characters.ArchiveCharacterAsync(id);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
|
}
|
||||||
|
}
|
||||||
27
PlotLine/Controllers/HomeController.cs
Normal file
27
PlotLine/Controllers/HomeController.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using PlotLine.Models;
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers
|
||||||
|
{
|
||||||
|
public class HomeController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Privacy()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
public IActionResult Error()
|
||||||
|
{
|
||||||
|
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
PlotLine/Controllers/LocationsController.cs
Normal file
69
PlotLine/Controllers/LocationsController.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class LocationsController(ILocationService locations) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(int projectId)
|
||||||
|
{
|
||||||
|
var model = await locations.GetLocationsAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Create(int projectId)
|
||||||
|
{
|
||||||
|
var model = await locations.GetCreateAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await locations.GetEditAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Details(int id)
|
||||||
|
{
|
||||||
|
var model = await locations.GetDetailAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(LocationEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var locationId = await locations.SaveAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = locationId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int projectId)
|
||||||
|
{
|
||||||
|
await locations.ArchiveAsync(id);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddRelationship(LocationRelationshipEditViewModel model)
|
||||||
|
{
|
||||||
|
await locations.SaveRelationshipAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = model.FromLocationID });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> ArchiveRelationship(int id, int returnLocationId)
|
||||||
|
{
|
||||||
|
await locations.ArchiveRelationshipAsync(id);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = returnLocationId });
|
||||||
|
}
|
||||||
|
}
|
||||||
47
PlotLine/Controllers/PlotLinesController.cs
Normal file
47
PlotLine/Controllers/PlotLinesController.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class PlotLinesController(IPlotService plots) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(int projectId)
|
||||||
|
{
|
||||||
|
var model = await plots.GetPlotLinesAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Create(int projectId)
|
||||||
|
{
|
||||||
|
var model = await plots.GetCreatePlotLineAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await plots.GetEditPlotLineAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(PlotLineEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
await plots.SavePlotLineAsync(model);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId = model.ProjectID });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int projectId)
|
||||||
|
{
|
||||||
|
await plots.ArchivePlotLineAsync(id);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
|
}
|
||||||
|
}
|
||||||
47
PlotLine/Controllers/PlotThreadsController.cs
Normal file
47
PlotLine/Controllers/PlotThreadsController.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class PlotThreadsController(IPlotService plots) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(int projectId)
|
||||||
|
{
|
||||||
|
var model = await plots.GetPlotThreadsAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Create(int projectId, int? plotLineId)
|
||||||
|
{
|
||||||
|
var model = await plots.GetCreatePlotThreadAsync(projectId, plotLineId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await plots.GetEditPlotThreadAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(PlotThreadEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
await plots.SavePlotThreadAsync(model);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId = model.ProjectID });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int projectId)
|
||||||
|
{
|
||||||
|
await plots.ArchivePlotThreadAsync(id);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
|
}
|
||||||
|
}
|
||||||
45
PlotLine/Controllers/ProjectsController.cs
Normal file
45
PlotLine/Controllers/ProjectsController.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class ProjectsController(IProjectService projects) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index() => View(await projects.ListAsync());
|
||||||
|
|
||||||
|
public async Task<IActionResult> Details(int id)
|
||||||
|
{
|
||||||
|
var model = await projects.GetDetailAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Create() => View("Edit", new ProjectEditViewModel());
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await projects.GetEditAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(ProjectEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectId = await projects.SaveAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = projectId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id)
|
||||||
|
{
|
||||||
|
await projects.ArchiveAsync(id);
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
}
|
||||||
268
PlotLine/Controllers/ScenesController.cs
Normal file
268
PlotLine/Controllers/ScenesController.cs
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class ScenesController(ISceneService scenes, IPlotService plots, IAssetService assets, ICharacterService characters, ILocationService locations) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Create(int chapterId)
|
||||||
|
{
|
||||||
|
var model = await scenes.GetCreateAsync(chapterId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await scenes.GetEditAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(SceneEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sceneId = await scenes.SaveAsync(model);
|
||||||
|
if (model.ReturnToTimeline && model.ReturnProjectID.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new
|
||||||
|
{
|
||||||
|
projectId = model.ReturnProjectID.Value,
|
||||||
|
bookId = model.ReturnBookID,
|
||||||
|
selectedSceneId = sceneId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int chapterId)
|
||||||
|
{
|
||||||
|
await scenes.ArchiveAsync(id);
|
||||||
|
return RedirectToAction("Details", "Chapters", new { id = chapterId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> MoveUp(int id, int chapterId)
|
||||||
|
{
|
||||||
|
await scenes.MoveAsync(id, "Up");
|
||||||
|
return RedirectToAction("Details", "Chapters", new { id = chapterId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> MoveDown(int id, int chapterId)
|
||||||
|
{
|
||||||
|
await scenes.MoveAsync(id, "Down");
|
||||||
|
return RedirectToAction("Details", "Chapters", new { id = chapterId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddThreadEvent(ThreadEventCreateViewModel model)
|
||||||
|
{
|
||||||
|
await plots.AddThreadEventAsync(model);
|
||||||
|
if (model.ReturnProjectID.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new
|
||||||
|
{
|
||||||
|
projectId = model.ReturnProjectID.Value,
|
||||||
|
bookId = model.ReturnBookID,
|
||||||
|
selectedSceneId = model.SceneID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = model.SceneID });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteThreadEvent(int id, int sceneId, int? projectId, int? bookId)
|
||||||
|
{
|
||||||
|
await plots.DeleteThreadEventAsync(id);
|
||||||
|
if (projectId.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddAssetEvent(AssetEventCreateViewModel model)
|
||||||
|
{
|
||||||
|
await assets.AddAssetEventAsync(model);
|
||||||
|
if (model.ReturnProjectID.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new
|
||||||
|
{
|
||||||
|
projectId = model.ReturnProjectID.Value,
|
||||||
|
bookId = model.ReturnBookID,
|
||||||
|
selectedSceneId = model.SceneID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = model.SceneID });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteAssetEvent(int id, int sceneId, int? projectId, int? bookId)
|
||||||
|
{
|
||||||
|
await assets.DeleteAssetEventAsync(id);
|
||||||
|
if (projectId.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddAssetCustodyEvent(AssetCustodyCreateViewModel model)
|
||||||
|
{
|
||||||
|
await assets.AddCustodyEventAsync(model);
|
||||||
|
if (model.ReturnProjectID.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new
|
||||||
|
{
|
||||||
|
projectId = model.ReturnProjectID.Value,
|
||||||
|
bookId = model.ReturnBookID,
|
||||||
|
selectedSceneId = model.SceneID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = model.SceneID });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddSceneAssetLocation(SceneAssetLocationCreateViewModel model)
|
||||||
|
{
|
||||||
|
await locations.SaveSceneAssetLocationAsync(model);
|
||||||
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteSceneAssetLocation(int id, int sceneId, int? projectId, int? bookId)
|
||||||
|
{
|
||||||
|
await locations.DeleteSceneAssetLocationAsync(id);
|
||||||
|
return RedirectForScene(sceneId, projectId, bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteAssetCustodyEvent(int id, int sceneId, int? projectId, int? bookId)
|
||||||
|
{
|
||||||
|
await assets.DeleteCustodyEventAsync(id);
|
||||||
|
if (projectId.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddSceneCharacter(SceneCharacterCreateViewModel model)
|
||||||
|
{
|
||||||
|
await characters.AddSceneCharacterAsync(model);
|
||||||
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteSceneCharacter(int id, int sceneId, int? projectId, int? bookId)
|
||||||
|
{
|
||||||
|
await characters.DeleteSceneCharacterAsync(id);
|
||||||
|
return RedirectForScene(sceneId, projectId, bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddSceneDependency(SceneDependencyCreateViewModel model)
|
||||||
|
{
|
||||||
|
await scenes.AddDependencyAsync(model);
|
||||||
|
return RedirectForScene(model.CurrentSceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteSceneDependency(int id, int sceneId, int? projectId, int? bookId)
|
||||||
|
{
|
||||||
|
await scenes.DeleteDependencyAsync(id);
|
||||||
|
return RedirectForScene(sceneId, projectId, bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> PreviewMove(SceneMoveRequestViewModel model)
|
||||||
|
{
|
||||||
|
var preview = await scenes.GetMovePreviewAsync(model);
|
||||||
|
return preview is null ? NotFound() : View("MovePreview", preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> ConfirmMove(SceneMoveRequestViewModel model)
|
||||||
|
{
|
||||||
|
await scenes.MoveToChapterAsync(model);
|
||||||
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> RenumberChapter(int chapterId, int? projectId, int? bookId, int? sceneId)
|
||||||
|
{
|
||||||
|
await scenes.RenumberChapterAsync(chapterId);
|
||||||
|
return sceneId.HasValue
|
||||||
|
? RedirectForScene(sceneId.Value, projectId, bookId)
|
||||||
|
: RedirectToAction("Details", "Chapters", new { id = chapterId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddCharacterAttributeEvent(CharacterAttributeEventCreateViewModel model)
|
||||||
|
{
|
||||||
|
await characters.AddAttributeEventAsync(model);
|
||||||
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddCharacterKnowledge(CharacterKnowledgeCreateViewModel model)
|
||||||
|
{
|
||||||
|
await characters.AddKnowledgeAsync(model);
|
||||||
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddRelationshipEvent(RelationshipEventCreateViewModel model)
|
||||||
|
{
|
||||||
|
await characters.AddRelationshipEventAsync(model);
|
||||||
|
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IActionResult RedirectForScene(int sceneId, int? projectId, int? bookId)
|
||||||
|
{
|
||||||
|
if (projectId.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new { projectId = projectId.Value, bookId, selectedSceneId = sceneId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Edit), new { id = sceneId });
|
||||||
|
}
|
||||||
|
}
|
||||||
69
PlotLine/Controllers/StoryAssetsController.cs
Normal file
69
PlotLine/Controllers/StoryAssetsController.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class StoryAssetsController(IAssetService assets) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(int projectId)
|
||||||
|
{
|
||||||
|
var model = await assets.GetAssetsAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Create(int projectId)
|
||||||
|
{
|
||||||
|
var model = await assets.GetCreateAssetAsync(projectId);
|
||||||
|
return model is null ? NotFound() : View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Edit(int id)
|
||||||
|
{
|
||||||
|
var model = await assets.GetEditAssetAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Details(int id)
|
||||||
|
{
|
||||||
|
var model = await assets.GetAssetDetailAsync(id);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Save(StoryAssetEditViewModel model)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View("Edit", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var assetId = await assets.SaveAssetAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = assetId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Archive(int id, int projectId)
|
||||||
|
{
|
||||||
|
await assets.ArchiveAssetAsync(id);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> AddDependency(AssetDependencyEditViewModel model, int returnAssetId)
|
||||||
|
{
|
||||||
|
await assets.SaveDependencyAsync(model);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = returnAssetId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteDependency(int id, int returnAssetId)
|
||||||
|
{
|
||||||
|
await assets.DeleteDependencyAsync(id);
|
||||||
|
return RedirectToAction(nameof(Details), new { id = returnAssetId });
|
||||||
|
}
|
||||||
|
}
|
||||||
13
PlotLine/Controllers/TimelineController.cs
Normal file
13
PlotLine/Controllers/TimelineController.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class TimelineController(ITimelineService timeline) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(int projectId, int? bookId, int? selectedSceneId)
|
||||||
|
{
|
||||||
|
var model = await timeline.GetAsync(projectId, bookId, selectedSceneId);
|
||||||
|
return model is null ? NotFound() : View(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
PlotLine/Controllers/WarningsController.cs
Normal file
86
PlotLine/Controllers/WarningsController.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class WarningsController(IContinuityValidationService validation) : Controller
|
||||||
|
{
|
||||||
|
public async Task<IActionResult> Index(
|
||||||
|
int projectId,
|
||||||
|
int? bookId,
|
||||||
|
int? sceneId,
|
||||||
|
int? warningTypeId,
|
||||||
|
int? warningSeverityId,
|
||||||
|
string? entityType,
|
||||||
|
bool includeDismissed = false,
|
||||||
|
bool includeIntentional = false)
|
||||||
|
{
|
||||||
|
var model = await validation.GetDashboardAsync(projectId, bookId, sceneId, warningTypeId, warningSeverityId, entityType, includeDismissed, includeIntentional);
|
||||||
|
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, 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, 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 (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)
|
||||||
|
{
|
||||||
|
await validation.DismissAsync(id);
|
||||||
|
return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> MarkIntentional(int id, int projectId, int? bookId, int? sceneId, bool returnToTimeline = false)
|
||||||
|
{
|
||||||
|
await validation.MarkIntentionalAsync(id);
|
||||||
|
return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Restore(int id, int projectId, int? bookId, int? sceneId)
|
||||||
|
{
|
||||||
|
await validation.RestoreAsync(id);
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId, includeDismissed = true, includeIntentional = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline)
|
||||||
|
{
|
||||||
|
if (returnToTimeline && sceneId.HasValue)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId.Value });
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId });
|
||||||
|
}
|
||||||
|
}
|
||||||
1372
PlotLine/Data/Repositories.cs
Normal file
1372
PlotLine/Data/Repositories.cs
Normal file
File diff suppressed because it is too large
Load Diff
17
PlotLine/Data/SqlConnectionFactory.cs
Normal file
17
PlotLine/Data/SqlConnectionFactory.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace PlotLine.Data;
|
||||||
|
|
||||||
|
public interface ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
IDbConnection CreateConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
private readonly string _connectionString = configuration.GetConnectionString("DefaultConnection")
|
||||||
|
?? throw new InvalidOperationException("DefaultConnection is not configured.");
|
||||||
|
|
||||||
|
public IDbConnection CreateConnection() => new SqlConnection(_connectionString);
|
||||||
|
}
|
||||||
1026
PlotLine/Models/CoreModels.cs
Normal file
1026
PlotLine/Models/CoreModels.cs
Normal file
File diff suppressed because it is too large
Load Diff
9
PlotLine/Models/ErrorViewModel.cs
Normal file
9
PlotLine/Models/ErrorViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace PlotLine.Models
|
||||||
|
{
|
||||||
|
public class ErrorViewModel
|
||||||
|
{
|
||||||
|
public string? RequestId { get; set; }
|
||||||
|
|
||||||
|
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
PlotLine/PlotLine.csproj
Normal file
14
PlotLine/PlotLine.csproj
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||||
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
67
PlotLine/Program.cs
Normal file
67
PlotLine/Program.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using PlotLine.Data;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
|
||||||
|
namespace PlotLine;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddConsole();
|
||||||
|
builder.Logging.AddDebug();
|
||||||
|
|
||||||
|
builder.Services.AddControllersWithViews();
|
||||||
|
builder.Services.AddDataProtection()
|
||||||
|
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(builder.Environment.ContentRootPath, "App_Data", "DataProtectionKeys")));
|
||||||
|
builder.Services.AddSingleton<ISqlConnectionFactory, SqlConnectionFactory>();
|
||||||
|
builder.Services.AddScoped<IProjectRepository, ProjectRepository>();
|
||||||
|
builder.Services.AddScoped<IBookRepository, BookRepository>();
|
||||||
|
builder.Services.AddScoped<IChapterRepository, ChapterRepository>();
|
||||||
|
builder.Services.AddScoped<ISceneRepository, SceneRepository>();
|
||||||
|
builder.Services.AddScoped<ILookupRepository, LookupRepository>();
|
||||||
|
builder.Services.AddScoped<ITimelineRepository, TimelineRepository>();
|
||||||
|
builder.Services.AddScoped<IPlotRepository, PlotRepository>();
|
||||||
|
builder.Services.AddScoped<IAssetRepository, AssetRepository>();
|
||||||
|
builder.Services.AddScoped<ICharacterRepository, CharacterRepository>();
|
||||||
|
builder.Services.AddScoped<ILocationRepository, LocationRepository>();
|
||||||
|
builder.Services.AddScoped<IWarningRepository, WarningRepository>();
|
||||||
|
builder.Services.AddScoped<ISceneDependencyRepository, SceneDependencyRepository>();
|
||||||
|
builder.Services.AddScoped<IAnalyticsRepository, AnalyticsRepository>();
|
||||||
|
builder.Services.AddScoped<IProjectService, ProjectService>();
|
||||||
|
builder.Services.AddScoped<IBookService, BookService>();
|
||||||
|
builder.Services.AddScoped<IChapterService, ChapterService>();
|
||||||
|
builder.Services.AddScoped<ISceneService, SceneService>();
|
||||||
|
builder.Services.AddScoped<ITimelineService, TimelineService>();
|
||||||
|
builder.Services.AddScoped<IPlotService, PlotService>();
|
||||||
|
builder.Services.AddScoped<IAssetService, AssetService>();
|
||||||
|
builder.Services.AddScoped<ICharacterService, CharacterService>();
|
||||||
|
builder.Services.AddScoped<ILocationService, LocationService>();
|
||||||
|
builder.Services.AddScoped<IContinuityValidationService, ContinuityValidationService>();
|
||||||
|
builder.Services.AddScoped<IAnalyticsService, AnalyticsService>();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (!app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseExceptionHandler("/Home/Error");
|
||||||
|
app.UseHsts();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapStaticAssets();
|
||||||
|
app.MapControllerRoute(
|
||||||
|
name: "default",
|
||||||
|
pattern: "{controller=Projects}/{action=Index}/{id?}")
|
||||||
|
.WithStaticAssets();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
23
PlotLine/Properties/launchSettings.json
Normal file
23
PlotLine/Properties/launchSettings.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "http://localhost:5229",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:7296;http://localhost:5229",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1903
PlotLine/Services/CoreServices.cs
Normal file
1903
PlotLine/Services/CoreServices.cs
Normal file
File diff suppressed because it is too large
Load Diff
650
PlotLine/Sql/001_Phase1A_Core.sql
Normal file
650
PlotLine/Sql/001_Phase1A_Core.sql
Normal file
@ -0,0 +1,650 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ScenePurposes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ScenePurposes
|
||||||
|
(
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
ScenePurposeTypeID int NOT NULL,
|
||||||
|
CONSTRAINT PK_ScenePurposes PRIMARY KEY (SceneID, ScenePurposeTypeID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.SceneOutcomes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.SceneOutcomes
|
||||||
|
(
|
||||||
|
SceneOutcomeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneOutcomes PRIMARY KEY,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
OutcomeDescription nvarchar(1000) NOT NULL,
|
||||||
|
OutcomeTypeID int NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_SceneOutcomes_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_SceneOutcomes_UpdatedDate DEFAULT SYSUTCDATETIME()
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ScenePurposeTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ScenePurposeTypes
|
||||||
|
(
|
||||||
|
ScenePurposeTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ScenePurposeTypes PRIMARY KEY,
|
||||||
|
PurposeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_ScenePurposeTypes_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT UQ_ScenePurposeTypes_PurposeName UNIQUE (PurposeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.TimeConfidences', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.TimeConfidences
|
||||||
|
(
|
||||||
|
TimeConfidenceID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_TimeConfidences PRIMARY KEY,
|
||||||
|
TimeConfidenceName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
CONSTRAINT UQ_TimeConfidences_TimeConfidenceName UNIQUE (TimeConfidenceName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.DurationUnits', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.DurationUnits
|
||||||
|
(
|
||||||
|
DurationUnitID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_DurationUnits PRIMARY KEY,
|
||||||
|
DurationUnitName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
CONSTRAINT UQ_DurationUnits_DurationUnitName UNIQUE (DurationUnitName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.TimeModes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.TimeModes
|
||||||
|
(
|
||||||
|
TimeModeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_TimeModes PRIMARY KEY,
|
||||||
|
TimeModeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
CONSTRAINT UQ_TimeModes_TimeModeName UNIQUE (TimeModeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.RevisionStatuses', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.RevisionStatuses
|
||||||
|
(
|
||||||
|
RevisionStatusID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_RevisionStatuses PRIMARY KEY,
|
||||||
|
StatusName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
CONSTRAINT UQ_RevisionStatuses_StatusName UNIQUE (StatusName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Projects', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Projects
|
||||||
|
(
|
||||||
|
ProjectID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Projects PRIMARY KEY,
|
||||||
|
ProjectName nvarchar(200) NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_Projects_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_Projects_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_Projects_IsArchived DEFAULT 0
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Books', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Books
|
||||||
|
(
|
||||||
|
BookID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Books PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
BookTitle nvarchar(200) NOT NULL,
|
||||||
|
BookNumber int NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_Books_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_Books_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_Books_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_Books_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Chapters', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Chapters
|
||||||
|
(
|
||||||
|
ChapterID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Chapters PRIMARY KEY,
|
||||||
|
BookID int NOT NULL,
|
||||||
|
ChapterNumber decimal(9,2) NOT NULL,
|
||||||
|
ChapterTitle nvarchar(200) NOT NULL,
|
||||||
|
POVCharacterID int NULL,
|
||||||
|
Summary nvarchar(max) NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
RevisionStatusID int NOT NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_Chapters_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_Chapters_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_Chapters_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_Chapters_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID),
|
||||||
|
CONSTRAINT FK_Chapters_RevisionStatuses FOREIGN KEY (RevisionStatusID) REFERENCES dbo.RevisionStatuses(RevisionStatusID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Scenes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Scenes
|
||||||
|
(
|
||||||
|
SceneID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Scenes PRIMARY KEY,
|
||||||
|
ChapterID int NOT NULL,
|
||||||
|
SceneNumber decimal(9,2) NOT NULL,
|
||||||
|
SceneTitle nvarchar(200) NOT NULL,
|
||||||
|
Summary nvarchar(max) NULL,
|
||||||
|
POVCharacterID int NULL,
|
||||||
|
PrimaryLocationID int NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
TimeModeID int NOT NULL,
|
||||||
|
StartDateTime datetime2 NULL,
|
||||||
|
EndDateTime datetime2 NULL,
|
||||||
|
DurationAmount decimal(9,2) NULL,
|
||||||
|
DurationUnitID int NULL,
|
||||||
|
RelativeTimeText nvarchar(200) NULL,
|
||||||
|
TimeConfidenceID int NOT NULL,
|
||||||
|
ScenePurposeNotes nvarchar(max) NULL,
|
||||||
|
SceneOutcomeNotes nvarchar(max) NULL,
|
||||||
|
RevisionStatusID int NOT NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_Scenes_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_Scenes_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_Scenes_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_Scenes_Chapters FOREIGN KEY (ChapterID) REFERENCES dbo.Chapters(ChapterID),
|
||||||
|
CONSTRAINT FK_Scenes_TimeModes FOREIGN KEY (TimeModeID) REFERENCES dbo.TimeModes(TimeModeID),
|
||||||
|
CONSTRAINT FK_Scenes_DurationUnits FOREIGN KEY (DurationUnitID) REFERENCES dbo.DurationUnits(DurationUnitID),
|
||||||
|
CONSTRAINT FK_Scenes_TimeConfidences FOREIGN KEY (TimeConfidenceID) REFERENCES dbo.TimeConfidences(TimeConfidenceID),
|
||||||
|
CONSTRAINT FK_Scenes_RevisionStatuses FOREIGN KEY (RevisionStatusID) REFERENCES dbo.RevisionStatuses(RevisionStatusID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ScenePurposes_Scenes')
|
||||||
|
ALTER TABLE dbo.ScenePurposes ADD CONSTRAINT FK_ScenePurposes_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ScenePurposes_ScenePurposeTypes')
|
||||||
|
ALTER TABLE dbo.ScenePurposes ADD CONSTRAINT FK_ScenePurposes_ScenePurposeTypes FOREIGN KEY (ScenePurposeTypeID) REFERENCES dbo.ScenePurposeTypes(ScenePurposeTypeID);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_SceneOutcomes_Scenes')
|
||||||
|
ALTER TABLE dbo.SceneOutcomes ADD CONSTRAINT FK_SceneOutcomes_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID);
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Books_ProjectID_SortOrder' AND object_id = OBJECT_ID(N'dbo.Books'))
|
||||||
|
CREATE INDEX IX_Books_ProjectID_SortOrder ON dbo.Books(ProjectID, IsArchived, SortOrder);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Chapters_BookID_SortOrder' AND object_id = OBJECT_ID(N'dbo.Chapters'))
|
||||||
|
CREATE INDEX IX_Chapters_BookID_SortOrder ON dbo.Chapters(BookID, IsArchived, SortOrder);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Scenes_ChapterID_SortOrder' AND object_id = OBJECT_ID(N'dbo.Scenes'))
|
||||||
|
CREATE INDEX IX_Scenes_ChapterID_SortOrder ON dbo.Scenes(ChapterID, IsArchived, SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.RevisionStatuses AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Planned', 10), (N'Outlined', 20), (N'Drafted', 30), (N'Needs Work', 40),
|
||||||
|
(N'Revised', 50), (N'Polished', 60), (N'Locked', 70), (N'Cut Candidate', 80)
|
||||||
|
) AS source (StatusName, SortOrder)
|
||||||
|
ON target.StatusName = source.StatusName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (StatusName, SortOrder) VALUES (source.StatusName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.TimeModes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Exact DateTime', 10), (N'Exact Date', 20), (N'Relative', 30), (N'Approximate', 40),
|
||||||
|
(N'Unknown', 50), (N'Flashback', 60), (N'Timeless / Memory', 70)
|
||||||
|
) AS source (TimeModeName, SortOrder)
|
||||||
|
ON target.TimeModeName = source.TimeModeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TimeModeName, SortOrder) VALUES (source.TimeModeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.DurationUnits AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Minutes', 10), (N'Hours', 20), (N'Days', 30), (N'Weeks', 40),
|
||||||
|
(N'Months', 50), (N'Years', 60), (N'Unknown', 70)
|
||||||
|
) AS source (DurationUnitName, SortOrder)
|
||||||
|
ON target.DurationUnitName = source.DurationUnitName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (DurationUnitName, SortOrder) VALUES (source.DurationUnitName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.TimeConfidences AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Confirmed', 10), (N'Implied', 20), (N'Estimated', 30), (N'Unknown', 40)
|
||||||
|
) AS source (TimeConfidenceName, SortOrder)
|
||||||
|
ON target.TimeConfidenceName = source.TimeConfidenceName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TimeConfidenceName, SortOrder) VALUES (source.TimeConfidenceName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.ScenePurposeTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Advances Plot', 10), (N'Character Development', 20), (N'Relationship Development', 30),
|
||||||
|
(N'Setup', 40), (N'Payoff', 50), (N'Reveal', 60), (N'Exposition', 70),
|
||||||
|
(N'Mystery', 80), (N'Emotional Recovery', 90), (N'Comic Relief', 100),
|
||||||
|
(N'Action', 110), (N'Transition / Travel', 120), (N'Location Change', 130),
|
||||||
|
(N'Time Passage', 140), (N'Consequence', 150), (N'Reversal', 160)
|
||||||
|
) AS source (PurposeName, SortOrder)
|
||||||
|
ON target.PurposeName = source.PurposeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (PurposeName, SortOrder) VALUES (source.PurposeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Project_List
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Projects
|
||||||
|
WHERE IsArchived = 0
|
||||||
|
ORDER BY ProjectName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Project_Get
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Projects
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Project_Save
|
||||||
|
@ProjectID int = NULL,
|
||||||
|
@ProjectName nvarchar(200),
|
||||||
|
@Description nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @ProjectID IS NULL OR @ProjectID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.Projects (ProjectName, Description)
|
||||||
|
VALUES (@ProjectName, @Description);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int) AS ProjectID;
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE dbo.Projects
|
||||||
|
SET ProjectName = @ProjectName,
|
||||||
|
Description = @Description,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE ProjectID = @ProjectID;
|
||||||
|
|
||||||
|
SELECT @ProjectID AS ProjectID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Project_Archive
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.Projects SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE ProjectID = @ProjectID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Book_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Books
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0
|
||||||
|
ORDER BY SortOrder, BookNumber, BookTitle;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Book_Get
|
||||||
|
@BookID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Books
|
||||||
|
WHERE BookID = @BookID AND IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Book_Save
|
||||||
|
@BookID int = NULL,
|
||||||
|
@ProjectID int,
|
||||||
|
@BookTitle nvarchar(200),
|
||||||
|
@BookNumber int,
|
||||||
|
@Description nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @BookID IS NULL OR @BookID = 0
|
||||||
|
BEGIN
|
||||||
|
DECLARE @NextBookOrder int = ISNULL((SELECT MAX(SortOrder) FROM dbo.Books WHERE ProjectID = @ProjectID), 0) + 10;
|
||||||
|
INSERT dbo.Books (ProjectID, BookTitle, BookNumber, Description, SortOrder)
|
||||||
|
VALUES (@ProjectID, @BookTitle, @BookNumber, @Description, @NextBookOrder);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int) AS BookID;
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE dbo.Books
|
||||||
|
SET BookTitle = @BookTitle,
|
||||||
|
BookNumber = @BookNumber,
|
||||||
|
Description = @Description,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE BookID = @BookID;
|
||||||
|
|
||||||
|
SELECT @BookID AS BookID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Book_Archive
|
||||||
|
@BookID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.Books SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE BookID = @BookID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Chapter_ListByBook
|
||||||
|
@BookID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
|
||||||
|
c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
|
||||||
|
c.CreatedDate, c.UpdatedDate, c.IsArchived
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||||
|
WHERE c.BookID = @BookID AND c.IsArchived = 0
|
||||||
|
ORDER BY c.SortOrder, c.ChapterNumber, c.ChapterTitle;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Chapter_Get
|
||||||
|
@ChapterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
|
||||||
|
c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
|
||||||
|
c.CreatedDate, c.UpdatedDate, c.IsArchived
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||||
|
WHERE c.ChapterID = @ChapterID AND c.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Chapter_Save
|
||||||
|
@ChapterID int = NULL,
|
||||||
|
@BookID int,
|
||||||
|
@ChapterNumber decimal(9,2),
|
||||||
|
@ChapterTitle nvarchar(200),
|
||||||
|
@Summary nvarchar(max) = NULL,
|
||||||
|
@RevisionStatusID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @ChapterID IS NULL OR @ChapterID = 0
|
||||||
|
BEGIN
|
||||||
|
DECLARE @NextChapterOrder int = ISNULL((SELECT MAX(SortOrder) FROM dbo.Chapters WHERE BookID = @BookID), 0) + 10;
|
||||||
|
INSERT dbo.Chapters (BookID, ChapterNumber, ChapterTitle, Summary, SortOrder, RevisionStatusID)
|
||||||
|
VALUES (@BookID, @ChapterNumber, @ChapterTitle, @Summary, @NextChapterOrder, @RevisionStatusID);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int) AS ChapterID;
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE dbo.Chapters
|
||||||
|
SET ChapterNumber = @ChapterNumber,
|
||||||
|
ChapterTitle = @ChapterTitle,
|
||||||
|
Summary = @Summary,
|
||||||
|
RevisionStatusID = @RevisionStatusID,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE ChapterID = @ChapterID;
|
||||||
|
|
||||||
|
SELECT @ChapterID AS ChapterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Chapter_Archive
|
||||||
|
@ChapterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.Chapters SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE ChapterID = @ChapterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_ListByChapter
|
||||||
|
@ChapterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
|
||||||
|
s.PrimaryLocationID, s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime,
|
||||||
|
s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText,
|
||||||
|
s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
||||||
|
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
WHERE s.ChapterID = @ChapterID AND s.IsArchived = 0
|
||||||
|
ORDER BY s.SortOrder, s.SceneNumber, s.SceneTitle;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_Get
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
|
||||||
|
s.PrimaryLocationID, s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime,
|
||||||
|
s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText,
|
||||||
|
s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
||||||
|
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
WHERE s.SceneID = @SceneID AND s.IsArchived = 0;
|
||||||
|
|
||||||
|
SELECT ScenePurposeTypeID
|
||||||
|
FROM dbo.ScenePurposes
|
||||||
|
WHERE SceneID = @SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_Save
|
||||||
|
@SceneID int = NULL,
|
||||||
|
@ChapterID int,
|
||||||
|
@SceneNumber decimal(9,2),
|
||||||
|
@SceneTitle nvarchar(200),
|
||||||
|
@Summary nvarchar(max) = NULL,
|
||||||
|
@TimeModeID int,
|
||||||
|
@StartDateTime datetime2 = NULL,
|
||||||
|
@EndDateTime datetime2 = NULL,
|
||||||
|
@DurationAmount decimal(9,2) = NULL,
|
||||||
|
@DurationUnitID int = NULL,
|
||||||
|
@RelativeTimeText nvarchar(200) = NULL,
|
||||||
|
@TimeConfidenceID int,
|
||||||
|
@ScenePurposeNotes nvarchar(max) = NULL,
|
||||||
|
@SceneOutcomeNotes nvarchar(max) = NULL,
|
||||||
|
@RevisionStatusID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @SceneID IS NULL OR @SceneID = 0
|
||||||
|
BEGIN
|
||||||
|
DECLARE @NextSceneOrder int = ISNULL((SELECT MAX(SortOrder) FROM dbo.Scenes WHERE ChapterID = @ChapterID), 0) + 10;
|
||||||
|
INSERT dbo.Scenes
|
||||||
|
(
|
||||||
|
ChapterID, SceneNumber, SceneTitle, Summary, SortOrder, TimeModeID, StartDateTime,
|
||||||
|
EndDateTime, DurationAmount, DurationUnitID, RelativeTimeText, TimeConfidenceID,
|
||||||
|
ScenePurposeNotes, SceneOutcomeNotes, RevisionStatusID
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@ChapterID, @SceneNumber, @SceneTitle, @Summary, @NextSceneOrder, @TimeModeID, @StartDateTime,
|
||||||
|
@EndDateTime, @DurationAmount, @DurationUnitID, @RelativeTimeText, @TimeConfidenceID,
|
||||||
|
@ScenePurposeNotes, @SceneOutcomeNotes, @RevisionStatusID
|
||||||
|
);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int) AS SceneID;
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE dbo.Scenes
|
||||||
|
SET SceneNumber = @SceneNumber,
|
||||||
|
SceneTitle = @SceneTitle,
|
||||||
|
Summary = @Summary,
|
||||||
|
TimeModeID = @TimeModeID,
|
||||||
|
StartDateTime = @StartDateTime,
|
||||||
|
EndDateTime = @EndDateTime,
|
||||||
|
DurationAmount = @DurationAmount,
|
||||||
|
DurationUnitID = @DurationUnitID,
|
||||||
|
RelativeTimeText = @RelativeTimeText,
|
||||||
|
TimeConfidenceID = @TimeConfidenceID,
|
||||||
|
ScenePurposeNotes = @ScenePurposeNotes,
|
||||||
|
SceneOutcomeNotes = @SceneOutcomeNotes,
|
||||||
|
RevisionStatusID = @RevisionStatusID,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneID = @SceneID;
|
||||||
|
|
||||||
|
SELECT @SceneID AS SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_SavePurposes
|
||||||
|
@SceneID int,
|
||||||
|
@PurposeIds nvarchar(max)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DELETE FROM dbo.ScenePurposes WHERE SceneID = @SceneID;
|
||||||
|
|
||||||
|
INSERT dbo.ScenePurposes (SceneID, ScenePurposeTypeID)
|
||||||
|
SELECT @SceneID, TRY_CAST(value AS int)
|
||||||
|
FROM STRING_SPLIT(ISNULL(@PurposeIds, N''), N',')
|
||||||
|
WHERE TRY_CAST(value AS int) IS NOT NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_Archive
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.Scenes SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE SceneID = @SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_Move
|
||||||
|
@SceneID int,
|
||||||
|
@Direction nvarchar(10)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @ChapterID int;
|
||||||
|
DECLARE @CurrentSortOrder int;
|
||||||
|
DECLARE @SwapSceneID int;
|
||||||
|
DECLARE @SwapSortOrder int;
|
||||||
|
|
||||||
|
SELECT @ChapterID = ChapterID, @CurrentSortOrder = SortOrder
|
||||||
|
FROM dbo.Scenes
|
||||||
|
WHERE SceneID = @SceneID AND IsArchived = 0;
|
||||||
|
|
||||||
|
IF @ChapterID IS NULL RETURN;
|
||||||
|
|
||||||
|
IF @Direction = N'Up'
|
||||||
|
BEGIN
|
||||||
|
SELECT TOP (1) @SwapSceneID = SceneID, @SwapSortOrder = SortOrder
|
||||||
|
FROM dbo.Scenes
|
||||||
|
WHERE ChapterID = @ChapterID AND IsArchived = 0 AND SortOrder < @CurrentSortOrder
|
||||||
|
ORDER BY SortOrder DESC;
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
SELECT TOP (1) @SwapSceneID = SceneID, @SwapSortOrder = SortOrder
|
||||||
|
FROM dbo.Scenes
|
||||||
|
WHERE ChapterID = @ChapterID AND IsArchived = 0 AND SortOrder > @CurrentSortOrder
|
||||||
|
ORDER BY SortOrder ASC;
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF @SwapSceneID IS NULL RETURN;
|
||||||
|
|
||||||
|
UPDATE dbo.Scenes SET SortOrder = @SwapSortOrder, UpdatedDate = SYSUTCDATETIME() WHERE SceneID = @SceneID;
|
||||||
|
UPDATE dbo.Scenes SET SortOrder = @CurrentSortOrder, UpdatedDate = SYSUTCDATETIME() WHERE SceneID = @SwapSceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Lookup_All
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT RevisionStatusID, StatusName, SortOrder FROM dbo.RevisionStatuses ORDER BY SortOrder;
|
||||||
|
SELECT TimeModeID, TimeModeName, SortOrder FROM dbo.TimeModes ORDER BY SortOrder;
|
||||||
|
SELECT DurationUnitID, DurationUnitName, SortOrder FROM dbo.DurationUnits ORDER BY SortOrder;
|
||||||
|
SELECT TimeConfidenceID, TimeConfidenceName, SortOrder FROM dbo.TimeConfidences ORDER BY SortOrder;
|
||||||
|
SELECT ScenePurposeTypeID, PurposeName, SortOrder FROM dbo.ScenePurposeTypes WHERE IsArchived = 0 ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Timeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Projects
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0;
|
||||||
|
|
||||||
|
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Books
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0 AND (@BookID IS NULL OR BookID = @BookID)
|
||||||
|
ORDER BY SortOrder, BookNumber;
|
||||||
|
|
||||||
|
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
|
||||||
|
c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
|
||||||
|
c.CreatedDate, c.UpdatedDate, c.IsArchived
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, c.ChapterNumber;
|
||||||
|
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
|
||||||
|
s.PrimaryLocationID, s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime,
|
||||||
|
s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText,
|
||||||
|
s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
||||||
|
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, s.SceneNumber;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
258
PlotLine/Sql/002_Phase1B_WorkspaceMetrics.sql
Normal file
258
PlotLine/Sql/002_Phase1B_WorkspaceMetrics.sql
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.SceneMetricTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.SceneMetricTypes
|
||||||
|
(
|
||||||
|
MetricTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneMetricTypes PRIMARY KEY,
|
||||||
|
ProjectID int NULL,
|
||||||
|
MetricName nvarchar(100) NOT NULL,
|
||||||
|
Description nvarchar(500) NULL,
|
||||||
|
MinValue int NOT NULL CONSTRAINT DF_SceneMetricTypes_MinValue DEFAULT 1,
|
||||||
|
MaxValue int NOT NULL CONSTRAINT DF_SceneMetricTypes_MaxValue DEFAULT 10,
|
||||||
|
DefaultValue int NOT NULL CONSTRAINT DF_SceneMetricTypes_DefaultValue DEFAULT 5,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_SceneMetricTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT FK_SceneMetricTypes_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT CK_SceneMetricTypes_Range CHECK (MinValue < MaxValue AND DefaultValue BETWEEN MinValue AND MaxValue)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.SceneMetricValues', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.SceneMetricValues
|
||||||
|
(
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
MetricTypeID int NOT NULL,
|
||||||
|
Value int NOT NULL,
|
||||||
|
Notes nvarchar(500) NULL,
|
||||||
|
CONSTRAINT PK_SceneMetricValues PRIMARY KEY (SceneID, MetricTypeID),
|
||||||
|
CONSTRAINT FK_SceneMetricValues_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_SceneMetricValues_SceneMetricTypes FOREIGN KEY (MetricTypeID) REFERENCES dbo.SceneMetricTypes(MetricTypeID),
|
||||||
|
CONSTRAINT CK_SceneMetricValues_Value CHECK (Value BETWEEN 1 AND 10)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneMetricTypes_ProjectID_SortOrder' AND object_id = OBJECT_ID(N'dbo.SceneMetricTypes'))
|
||||||
|
CREATE INDEX IX_SceneMetricTypes_ProjectID_SortOrder ON dbo.SceneMetricTypes(ProjectID, IsActive, SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.SceneMetricTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Overall Intensity', N'General dramatic intensity for the scene.', 1, 10, 5, 10),
|
||||||
|
(N'Tension', N'Pressure, suspense, uncertainty, or conflict.', 1, 10, 5, 20),
|
||||||
|
(N'Emotional Weight', N'Emotional load carried by the scene.', 1, 10, 5, 30),
|
||||||
|
(N'Action', N'Physical action, movement, or event energy.', 1, 10, 3, 40),
|
||||||
|
(N'Darkness', N'Darkness, bleakness, fear, or threat.', 1, 10, 3, 50),
|
||||||
|
(N'Romance', N'Romantic focus or intimacy.', 1, 10, 1, 60),
|
||||||
|
(N'Sexual Charge', N'Sexual tension or sensual charge.', 1, 10, 1, 70),
|
||||||
|
(N'Violence', N'Violent action, injury, or threat.', 1, 10, 1, 80),
|
||||||
|
(N'Hope / Lightness', N'Hope, relief, warmth, or tonal lightness.', 1, 10, 4, 90),
|
||||||
|
(N'Comedy', N'Humour or comic relief.', 1, 10, 1, 100),
|
||||||
|
(N'Mystery', N'Questions, clue energy, and mystery pressure.', 1, 10, 5, 110)
|
||||||
|
) AS source (MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder)
|
||||||
|
ON target.ProjectID IS NULL AND target.MetricName = source.MetricName
|
||||||
|
WHEN NOT MATCHED THEN
|
||||||
|
INSERT (ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive)
|
||||||
|
VALUES (NULL, source.MetricName, source.Description, source.MinValue, source.MaxValue, source.DefaultValue, source.SortOrder, 1);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneMetricType_List
|
||||||
|
@ProjectID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT MetricTypeID, ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive
|
||||||
|
FROM dbo.SceneMetricTypes
|
||||||
|
WHERE IsActive = 1 AND (ProjectID IS NULL OR ProjectID = @ProjectID)
|
||||||
|
ORDER BY SortOrder, MetricName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneMetric_EnsureDefaults
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @ProjectID int;
|
||||||
|
|
||||||
|
SELECT @ProjectID = b.ProjectID
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE s.SceneID = @SceneID;
|
||||||
|
|
||||||
|
INSERT dbo.SceneMetricValues (SceneID, MetricTypeID, Value, Notes)
|
||||||
|
SELECT @SceneID, mt.MetricTypeID, mt.DefaultValue, NULL
|
||||||
|
FROM dbo.SceneMetricTypes mt
|
||||||
|
WHERE mt.IsActive = 1
|
||||||
|
AND (mt.ProjectID IS NULL OR mt.ProjectID = @ProjectID)
|
||||||
|
AND NOT EXISTS
|
||||||
|
(
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.SceneMetricValues smv
|
||||||
|
WHERE smv.SceneID = @SceneID AND smv.MetricTypeID = mt.MetricTypeID
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneMetric_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
EXEC dbo.SceneMetric_EnsureDefaults @SceneID = @SceneID;
|
||||||
|
|
||||||
|
SELECT mt.MetricTypeID, mt.ProjectID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
|
||||||
|
mt.DefaultValue, mt.SortOrder, mt.IsActive, smv.SceneID, smv.Value, smv.Notes
|
||||||
|
FROM dbo.SceneMetricTypes mt
|
||||||
|
INNER JOIN dbo.SceneMetricValues smv ON smv.MetricTypeID = mt.MetricTypeID AND smv.SceneID = @SceneID
|
||||||
|
WHERE mt.IsActive = 1
|
||||||
|
ORDER BY mt.SortOrder, mt.MetricName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneMetric_SaveValue
|
||||||
|
@SceneID int,
|
||||||
|
@MetricTypeID int,
|
||||||
|
@Value int,
|
||||||
|
@Notes nvarchar(500) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @MinValue int;
|
||||||
|
DECLARE @MaxValue int;
|
||||||
|
|
||||||
|
SELECT @MinValue = MinValue, @MaxValue = MaxValue
|
||||||
|
FROM dbo.SceneMetricTypes
|
||||||
|
WHERE MetricTypeID = @MetricTypeID AND IsActive = 1;
|
||||||
|
|
||||||
|
IF @MinValue IS NULL
|
||||||
|
THROW 50001, 'Metric type does not exist.', 1;
|
||||||
|
|
||||||
|
IF @Value < @MinValue OR @Value > @MaxValue
|
||||||
|
THROW 50002, 'Metric value is outside the configured range.', 1;
|
||||||
|
|
||||||
|
MERGE dbo.SceneMetricValues AS target
|
||||||
|
USING (SELECT @SceneID AS SceneID, @MetricTypeID AS MetricTypeID) AS source
|
||||||
|
ON target.SceneID = source.SceneID AND target.MetricTypeID = source.MetricTypeID
|
||||||
|
WHEN MATCHED THEN
|
||||||
|
UPDATE SET Value = @Value, Notes = @Notes
|
||||||
|
WHEN NOT MATCHED THEN
|
||||||
|
INSERT (SceneID, MetricTypeID, Value, Notes)
|
||||||
|
VALUES (@SceneID, @MetricTypeID, @Value, @Notes);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_Get
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
EXEC dbo.SceneMetric_EnsureDefaults @SceneID = @SceneID;
|
||||||
|
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
|
||||||
|
s.PrimaryLocationID, s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime,
|
||||||
|
s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText,
|
||||||
|
s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
||||||
|
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
WHERE s.SceneID = @SceneID AND s.IsArchived = 0;
|
||||||
|
|
||||||
|
SELECT sp.ScenePurposeTypeID
|
||||||
|
FROM dbo.ScenePurposes sp
|
||||||
|
WHERE sp.SceneID = @SceneID;
|
||||||
|
|
||||||
|
SELECT mt.MetricTypeID, mt.ProjectID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
|
||||||
|
mt.DefaultValue, mt.SortOrder, mt.IsActive, smv.SceneID, smv.Value, smv.Notes
|
||||||
|
FROM dbo.SceneMetricTypes mt
|
||||||
|
INNER JOIN dbo.SceneMetricValues smv ON smv.MetricTypeID = mt.MetricTypeID AND smv.SceneID = @SceneID
|
||||||
|
WHERE mt.IsActive = 1
|
||||||
|
ORDER BY mt.SortOrder, mt.MetricName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Timeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Projects
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0;
|
||||||
|
|
||||||
|
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Books
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0 AND (@BookID IS NULL OR BookID = @BookID)
|
||||||
|
ORDER BY SortOrder, BookNumber;
|
||||||
|
|
||||||
|
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
|
||||||
|
c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
|
||||||
|
c.CreatedDate, c.UpdatedDate, c.IsArchived
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, c.ChapterNumber;
|
||||||
|
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
|
||||||
|
s.PrimaryLocationID, s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime,
|
||||||
|
s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText,
|
||||||
|
s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
||||||
|
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, s.SceneNumber;
|
||||||
|
|
||||||
|
SELECT sp.SceneID, spt.ScenePurposeTypeID, spt.PurposeName, spt.SortOrder
|
||||||
|
FROM dbo.ScenePurposes sp
|
||||||
|
INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = sp.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY sp.SceneID, spt.SortOrder;
|
||||||
|
|
||||||
|
SELECT MetricTypeID, ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive
|
||||||
|
FROM dbo.SceneMetricTypes
|
||||||
|
WHERE IsActive = 1 AND (ProjectID IS NULL OR ProjectID = @ProjectID)
|
||||||
|
ORDER BY SortOrder, MetricName;
|
||||||
|
|
||||||
|
SELECT s.SceneID, mt.MetricTypeID, mt.MetricName, COALESCE(smv.Value, mt.DefaultValue) AS Value, smv.Notes
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
CROSS JOIN dbo.SceneMetricTypes mt
|
||||||
|
LEFT JOIN dbo.SceneMetricValues smv ON smv.SceneID = s.SceneID AND smv.MetricTypeID = mt.MetricTypeID
|
||||||
|
WHERE b.ProjectID = @ProjectID
|
||||||
|
AND b.IsArchived = 0
|
||||||
|
AND c.IsArchived = 0
|
||||||
|
AND s.IsArchived = 0
|
||||||
|
AND mt.IsActive = 1
|
||||||
|
AND (mt.ProjectID IS NULL OR mt.ProjectID = @ProjectID)
|
||||||
|
AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, mt.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
27
PlotLine/Sql/003_FixScenePurposeSave.sql
Normal file
27
PlotLine/Sql/003_FixScenePurposeSave.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_SavePurposes
|
||||||
|
@SceneID int,
|
||||||
|
@PurposeIds nvarchar(max)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DELETE FROM dbo.ScenePurposes WHERE SceneID = @SceneID;
|
||||||
|
|
||||||
|
INSERT dbo.ScenePurposes (SceneID, ScenePurposeTypeID)
|
||||||
|
SELECT DISTINCT @SceneID, parsed.ScenePurposeTypeID
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT TRY_CAST(value AS int) AS ScenePurposeTypeID
|
||||||
|
FROM STRING_SPLIT(ISNULL(@PurposeIds, N''), N',')
|
||||||
|
) parsed
|
||||||
|
INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = parsed.ScenePurposeTypeID
|
||||||
|
WHERE parsed.ScenePurposeTypeID IS NOT NULL
|
||||||
|
AND parsed.ScenePurposeTypeID > 0
|
||||||
|
AND spt.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
603
PlotLine/Sql/004_Phase1C_PlotThreads.sql
Normal file
603
PlotLine/Sql/004_Phase1C_PlotThreads.sql
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.PlotLineTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.PlotLineTypes
|
||||||
|
(
|
||||||
|
PlotLineTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_PlotLineTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_PlotLineTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_PlotLineTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.PlotLines', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.PlotLines
|
||||||
|
(
|
||||||
|
PlotLineID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_PlotLines PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
BookID int NULL,
|
||||||
|
PlotLineName nvarchar(200) NOT NULL,
|
||||||
|
PlotLineTypeID int NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
ParentPlotLineID int NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
Colour nvarchar(20) NOT NULL CONSTRAINT DF_PlotLines_Colour DEFAULT N'#2f6f63',
|
||||||
|
IsMainPlot bit NOT NULL CONSTRAINT DF_PlotLines_IsMainPlot DEFAULT 0,
|
||||||
|
IsVisibleOnTimeline bit NOT NULL CONSTRAINT DF_PlotLines_IsVisibleOnTimeline DEFAULT 1,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_PlotLines_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_PlotLines_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_PlotLines_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_PlotLines_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_PlotLines_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID),
|
||||||
|
CONSTRAINT FK_PlotLines_PlotLineTypes FOREIGN KEY (PlotLineTypeID) REFERENCES dbo.PlotLineTypes(PlotLineTypeID),
|
||||||
|
CONSTRAINT FK_PlotLines_ParentPlotLines FOREIGN KEY (ParentPlotLineID) REFERENCES dbo.PlotLines(PlotLineID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ThreadTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ThreadTypes
|
||||||
|
(
|
||||||
|
ThreadTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ThreadTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_ThreadTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_ThreadTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ThreadStatuses', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ThreadStatuses
|
||||||
|
(
|
||||||
|
ThreadStatusID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ThreadStatuses PRIMARY KEY,
|
||||||
|
StatusName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsOpenStatus bit NOT NULL,
|
||||||
|
IsResolvedStatus bit NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_ThreadStatuses_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_ThreadStatuses_StatusName UNIQUE (StatusName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.PlotThreads', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.PlotThreads
|
||||||
|
(
|
||||||
|
PlotThreadID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_PlotThreads PRIMARY KEY,
|
||||||
|
PlotLineID int NOT NULL,
|
||||||
|
ThreadTitle nvarchar(200) NOT NULL,
|
||||||
|
ThreadTypeID int NOT NULL,
|
||||||
|
ThreadStatusID int NOT NULL,
|
||||||
|
Importance int NOT NULL CONSTRAINT DF_PlotThreads_Importance DEFAULT 5,
|
||||||
|
Summary nvarchar(max) NULL,
|
||||||
|
IntroducedSceneID int NULL,
|
||||||
|
PlannedResolutionSceneID int NULL,
|
||||||
|
ActualResolutionSceneID int NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_PlotThreads_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_PlotThreads_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_PlotThreads_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_PlotThreads_PlotLines FOREIGN KEY (PlotLineID) REFERENCES dbo.PlotLines(PlotLineID),
|
||||||
|
CONSTRAINT FK_PlotThreads_ThreadTypes FOREIGN KEY (ThreadTypeID) REFERENCES dbo.ThreadTypes(ThreadTypeID),
|
||||||
|
CONSTRAINT FK_PlotThreads_ThreadStatuses FOREIGN KEY (ThreadStatusID) REFERENCES dbo.ThreadStatuses(ThreadStatusID),
|
||||||
|
CONSTRAINT FK_PlotThreads_IntroducedScene FOREIGN KEY (IntroducedSceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_PlotThreads_PlannedResolutionScene FOREIGN KEY (PlannedResolutionSceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_PlotThreads_ActualResolutionScene FOREIGN KEY (ActualResolutionSceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT CK_PlotThreads_Importance CHECK (Importance BETWEEN 1 AND 10)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ThreadEventTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ThreadEventTypes
|
||||||
|
(
|
||||||
|
ThreadEventTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ThreadEventTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
MarkerText nvarchar(20) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_ThreadEventTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_ThreadEventTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ThreadEvents', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ThreadEvents
|
||||||
|
(
|
||||||
|
ThreadEventID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ThreadEvents PRIMARY KEY,
|
||||||
|
PlotThreadID int NOT NULL,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
EventTypeID int NOT NULL,
|
||||||
|
EventTitle nvarchar(200) NOT NULL,
|
||||||
|
EventDescription nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_ThreadEvents_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_ThreadEvents_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_ThreadEvents_PlotThreads FOREIGN KEY (PlotThreadID) REFERENCES dbo.PlotThreads(PlotThreadID),
|
||||||
|
CONSTRAINT FK_ThreadEvents_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_ThreadEvents_ThreadEventTypes FOREIGN KEY (EventTypeID) REFERENCES dbo.ThreadEventTypes(ThreadEventTypeID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_PlotLines_ProjectID_SortOrder' AND object_id = OBJECT_ID(N'dbo.PlotLines'))
|
||||||
|
CREATE INDEX IX_PlotLines_ProjectID_SortOrder ON dbo.PlotLines(ProjectID, IsArchived, IsVisibleOnTimeline, SortOrder);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_PlotThreads_PlotLineID' AND object_id = OBJECT_ID(N'dbo.PlotThreads'))
|
||||||
|
CREATE INDEX IX_PlotThreads_PlotLineID ON dbo.PlotThreads(PlotLineID, IsArchived, ThreadStatusID);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ThreadEvents_SceneID' AND object_id = OBJECT_ID(N'dbo.ThreadEvents'))
|
||||||
|
CREATE INDEX IX_ThreadEvents_SceneID ON dbo.ThreadEvents(SceneID, PlotThreadID, EventTypeID);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.PlotLineTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Main Plot', 10), (N'Side Plot', 20), (N'Mystery', 30), (N'Emotional Arc', 40),
|
||||||
|
(N'Relationship Arc', 50), (N'Character Arc', 60), (N'Object Trail', 70),
|
||||||
|
(N'Investigation', 80), (N'Location Plot', 90), (N'Theme', 100)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder, IsActive) VALUES (source.TypeName, source.SortOrder, 1);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.ThreadTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Question', 10), (N'Clue', 20), (N'Mystery', 30), (N'Suspicion', 40), (N'Promise', 50),
|
||||||
|
(N'Threat', 60), (N'Emotional Issue', 70), (N'Relationship Issue', 80), (N'Secret', 90),
|
||||||
|
(N'Continuity Item', 100), (N'Payoff Setup', 110)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder, IsActive) VALUES (source.TypeName, source.SortOrder, 1);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.ThreadStatuses AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Planned', 10, 1, 0), (N'Introduced', 20, 1, 0), (N'Developing', 30, 1, 0),
|
||||||
|
(N'Dormant', 40, 1, 0), (N'Ready For Payoff', 50, 1, 0),
|
||||||
|
(N'Resolved', 60, 0, 1), (N'Abandoned', 70, 0, 1)
|
||||||
|
) AS source (StatusName, SortOrder, IsOpenStatus, IsResolvedStatus)
|
||||||
|
ON target.StatusName = source.StatusName
|
||||||
|
WHEN NOT MATCHED THEN
|
||||||
|
INSERT (StatusName, SortOrder, IsOpenStatus, IsResolvedStatus, IsActive)
|
||||||
|
VALUES (source.StatusName, source.SortOrder, source.IsOpenStatus, source.IsResolvedStatus, 1);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.ThreadEventTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Introduced', N'I', 10), (N'Mentioned', N'M', 20), (N'Developed', N'D', 30),
|
||||||
|
(N'Complicated', N'C', 40), (N'Clue Planted', N'clue', 50), (N'Question Raised', N'?', 60),
|
||||||
|
(N'Question Answered', N'A', 70), (N'False Lead', N'F', 80), (N'Revealed', N'Rv', 90),
|
||||||
|
(N'Payoff', N'P', 100), (N'Resolved', NCHAR(10003), 110), (N'Contradicted', N'!', 120),
|
||||||
|
(N'Retconned', N'Rt', 130), (N'Note', N'N', 140)
|
||||||
|
) AS source (TypeName, MarkerText, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN
|
||||||
|
INSERT (TypeName, MarkerText, SortOrder, IsActive)
|
||||||
|
VALUES (source.TypeName, source.MarkerText, source.SortOrder, 1);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotLookup_All
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT PlotLineTypeID, TypeName, SortOrder, IsActive FROM dbo.PlotLineTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT ThreadTypeID, TypeName, SortOrder, IsActive FROM dbo.ThreadTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT ThreadStatusID, StatusName, SortOrder, IsOpenStatus, IsResolvedStatus, IsActive FROM dbo.ThreadStatuses WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT ThreadEventTypeID, TypeName, MarkerText, SortOrder, IsActive FROM dbo.ThreadEventTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotLine_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
|
||||||
|
plt.TypeName AS PlotLineTypeName, pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
|
||||||
|
pl.SortOrder, pl.Colour, pl.IsMainPlot, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
|
||||||
|
FROM dbo.PlotLines pl
|
||||||
|
INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
|
||||||
|
LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
|
||||||
|
WHERE pl.ProjectID = @ProjectID AND pl.IsArchived = 0
|
||||||
|
ORDER BY pl.SortOrder, pl.PlotLineName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotLine_Get
|
||||||
|
@PlotLineID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
|
||||||
|
plt.TypeName AS PlotLineTypeName, pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
|
||||||
|
pl.SortOrder, pl.Colour, pl.IsMainPlot, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
|
||||||
|
FROM dbo.PlotLines pl
|
||||||
|
INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
|
||||||
|
LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
|
||||||
|
WHERE pl.PlotLineID = @PlotLineID AND pl.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotLine_Save
|
||||||
|
@PlotLineID int = NULL,
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@PlotLineName nvarchar(200),
|
||||||
|
@PlotLineTypeID int,
|
||||||
|
@Description nvarchar(max) = NULL,
|
||||||
|
@ParentPlotLineID int = NULL,
|
||||||
|
@SortOrder int = NULL,
|
||||||
|
@Colour nvarchar(20),
|
||||||
|
@IsMainPlot bit,
|
||||||
|
@IsVisibleOnTimeline bit
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @PlotLineID IS NULL OR @PlotLineID = 0
|
||||||
|
BEGIN
|
||||||
|
DECLARE @NextSortOrder int = COALESCE(@SortOrder, ISNULL((SELECT MAX(SortOrder) FROM dbo.PlotLines WHERE ProjectID = @ProjectID), 0) + 10);
|
||||||
|
INSERT dbo.PlotLines (ProjectID, BookID, PlotLineName, PlotLineTypeID, Description, ParentPlotLineID, SortOrder, Colour, IsMainPlot, IsVisibleOnTimeline)
|
||||||
|
VALUES (@ProjectID, @BookID, @PlotLineName, @PlotLineTypeID, @Description, @ParentPlotLineID, @NextSortOrder, @Colour, @IsMainPlot, @IsVisibleOnTimeline);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE dbo.PlotLines
|
||||||
|
SET BookID = @BookID,
|
||||||
|
PlotLineName = @PlotLineName,
|
||||||
|
PlotLineTypeID = @PlotLineTypeID,
|
||||||
|
Description = @Description,
|
||||||
|
ParentPlotLineID = @ParentPlotLineID,
|
||||||
|
SortOrder = COALESCE(@SortOrder, SortOrder),
|
||||||
|
Colour = @Colour,
|
||||||
|
IsMainPlot = @IsMainPlot,
|
||||||
|
IsVisibleOnTimeline = @IsVisibleOnTimeline,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE PlotLineID = @PlotLineID;
|
||||||
|
|
||||||
|
SELECT @PlotLineID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotLine_Archive
|
||||||
|
@PlotLineID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.PlotLines SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE PlotLineID = @PlotLineID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotThread_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT pt.PlotThreadID, pt.PlotLineID, pl.ProjectID, pl.PlotLineName, pt.ThreadTitle,
|
||||||
|
pt.ThreadTypeID, tt.TypeName AS ThreadTypeName, pt.ThreadStatusID, ts.StatusName AS ThreadStatusName,
|
||||||
|
pt.Importance, pt.Summary, pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
|
||||||
|
pt.CreatedDate, pt.UpdatedDate, pt.IsArchived
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
|
||||||
|
WHERE pl.ProjectID = @ProjectID AND pl.IsArchived = 0 AND pt.IsArchived = 0
|
||||||
|
ORDER BY pl.SortOrder, pt.Importance DESC, pt.ThreadTitle;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotThread_ListByPlotLine
|
||||||
|
@PlotLineID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT pt.PlotThreadID, pt.PlotLineID, pl.ProjectID, pl.PlotLineName, pt.ThreadTitle,
|
||||||
|
pt.ThreadTypeID, tt.TypeName AS ThreadTypeName, pt.ThreadStatusID, ts.StatusName AS ThreadStatusName,
|
||||||
|
pt.Importance, pt.Summary, pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
|
||||||
|
pt.CreatedDate, pt.UpdatedDate, pt.IsArchived
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
|
||||||
|
WHERE pt.PlotLineID = @PlotLineID AND pt.IsArchived = 0
|
||||||
|
ORDER BY pt.Importance DESC, pt.ThreadTitle;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotThread_Get
|
||||||
|
@PlotThreadID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT pt.PlotThreadID, pt.PlotLineID, pl.ProjectID, pl.PlotLineName, pt.ThreadTitle,
|
||||||
|
pt.ThreadTypeID, tt.TypeName AS ThreadTypeName, pt.ThreadStatusID, ts.StatusName AS ThreadStatusName,
|
||||||
|
pt.Importance, pt.Summary, pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
|
||||||
|
pt.CreatedDate, pt.UpdatedDate, pt.IsArchived
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
|
||||||
|
WHERE pt.PlotThreadID = @PlotThreadID AND pt.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotThread_Save
|
||||||
|
@PlotThreadID int = NULL,
|
||||||
|
@PlotLineID int,
|
||||||
|
@ThreadTitle nvarchar(200),
|
||||||
|
@ThreadTypeID int,
|
||||||
|
@ThreadStatusID int,
|
||||||
|
@Importance int,
|
||||||
|
@Summary nvarchar(max) = NULL,
|
||||||
|
@IntroducedSceneID int = NULL,
|
||||||
|
@PlannedResolutionSceneID int = NULL,
|
||||||
|
@ActualResolutionSceneID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @PlotThreadID IS NULL OR @PlotThreadID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.PlotThreads (PlotLineID, ThreadTitle, ThreadTypeID, ThreadStatusID, Importance, Summary, IntroducedSceneID, PlannedResolutionSceneID, ActualResolutionSceneID)
|
||||||
|
VALUES (@PlotLineID, @ThreadTitle, @ThreadTypeID, @ThreadStatusID, @Importance, @Summary, @IntroducedSceneID, @PlannedResolutionSceneID, @ActualResolutionSceneID);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE dbo.PlotThreads
|
||||||
|
SET PlotLineID = @PlotLineID,
|
||||||
|
ThreadTitle = @ThreadTitle,
|
||||||
|
ThreadTypeID = @ThreadTypeID,
|
||||||
|
ThreadStatusID = @ThreadStatusID,
|
||||||
|
Importance = @Importance,
|
||||||
|
Summary = @Summary,
|
||||||
|
IntroducedSceneID = @IntroducedSceneID,
|
||||||
|
PlannedResolutionSceneID = @PlannedResolutionSceneID,
|
||||||
|
ActualResolutionSceneID = @ActualResolutionSceneID,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE PlotThreadID = @PlotThreadID;
|
||||||
|
|
||||||
|
SELECT @PlotThreadID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.PlotThread_Archive
|
||||||
|
@PlotThreadID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.PlotThreads SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE PlotThreadID = @PlotThreadID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ThreadEvent_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pt.PlotLineID, pl.PlotLineName, pl.Colour,
|
||||||
|
te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText, te.EventTitle,
|
||||||
|
te.EventDescription, te.CreatedDate, te.UpdatedDate
|
||||||
|
FROM dbo.ThreadEvents te
|
||||||
|
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
WHERE te.SceneID = @SceneID AND pt.IsArchived = 0 AND pl.IsArchived = 0
|
||||||
|
ORDER BY pl.SortOrder, pt.Importance DESC, te.CreatedDate;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ThreadEvent_ListByPlotThread
|
||||||
|
@PlotThreadID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pt.PlotLineID, pl.PlotLineName, pl.Colour,
|
||||||
|
te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText, te.EventTitle,
|
||||||
|
te.EventDescription, te.CreatedDate, te.UpdatedDate
|
||||||
|
FROM dbo.ThreadEvents te
|
||||||
|
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
WHERE te.PlotThreadID = @PlotThreadID
|
||||||
|
ORDER BY te.CreatedDate;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ThreadEvent_Save
|
||||||
|
@ThreadEventID int = NULL,
|
||||||
|
@PlotThreadID int,
|
||||||
|
@SceneID int,
|
||||||
|
@EventTypeID int,
|
||||||
|
@EventTitle nvarchar(200),
|
||||||
|
@EventDescription nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF @ThreadEventID IS NULL OR @ThreadEventID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.ThreadEvents (PlotThreadID, SceneID, EventTypeID, EventTitle, EventDescription)
|
||||||
|
VALUES (@PlotThreadID, @SceneID, @EventTypeID, @EventTitle, @EventDescription);
|
||||||
|
SET @ThreadEventID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.ThreadEvents
|
||||||
|
SET PlotThreadID = @PlotThreadID,
|
||||||
|
SceneID = @SceneID,
|
||||||
|
EventTypeID = @EventTypeID,
|
||||||
|
EventTitle = @EventTitle,
|
||||||
|
EventDescription = @EventDescription,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE ThreadEventID = @ThreadEventID;
|
||||||
|
END;
|
||||||
|
|
||||||
|
DECLARE @EventTypeName nvarchar(100);
|
||||||
|
SELECT @EventTypeName = TypeName FROM dbo.ThreadEventTypes WHERE ThreadEventTypeID = @EventTypeID;
|
||||||
|
|
||||||
|
IF @EventTypeName = N'Introduced'
|
||||||
|
BEGIN
|
||||||
|
UPDATE pt
|
||||||
|
SET IntroducedSceneID = COALESCE(IntroducedSceneID, @SceneID),
|
||||||
|
ThreadStatusID = CASE WHEN ts.StatusName = N'Planned' THEN introduced.ThreadStatusID ELSE pt.ThreadStatusID END,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
|
||||||
|
CROSS APPLY (SELECT ThreadStatusID FROM dbo.ThreadStatuses WHERE StatusName = N'Introduced') introduced
|
||||||
|
WHERE pt.PlotThreadID = @PlotThreadID;
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF @EventTypeName IN (N'Resolved', N'Payoff')
|
||||||
|
BEGIN
|
||||||
|
UPDATE pt
|
||||||
|
SET ActualResolutionSceneID = COALESCE(ActualResolutionSceneID, @SceneID),
|
||||||
|
ThreadStatusID = CASE WHEN @EventTypeName = N'Resolved' THEN resolved.ThreadStatusID ELSE pt.ThreadStatusID END,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
CROSS APPLY (SELECT ThreadStatusID FROM dbo.ThreadStatuses WHERE StatusName = N'Resolved') resolved
|
||||||
|
WHERE pt.PlotThreadID = @PlotThreadID;
|
||||||
|
END;
|
||||||
|
|
||||||
|
SELECT @ThreadEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ThreadEvent_Delete
|
||||||
|
@ThreadEventID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DELETE FROM dbo.ThreadEvents WHERE ThreadEventID = @ThreadEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneOption_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT s.SceneID, b.BookTitle, c.ChapterNumber, c.ChapterTitle, s.SceneNumber, s.SceneTitle
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Timeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Projects
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0;
|
||||||
|
|
||||||
|
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Books
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0 AND (@BookID IS NULL OR BookID = @BookID)
|
||||||
|
ORDER BY SortOrder, BookNumber;
|
||||||
|
|
||||||
|
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
|
||||||
|
c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
|
||||||
|
c.CreatedDate, c.UpdatedDate, c.IsArchived
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, c.ChapterNumber;
|
||||||
|
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
|
||||||
|
s.PrimaryLocationID, s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime,
|
||||||
|
s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText,
|
||||||
|
s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
||||||
|
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, s.SceneNumber;
|
||||||
|
|
||||||
|
SELECT sp.SceneID, spt.ScenePurposeTypeID, spt.PurposeName, spt.SortOrder
|
||||||
|
FROM dbo.ScenePurposes sp
|
||||||
|
INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = sp.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY sp.SceneID, spt.SortOrder;
|
||||||
|
|
||||||
|
SELECT MetricTypeID, ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive
|
||||||
|
FROM dbo.SceneMetricTypes
|
||||||
|
WHERE IsActive = 1 AND (ProjectID IS NULL OR ProjectID = @ProjectID)
|
||||||
|
ORDER BY SortOrder, MetricName;
|
||||||
|
|
||||||
|
SELECT s.SceneID, mt.MetricTypeID, mt.MetricName, COALESCE(smv.Value, mt.DefaultValue) AS Value, smv.Notes
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
CROSS JOIN dbo.SceneMetricTypes mt
|
||||||
|
LEFT JOIN dbo.SceneMetricValues smv ON smv.SceneID = s.SceneID AND smv.MetricTypeID = mt.MetricTypeID
|
||||||
|
WHERE b.ProjectID = @ProjectID
|
||||||
|
AND b.IsArchived = 0
|
||||||
|
AND c.IsArchived = 0
|
||||||
|
AND s.IsArchived = 0
|
||||||
|
AND mt.IsActive = 1
|
||||||
|
AND (mt.ProjectID IS NULL OR mt.ProjectID = @ProjectID)
|
||||||
|
AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, mt.SortOrder;
|
||||||
|
|
||||||
|
SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
|
||||||
|
plt.TypeName AS PlotLineTypeName, pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
|
||||||
|
pl.SortOrder, pl.Colour, pl.IsMainPlot, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
|
||||||
|
FROM dbo.PlotLines pl
|
||||||
|
INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
|
||||||
|
LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
|
||||||
|
WHERE pl.ProjectID = @ProjectID
|
||||||
|
AND pl.IsArchived = 0
|
||||||
|
AND pl.IsVisibleOnTimeline = 1
|
||||||
|
AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID)
|
||||||
|
ORDER BY pl.SortOrder, pl.PlotLineName;
|
||||||
|
|
||||||
|
SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pt.PlotLineID, pl.PlotLineName, pl.Colour,
|
||||||
|
te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText, te.EventTitle,
|
||||||
|
te.EventDescription, te.CreatedDate, te.UpdatedDate
|
||||||
|
FROM dbo.ThreadEvents te
|
||||||
|
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = te.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID
|
||||||
|
AND b.IsArchived = 0
|
||||||
|
AND c.IsArchived = 0
|
||||||
|
AND s.IsArchived = 0
|
||||||
|
AND pl.IsArchived = 0
|
||||||
|
AND pl.IsVisibleOnTimeline = 1
|
||||||
|
AND pt.IsArchived = 0
|
||||||
|
AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY pl.SortOrder, b.SortOrder, c.SortOrder, s.SortOrder, te.CreatedDate;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
709
PlotLine/Sql/005_Phase1D_StoryAssets.sql
Normal file
709
PlotLine/Sql/005_Phase1D_StoryAssets.sql
Normal file
@ -0,0 +1,709 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetKinds', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetKinds
|
||||||
|
(
|
||||||
|
AssetKindID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetKinds PRIMARY KEY,
|
||||||
|
KindName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_AssetKinds_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_AssetKinds_KindName UNIQUE (KindName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetStates', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetStates
|
||||||
|
(
|
||||||
|
AssetStateID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetStates PRIMARY KEY,
|
||||||
|
ProjectID int NULL,
|
||||||
|
AssetKindID int NULL,
|
||||||
|
StateName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
IsResolvedState bit NOT NULL CONSTRAINT DF_AssetStates_IsResolvedState DEFAULT 0,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_AssetStates_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT FK_AssetStates_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_AssetStates_AssetKinds FOREIGN KEY (AssetKindID) REFERENCES dbo.AssetKinds(AssetKindID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.StoryAssets', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.StoryAssets
|
||||||
|
(
|
||||||
|
StoryAssetID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_StoryAssets PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
AssetName nvarchar(200) NOT NULL,
|
||||||
|
AssetKindID int NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
Importance int NOT NULL CONSTRAINT DF_StoryAssets_Importance DEFAULT 5,
|
||||||
|
CurrentStateID int NULL,
|
||||||
|
CurrentLocationID int NULL,
|
||||||
|
IsResolved bit NOT NULL CONSTRAINT DF_StoryAssets_IsResolved DEFAULT 0,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_StoryAssets_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_StoryAssets_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_StoryAssets_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_StoryAssets_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_StoryAssets_AssetKinds FOREIGN KEY (AssetKindID) REFERENCES dbo.AssetKinds(AssetKindID),
|
||||||
|
CONSTRAINT FK_StoryAssets_CurrentState FOREIGN KEY (CurrentStateID) REFERENCES dbo.AssetStates(AssetStateID),
|
||||||
|
CONSTRAINT CK_StoryAssets_Importance CHECK (Importance BETWEEN 1 AND 10)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetEventTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetEventTypes
|
||||||
|
(
|
||||||
|
AssetEventTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetEventTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
MarkerText nvarchar(10) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_AssetEventTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_AssetEventTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetEvents', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetEvents
|
||||||
|
(
|
||||||
|
AssetEventID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetEvents PRIMARY KEY,
|
||||||
|
StoryAssetID int NOT NULL,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
AssetEventTypeID int NOT NULL,
|
||||||
|
FromStateID int NULL,
|
||||||
|
ToStateID int NULL,
|
||||||
|
EventTitle nvarchar(200) NOT NULL,
|
||||||
|
EventDescription nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_AssetEvents_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_AssetEvents_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_AssetEvents_StoryAssets FOREIGN KEY (StoryAssetID) REFERENCES dbo.StoryAssets(StoryAssetID),
|
||||||
|
CONSTRAINT FK_AssetEvents_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_AssetEvents_AssetEventTypes FOREIGN KEY (AssetEventTypeID) REFERENCES dbo.AssetEventTypes(AssetEventTypeID),
|
||||||
|
CONSTRAINT FK_AssetEvents_FromState FOREIGN KEY (FromStateID) REFERENCES dbo.AssetStates(AssetStateID),
|
||||||
|
CONSTRAINT FK_AssetEvents_ToState FOREIGN KEY (ToStateID) REFERENCES dbo.AssetStates(AssetStateID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetDependencyTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetDependencyTypes
|
||||||
|
(
|
||||||
|
AssetDependencyTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetDependencyTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsValidatingDependency bit NOT NULL CONSTRAINT DF_AssetDependencyTypes_IsValidatingDependency DEFAULT 0,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_AssetDependencyTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_AssetDependencyTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetDependencies', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetDependencies
|
||||||
|
(
|
||||||
|
AssetDependencyID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetDependencies PRIMARY KEY,
|
||||||
|
SourceAssetID int NOT NULL,
|
||||||
|
TargetAssetID int NOT NULL,
|
||||||
|
AssetDependencyTypeID int NOT NULL,
|
||||||
|
SourceRequiredStateID int NULL,
|
||||||
|
TargetBlockedStateID int NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_AssetDependencies_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_AssetDependencies_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_AssetDependencies_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_AssetDependencies_SourceAsset FOREIGN KEY (SourceAssetID) REFERENCES dbo.StoryAssets(StoryAssetID),
|
||||||
|
CONSTRAINT FK_AssetDependencies_TargetAsset FOREIGN KEY (TargetAssetID) REFERENCES dbo.StoryAssets(StoryAssetID),
|
||||||
|
CONSTRAINT FK_AssetDependencies_Type FOREIGN KEY (AssetDependencyTypeID) REFERENCES dbo.AssetDependencyTypes(AssetDependencyTypeID),
|
||||||
|
CONSTRAINT FK_AssetDependencies_SourceState FOREIGN KEY (SourceRequiredStateID) REFERENCES dbo.AssetStates(AssetStateID),
|
||||||
|
CONSTRAINT FK_AssetDependencies_TargetState FOREIGN KEY (TargetBlockedStateID) REFERENCES dbo.AssetStates(AssetStateID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetCustodyEventTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetCustodyEventTypes
|
||||||
|
(
|
||||||
|
AssetCustodyEventTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetCustodyEventTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_AssetCustodyEventTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_AssetCustodyEventTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.CustodyRoles', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.CustodyRoles
|
||||||
|
(
|
||||||
|
CustodyRoleID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_CustodyRoles PRIMARY KEY,
|
||||||
|
RoleName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_CustodyRoles_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_CustodyRoles_RoleName UNIQUE (RoleName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetCustodyEvents', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetCustodyEvents
|
||||||
|
(
|
||||||
|
AssetCustodyEventID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetCustodyEvents PRIMARY KEY,
|
||||||
|
StoryAssetID int NOT NULL,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
AssetCustodyEventTypeID int NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_AssetCustodyEvents_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_AssetCustodyEvents_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_AssetCustodyEvents_StoryAssets FOREIGN KEY (StoryAssetID) REFERENCES dbo.StoryAssets(StoryAssetID),
|
||||||
|
CONSTRAINT FK_AssetCustodyEvents_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_AssetCustodyEvents_Type FOREIGN KEY (AssetCustodyEventTypeID) REFERENCES dbo.AssetCustodyEventTypes(AssetCustodyEventTypeID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.AssetCustodyEventCharacters', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.AssetCustodyEventCharacters
|
||||||
|
(
|
||||||
|
AssetCustodyEventCharacterID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AssetCustodyEventCharacters PRIMARY KEY,
|
||||||
|
AssetCustodyEventID int NOT NULL,
|
||||||
|
CharacterID int NULL,
|
||||||
|
CharacterNameText nvarchar(200) NULL,
|
||||||
|
CustodyRoleID int NOT NULL,
|
||||||
|
CONSTRAINT FK_AssetCustodyEventCharacters_Events FOREIGN KEY (AssetCustodyEventID) REFERENCES dbo.AssetCustodyEvents(AssetCustodyEventID) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT FK_AssetCustodyEventCharacters_Roles FOREIGN KEY (CustodyRoleID) REFERENCES dbo.CustodyRoles(CustodyRoleID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_StoryAssets_ProjectID' AND object_id = OBJECT_ID(N'dbo.StoryAssets'))
|
||||||
|
CREATE INDEX IX_StoryAssets_ProjectID ON dbo.StoryAssets(ProjectID, IsArchived, Importance);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetEvents_SceneID' AND object_id = OBJECT_ID(N'dbo.AssetEvents'))
|
||||||
|
CREATE INDEX IX_AssetEvents_SceneID ON dbo.AssetEvents(SceneID, StoryAssetID, AssetEventTypeID);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetDependencies_TargetAssetID' AND object_id = OBJECT_ID(N'dbo.AssetDependencies'))
|
||||||
|
CREATE INDEX IX_AssetDependencies_TargetAssetID ON dbo.AssetDependencies(TargetAssetID, IsArchived, AssetDependencyTypeID);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetCustodyEvents_SceneID' AND object_id = OBJECT_ID(N'dbo.AssetCustodyEvents'))
|
||||||
|
CREATE INDEX IX_AssetCustodyEvents_SceneID ON dbo.AssetCustodyEvents(SceneID, StoryAssetID);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.AssetKinds AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Physical Object', 10), (N'Clothing', 20), (N'Document', 30), (N'Evidence', 40),
|
||||||
|
(N'Clue', 50), (N'Secret', 60), (N'Fact', 70), (N'Emotional Fact', 80),
|
||||||
|
(N'Relationship Truth', 90), (N'Plan', 100), (N'Threat', 110), (N'Promise', 120),
|
||||||
|
(N'Location Item', 130), (N'Vehicle', 140), (N'Money Trail', 150), (N'Other', 160)
|
||||||
|
) AS source (KindName, SortOrder)
|
||||||
|
ON target.KindName = source.KindName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (KindName, SortOrder) VALUES (source.KindName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.AssetStates AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Unknown', 10, 0), (N'Mentioned', 20, 0), (N'Known To Exist', 30, 0), (N'Found', 40, 0),
|
||||||
|
(N'Possessed', 50, 0), (N'Investigated', 60, 0), (N'Identified', 70, 0), (N'Used', 80, 0),
|
||||||
|
(N'Lost', 90, 0), (N'Hidden', 100, 0), (N'Revealed', 110, 0), (N'Opened', 120, 0),
|
||||||
|
(N'Destroyed', 130, 1), (N'Resolved', 140, 1)
|
||||||
|
) AS source (StateName, SortOrder, IsResolvedState)
|
||||||
|
ON target.ProjectID IS NULL AND target.AssetKindID IS NULL AND target.StateName = source.StateName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (StateName, SortOrder, IsResolvedState) VALUES (source.StateName, source.SortOrder, source.IsResolvedState);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.AssetEventTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Mentioned', N'M', 10), (N'First Mentioned', N'I', 20), (N'Found', N'F', 30),
|
||||||
|
(N'Lost', N'L', 40), (N'Given', N'G', 50), (N'Taken', N'T', 60), (N'Stolen', N'S', 70),
|
||||||
|
(N'Hidden', N'H', 80), (N'Moved', N'Mv', 90), (N'Used', N'U', 100), (N'Opened', N'O', 110),
|
||||||
|
(N'Destroyed', N'X', 120), (N'Revealed', N'R', 130), (N'Investigated', N'?', 140),
|
||||||
|
(N'State Changed', N'C', 150), (N'Custody Changed', N'Cc', 160), (N'Location Changed', N'Loc', 170),
|
||||||
|
(N'Note', N'N', 180)
|
||||||
|
) AS source (TypeName, MarkerText, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, MarkerText, SortOrder) VALUES (source.TypeName, source.MarkerText, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.AssetDependencyTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Requires', 10, 1), (N'Unlocks', 20, 1), (N'Reveals', 30, 0), (N'Leads To', 40, 0),
|
||||||
|
(N'Contains', 50, 0), (N'Contradicts', 60, 0), (N'Supports', 70, 0), (N'Related To', 80, 0)
|
||||||
|
) AS source (TypeName, SortOrder, IsValidatingDependency)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder, IsValidatingDependency) VALUES (source.TypeName, source.SortOrder, source.IsValidatingDependency);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.AssetCustodyEventTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Has', 10), (N'Shared Custody', 20), (N'Given To', 30), (N'Taken From', 40),
|
||||||
|
(N'Stolen By', 50), (N'Hidden By', 60), (N'Found By', 70), (N'Used By', 80),
|
||||||
|
(N'Observed By', 90), (N'Left With', 100), (N'Lost By', 110), (N'Returned To', 120)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.CustodyRoles AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Has Custody', 10), (N'Shared Custody', 20), (N'Handles', 30), (N'Uses', 40),
|
||||||
|
(N'Observes', 50), (N'Transfers From', 60), (N'Transfers To', 70), (N'Steals From', 80),
|
||||||
|
(N'Hides', 90), (N'Finds', 100)
|
||||||
|
) AS source (RoleName, SortOrder)
|
||||||
|
ON target.RoleName = source.RoleName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (RoleName, SortOrder) VALUES (source.RoleName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetLookup_All
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT AssetKindID, KindName, SortOrder, IsActive FROM dbo.AssetKinds WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT AssetStateID, ProjectID, AssetKindID, StateName, SortOrder, Description, IsResolvedState, IsActive FROM dbo.AssetStates WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT AssetEventTypeID, TypeName, MarkerText, SortOrder, IsActive FROM dbo.AssetEventTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT AssetDependencyTypeID, TypeName, SortOrder, IsValidatingDependency, IsActive FROM dbo.AssetDependencyTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT AssetCustodyEventTypeID, TypeName, SortOrder, IsActive FROM dbo.AssetCustodyEventTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT CustodyRoleID, RoleName, SortOrder, IsActive FROM dbo.CustodyRoles WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetKind_List AS BEGIN SET NOCOUNT ON; SELECT AssetKindID, KindName, SortOrder, IsActive FROM dbo.AssetKinds WHERE IsActive = 1 ORDER BY SortOrder; END;
|
||||||
|
GO
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetState_List AS BEGIN SET NOCOUNT ON; SELECT AssetStateID, ProjectID, AssetKindID, StateName, SortOrder, Description, IsResolvedState, IsActive FROM dbo.AssetStates WHERE IsActive = 1 ORDER BY SortOrder; END;
|
||||||
|
GO
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetEventType_List AS BEGIN SET NOCOUNT ON; SELECT AssetEventTypeID, TypeName, MarkerText, SortOrder, IsActive FROM dbo.AssetEventTypes WHERE IsActive = 1 ORDER BY SortOrder; END;
|
||||||
|
GO
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetDependencyType_List AS BEGIN SET NOCOUNT ON; SELECT AssetDependencyTypeID, TypeName, SortOrder, IsValidatingDependency, IsActive FROM dbo.AssetDependencyTypes WHERE IsActive = 1 ORDER BY SortOrder; END;
|
||||||
|
GO
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEventType_List AS BEGIN SET NOCOUNT ON; SELECT AssetCustodyEventTypeID, TypeName, SortOrder, IsActive FROM dbo.AssetCustodyEventTypes WHERE IsActive = 1 ORDER BY SortOrder; END;
|
||||||
|
GO
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CustodyRole_List AS BEGIN SET NOCOUNT ON; SELECT CustodyRoleID, RoleName, SortOrder, IsActive FROM dbo.CustodyRoles WHERE IsActive = 1 ORDER BY SortOrder; END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
|
||||||
|
sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID, sa.IsResolved,
|
||||||
|
sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0
|
||||||
|
ORDER BY sa.Importance DESC, sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_Get
|
||||||
|
@StoryAssetID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
|
||||||
|
sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID, sa.IsResolved,
|
||||||
|
sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
WHERE sa.StoryAssetID = @StoryAssetID AND sa.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_Save
|
||||||
|
@StoryAssetID int = NULL,
|
||||||
|
@ProjectID int,
|
||||||
|
@AssetName nvarchar(200),
|
||||||
|
@AssetKindID int,
|
||||||
|
@Description nvarchar(max) = NULL,
|
||||||
|
@Importance int,
|
||||||
|
@CurrentStateID int = NULL,
|
||||||
|
@IsResolved bit
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF @StoryAssetID IS NULL OR @StoryAssetID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.StoryAssets (ProjectID, AssetName, AssetKindID, Description, Importance, CurrentStateID, IsResolved)
|
||||||
|
VALUES (@ProjectID, @AssetName, @AssetKindID, @Description, @Importance, @CurrentStateID, @IsResolved);
|
||||||
|
SET @StoryAssetID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.StoryAssets
|
||||||
|
SET AssetName = @AssetName,
|
||||||
|
AssetKindID = @AssetKindID,
|
||||||
|
Description = @Description,
|
||||||
|
Importance = @Importance,
|
||||||
|
CurrentStateID = @CurrentStateID,
|
||||||
|
IsResolved = @IsResolved,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE StoryAssetID = @StoryAssetID;
|
||||||
|
END
|
||||||
|
|
||||||
|
SELECT @StoryAssetID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_Archive
|
||||||
|
@StoryAssetID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.StoryAssets SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE StoryAssetID = @StoryAssetID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetEvent_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ae.AssetEventID, ae.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName,
|
||||||
|
ae.SceneID, ae.AssetEventTypeID, aet.TypeName AS AssetEventTypeName, aet.MarkerText,
|
||||||
|
ae.FromStateID, fs.StateName AS FromStateName, ae.ToStateID, ts.StateName AS ToStateName,
|
||||||
|
ae.EventTitle, ae.EventDescription, ae.CreatedDate, ae.UpdatedDate
|
||||||
|
FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
|
||||||
|
LEFT JOIN dbo.AssetStates fs ON fs.AssetStateID = ae.FromStateID
|
||||||
|
LEFT JOIN dbo.AssetStates ts ON ts.AssetStateID = ae.ToStateID
|
||||||
|
WHERE ae.SceneID = @SceneID
|
||||||
|
ORDER BY sa.AssetName, ae.AssetEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetEvent_ListByAsset
|
||||||
|
@StoryAssetID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ae.AssetEventID, ae.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName,
|
||||||
|
ae.SceneID, ae.AssetEventTypeID, aet.TypeName AS AssetEventTypeName, aet.MarkerText,
|
||||||
|
ae.FromStateID, fs.StateName AS FromStateName, ae.ToStateID, ts.StateName AS ToStateName,
|
||||||
|
ae.EventTitle, ae.EventDescription, ae.CreatedDate, ae.UpdatedDate,
|
||||||
|
b.BookTitle, c.ChapterNumber, s.SceneNumber, s.SceneTitle
|
||||||
|
FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ae.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
LEFT JOIN dbo.AssetStates fs ON fs.AssetStateID = ae.FromStateID
|
||||||
|
LEFT JOIN dbo.AssetStates ts ON ts.AssetStateID = ae.ToStateID
|
||||||
|
WHERE ae.StoryAssetID = @StoryAssetID
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, ae.AssetEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetEvent_Save
|
||||||
|
@AssetEventID int = NULL,
|
||||||
|
@StoryAssetID int,
|
||||||
|
@SceneID int,
|
||||||
|
@AssetEventTypeID int,
|
||||||
|
@FromStateID int = NULL,
|
||||||
|
@ToStateID int = NULL,
|
||||||
|
@EventTitle nvarchar(200),
|
||||||
|
@EventDescription nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF @AssetEventID IS NULL OR @AssetEventID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.AssetEvents (StoryAssetID, SceneID, AssetEventTypeID, FromStateID, ToStateID, EventTitle, EventDescription)
|
||||||
|
VALUES (@StoryAssetID, @SceneID, @AssetEventTypeID, @FromStateID, @ToStateID, @EventTitle, @EventDescription);
|
||||||
|
SET @AssetEventID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.AssetEvents
|
||||||
|
SET StoryAssetID = @StoryAssetID,
|
||||||
|
SceneID = @SceneID,
|
||||||
|
AssetEventTypeID = @AssetEventTypeID,
|
||||||
|
FromStateID = @FromStateID,
|
||||||
|
ToStateID = @ToStateID,
|
||||||
|
EventTitle = @EventTitle,
|
||||||
|
EventDescription = @EventDescription,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE AssetEventID = @AssetEventID;
|
||||||
|
END
|
||||||
|
|
||||||
|
IF @ToStateID IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.StoryAssets
|
||||||
|
SET CurrentStateID = @ToStateID,
|
||||||
|
IsResolved = CASE WHEN EXISTS (SELECT 1 FROM dbo.AssetStates WHERE AssetStateID = @ToStateID AND IsResolvedState = 1) THEN 1 ELSE IsResolved END,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE StoryAssetID = @StoryAssetID;
|
||||||
|
END
|
||||||
|
|
||||||
|
SELECT @AssetEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetEvent_Delete
|
||||||
|
@AssetEventID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DELETE FROM dbo.AssetEvents WHERE AssetEventID = @AssetEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetDependency_ListByAsset
|
||||||
|
@StoryAssetID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ad.AssetDependencyID, ad.SourceAssetID, source.AssetName AS SourceAssetName,
|
||||||
|
ad.TargetAssetID, target.AssetName AS TargetAssetName,
|
||||||
|
ad.AssetDependencyTypeID, adt.TypeName AS DependencyTypeName, adt.IsValidatingDependency,
|
||||||
|
ad.SourceRequiredStateID, srs.StateName AS SourceRequiredStateName,
|
||||||
|
ad.TargetBlockedStateID, tbs.StateName AS TargetBlockedStateName,
|
||||||
|
ad.Description, ad.CreatedDate, ad.UpdatedDate, ad.IsArchived
|
||||||
|
FROM dbo.AssetDependencies ad
|
||||||
|
INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID
|
||||||
|
INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID
|
||||||
|
INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID
|
||||||
|
LEFT JOIN dbo.AssetStates srs ON srs.AssetStateID = ad.SourceRequiredStateID
|
||||||
|
LEFT JOIN dbo.AssetStates tbs ON tbs.AssetStateID = ad.TargetBlockedStateID
|
||||||
|
WHERE ad.IsArchived = 0 AND (@StoryAssetID IN (ad.SourceAssetID, ad.TargetAssetID))
|
||||||
|
ORDER BY ad.AssetDependencyID DESC;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetDependency_Save
|
||||||
|
@AssetDependencyID int = NULL,
|
||||||
|
@SourceAssetID int,
|
||||||
|
@TargetAssetID int,
|
||||||
|
@AssetDependencyTypeID int,
|
||||||
|
@SourceRequiredStateID int = NULL,
|
||||||
|
@TargetBlockedStateID int = NULL,
|
||||||
|
@Description nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @AssetDependencyID IS NULL OR @AssetDependencyID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.AssetDependencies (SourceAssetID, TargetAssetID, AssetDependencyTypeID, SourceRequiredStateID, TargetBlockedStateID, Description)
|
||||||
|
VALUES (@SourceAssetID, @TargetAssetID, @AssetDependencyTypeID, @SourceRequiredStateID, @TargetBlockedStateID, @Description);
|
||||||
|
SET @AssetDependencyID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.AssetDependencies
|
||||||
|
SET SourceAssetID = @SourceAssetID,
|
||||||
|
TargetAssetID = @TargetAssetID,
|
||||||
|
AssetDependencyTypeID = @AssetDependencyTypeID,
|
||||||
|
SourceRequiredStateID = @SourceRequiredStateID,
|
||||||
|
TargetBlockedStateID = @TargetBlockedStateID,
|
||||||
|
Description = @Description,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE AssetDependencyID = @AssetDependencyID;
|
||||||
|
END
|
||||||
|
SELECT @AssetDependencyID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetDependency_Delete
|
||||||
|
@AssetDependencyID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.AssetDependencies SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE AssetDependencyID = @AssetDependencyID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEvent_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID,
|
||||||
|
ace.AssetCustodyEventTypeID, acet.TypeName AS CustodyEventTypeName, ace.Description,
|
||||||
|
STRING_AGG(CONCAT(aec.CharacterNameText, N' (', cr.RoleName, N')'), N', ') AS CustodianSummary,
|
||||||
|
ace.CreatedDate, ace.UpdatedDate
|
||||||
|
FROM dbo.AssetCustodyEvents ace
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetCustodyEventTypes acet ON acet.AssetCustodyEventTypeID = ace.AssetCustodyEventTypeID
|
||||||
|
LEFT JOIN dbo.AssetCustodyEventCharacters aec ON aec.AssetCustodyEventID = ace.AssetCustodyEventID
|
||||||
|
LEFT JOIN dbo.CustodyRoles cr ON cr.CustodyRoleID = aec.CustodyRoleID
|
||||||
|
WHERE ace.SceneID = @SceneID
|
||||||
|
GROUP BY ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID, ace.AssetCustodyEventTypeID, acet.TypeName, ace.Description, ace.CreatedDate, ace.UpdatedDate
|
||||||
|
ORDER BY sa.AssetName, ace.AssetCustodyEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEvent_ListByAsset
|
||||||
|
@StoryAssetID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID,
|
||||||
|
ace.AssetCustodyEventTypeID, acet.TypeName AS CustodyEventTypeName, ace.Description,
|
||||||
|
STRING_AGG(CONCAT(aec.CharacterNameText, N' (', cr.RoleName, N')'), N', ') AS CustodianSummary,
|
||||||
|
ace.CreatedDate, ace.UpdatedDate, b.BookTitle, c.ChapterNumber, s.SceneNumber, s.SceneTitle
|
||||||
|
FROM dbo.AssetCustodyEvents ace
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetCustodyEventTypes acet ON acet.AssetCustodyEventTypeID = ace.AssetCustodyEventTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ace.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
LEFT JOIN dbo.AssetCustodyEventCharacters aec ON aec.AssetCustodyEventID = ace.AssetCustodyEventID
|
||||||
|
LEFT JOIN dbo.CustodyRoles cr ON cr.CustodyRoleID = aec.CustodyRoleID
|
||||||
|
WHERE ace.StoryAssetID = @StoryAssetID
|
||||||
|
GROUP BY ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID, ace.AssetCustodyEventTypeID, acet.TypeName, ace.Description, ace.CreatedDate, ace.UpdatedDate, b.BookTitle, c.ChapterNumber, s.SceneNumber, s.SceneTitle, b.SortOrder, c.SortOrder, s.SortOrder
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, ace.AssetCustodyEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEvent_Save
|
||||||
|
@AssetCustodyEventID int = NULL,
|
||||||
|
@StoryAssetID int,
|
||||||
|
@SceneID int,
|
||||||
|
@AssetCustodyEventTypeID int,
|
||||||
|
@Description nvarchar(max) = NULL,
|
||||||
|
@CustodianNames nvarchar(max) = NULL,
|
||||||
|
@CustodyRoleID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF @AssetCustodyEventID IS NULL OR @AssetCustodyEventID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.AssetCustodyEvents (StoryAssetID, SceneID, AssetCustodyEventTypeID, Description)
|
||||||
|
VALUES (@StoryAssetID, @SceneID, @AssetCustodyEventTypeID, @Description);
|
||||||
|
SET @AssetCustodyEventID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.AssetCustodyEvents
|
||||||
|
SET StoryAssetID = @StoryAssetID,
|
||||||
|
SceneID = @SceneID,
|
||||||
|
AssetCustodyEventTypeID = @AssetCustodyEventTypeID,
|
||||||
|
Description = @Description,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE AssetCustodyEventID = @AssetCustodyEventID;
|
||||||
|
END
|
||||||
|
|
||||||
|
DELETE FROM dbo.AssetCustodyEventCharacters WHERE AssetCustodyEventID = @AssetCustodyEventID;
|
||||||
|
|
||||||
|
INSERT dbo.AssetCustodyEventCharacters (AssetCustodyEventID, CharacterNameText, CustodyRoleID)
|
||||||
|
SELECT @AssetCustodyEventID, LTRIM(RTRIM(value)), @CustodyRoleID
|
||||||
|
FROM STRING_SPLIT(REPLACE(ISNULL(@CustodianNames, N''), CHAR(10), N','), N',')
|
||||||
|
WHERE LTRIM(RTRIM(value)) <> N'';
|
||||||
|
|
||||||
|
SELECT @AssetCustodyEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEvent_Delete
|
||||||
|
@AssetCustodyEventID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DELETE FROM dbo.AssetCustodyEvents WHERE AssetCustodyEventID = @AssetCustodyEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetTimeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
|
||||||
|
sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID, sa.IsResolved,
|
||||||
|
sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0 AND (sa.Importance >= 5 OR sa.IsResolved = 0)
|
||||||
|
ORDER BY sa.Importance DESC, sa.AssetName;
|
||||||
|
|
||||||
|
SELECT ae.AssetEventID, ae.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName,
|
||||||
|
ae.SceneID, ae.AssetEventTypeID, aet.TypeName AS AssetEventTypeName, aet.MarkerText,
|
||||||
|
ae.FromStateID, fs.StateName AS FromStateName, ae.ToStateID, ts.StateName AS ToStateName,
|
||||||
|
ae.EventTitle, ae.EventDescription, ae.CreatedDate, ae.UpdatedDate
|
||||||
|
FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ae.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
LEFT JOIN dbo.AssetStates fs ON fs.AssetStateID = ae.FromStateID
|
||||||
|
LEFT JOIN dbo.AssetStates ts ON ts.AssetStateID = ae.ToStateID
|
||||||
|
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetDependency_CheckForEvent
|
||||||
|
@StoryAssetID int,
|
||||||
|
@SceneID int,
|
||||||
|
@ToStateID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
;WITH CurrentScene AS
|
||||||
|
(
|
||||||
|
SELECT b.SortOrder AS BookSortOrder, c.SortOrder AS ChapterSortOrder, s.SortOrder AS SceneSortOrder
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE s.SceneID = @SceneID
|
||||||
|
)
|
||||||
|
SELECT ad.AssetDependencyID, source.AssetName AS SourceAssetName, target.AssetName AS TargetAssetName,
|
||||||
|
adt.TypeName AS DependencyTypeName, srs.StateName AS SourceRequiredStateName,
|
||||||
|
tbs.StateName AS TargetBlockedStateName, ad.Description
|
||||||
|
FROM dbo.AssetDependencies ad
|
||||||
|
INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID
|
||||||
|
INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID
|
||||||
|
INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID
|
||||||
|
LEFT JOIN dbo.AssetStates srs ON srs.AssetStateID = ad.SourceRequiredStateID
|
||||||
|
LEFT JOIN dbo.AssetStates tbs ON tbs.AssetStateID = ad.TargetBlockedStateID
|
||||||
|
CROSS JOIN CurrentScene cs
|
||||||
|
WHERE ad.IsArchived = 0
|
||||||
|
AND adt.IsValidatingDependency = 1
|
||||||
|
AND ad.TargetAssetID = @StoryAssetID
|
||||||
|
AND (@ToStateID IS NULL OR ad.TargetBlockedStateID IS NULL OR ad.TargetBlockedStateID = @ToStateID)
|
||||||
|
AND ad.SourceRequiredStateID IS NOT NULL
|
||||||
|
AND NOT EXISTS
|
||||||
|
(
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ae.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE ae.StoryAssetID = ad.SourceAssetID
|
||||||
|
AND ae.ToStateID = ad.SourceRequiredStateID
|
||||||
|
AND (
|
||||||
|
b.SortOrder < cs.BookSortOrder
|
||||||
|
OR (b.SortOrder = cs.BookSortOrder AND c.SortOrder < cs.ChapterSortOrder)
|
||||||
|
OR (b.SortOrder = cs.BookSortOrder AND c.SortOrder = cs.ChapterSortOrder AND s.SortOrder < cs.SceneSortOrder)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
821
PlotLine/Sql/006_Phase1E_Characters.sql
Normal file
821
PlotLine/Sql/006_Phase1E_Characters.sql
Normal file
@ -0,0 +1,821 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.CharacterRoleInSceneTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.CharacterRoleInSceneTypes
|
||||||
|
(
|
||||||
|
CharacterRoleInSceneTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_CharacterRoleInSceneTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_CharacterRoleInSceneTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_CharacterRoleInSceneTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.PresenceTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.PresenceTypes
|
||||||
|
(
|
||||||
|
PresenceTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_PresenceTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_PresenceTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_PresenceTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Characters', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Characters
|
||||||
|
(
|
||||||
|
CharacterID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Characters PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
CharacterName nvarchar(200) NOT NULL,
|
||||||
|
ShortName nvarchar(100) NULL,
|
||||||
|
Sex nvarchar(50) NULL,
|
||||||
|
BirthDate date NULL,
|
||||||
|
AgeAtSeriesStart int NULL,
|
||||||
|
AgeReferenceSceneID int NULL,
|
||||||
|
Height nvarchar(50) NULL,
|
||||||
|
EyeColour nvarchar(50) NULL,
|
||||||
|
DefaultDescription nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_Characters_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_Characters_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_Characters_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_Characters_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_Characters_AgeReferenceScene FOREIGN KEY (AgeReferenceSceneID) REFERENCES dbo.Scenes(SceneID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.SceneCharacters', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.SceneCharacters
|
||||||
|
(
|
||||||
|
SceneCharacterID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneCharacters PRIMARY KEY,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
CharacterID int NOT NULL,
|
||||||
|
RoleInSceneTypeID int NULL,
|
||||||
|
PresenceTypeID int NULL,
|
||||||
|
LocationID int NULL,
|
||||||
|
EntryLocationID int NULL,
|
||||||
|
ExitLocationID int NULL,
|
||||||
|
AppearanceNotes nvarchar(max) NULL,
|
||||||
|
OutfitDescription nvarchar(max) NULL,
|
||||||
|
PhysicalCondition nvarchar(max) NULL,
|
||||||
|
EmotionalState nvarchar(max) NULL,
|
||||||
|
KnowledgeNotes nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_SceneCharacters_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_SceneCharacters_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_SceneCharacters_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_SceneCharacters_Characters FOREIGN KEY (CharacterID) REFERENCES dbo.Characters(CharacterID),
|
||||||
|
CONSTRAINT FK_SceneCharacters_RoleTypes FOREIGN KEY (RoleInSceneTypeID) REFERENCES dbo.CharacterRoleInSceneTypes(CharacterRoleInSceneTypeID),
|
||||||
|
CONSTRAINT FK_SceneCharacters_PresenceTypes FOREIGN KEY (PresenceTypeID) REFERENCES dbo.PresenceTypes(PresenceTypeID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.CharacterAttributeTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.CharacterAttributeTypes
|
||||||
|
(
|
||||||
|
CharacterAttributeTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_CharacterAttributeTypes PRIMARY KEY,
|
||||||
|
ProjectID int NULL,
|
||||||
|
AttributeName nvarchar(100) NOT NULL,
|
||||||
|
DataType nvarchar(50) NOT NULL CONSTRAINT DF_CharacterAttributeTypes_DataType DEFAULT N'Text',
|
||||||
|
IsNormallyStable bit NOT NULL CONSTRAINT DF_CharacterAttributeTypes_IsNormallyStable DEFAULT 0,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_CharacterAttributeTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT FK_CharacterAttributeTypes_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.CharacterAttributeEvents', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.CharacterAttributeEvents
|
||||||
|
(
|
||||||
|
CharacterAttributeEventID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_CharacterAttributeEvents PRIMARY KEY,
|
||||||
|
CharacterID int NOT NULL,
|
||||||
|
CharacterAttributeTypeID int NOT NULL,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
AttributeValue nvarchar(500) NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_CharacterAttributeEvents_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_CharacterAttributeEvents_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_CharacterAttributeEvents_Characters FOREIGN KEY (CharacterID) REFERENCES dbo.Characters(CharacterID),
|
||||||
|
CONSTRAINT FK_CharacterAttributeEvents_Types FOREIGN KEY (CharacterAttributeTypeID) REFERENCES dbo.CharacterAttributeTypes(CharacterAttributeTypeID),
|
||||||
|
CONSTRAINT FK_CharacterAttributeEvents_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.KnowledgeStates', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.KnowledgeStates
|
||||||
|
(
|
||||||
|
KnowledgeStateID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_KnowledgeStates PRIMARY KEY,
|
||||||
|
StateName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_KnowledgeStates_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_KnowledgeStates_StateName UNIQUE (StateName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.CharacterKnowledge', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.CharacterKnowledge
|
||||||
|
(
|
||||||
|
CharacterKnowledgeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_CharacterKnowledge PRIMARY KEY,
|
||||||
|
CharacterID int NOT NULL,
|
||||||
|
StoryAssetID int NULL,
|
||||||
|
PlotThreadID int NULL,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
KnowledgeStateID int NOT NULL,
|
||||||
|
SourceEventID int NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_CharacterKnowledge_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_CharacterKnowledge_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_CharacterKnowledge_Characters FOREIGN KEY (CharacterID) REFERENCES dbo.Characters(CharacterID),
|
||||||
|
CONSTRAINT FK_CharacterKnowledge_StoryAssets FOREIGN KEY (StoryAssetID) REFERENCES dbo.StoryAssets(StoryAssetID),
|
||||||
|
CONSTRAINT FK_CharacterKnowledge_PlotThreads FOREIGN KEY (PlotThreadID) REFERENCES dbo.PlotThreads(PlotThreadID),
|
||||||
|
CONSTRAINT FK_CharacterKnowledge_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_CharacterKnowledge_States FOREIGN KEY (KnowledgeStateID) REFERENCES dbo.KnowledgeStates(KnowledgeStateID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.RelationshipTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.RelationshipTypes
|
||||||
|
(
|
||||||
|
RelationshipTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_RelationshipTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
IsPermanentDefault bit NOT NULL CONSTRAINT DF_RelationshipTypes_IsPermanentDefault DEFAULT 0,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_RelationshipTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_RelationshipTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.RelationshipStates', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.RelationshipStates
|
||||||
|
(
|
||||||
|
RelationshipStateID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_RelationshipStates PRIMARY KEY,
|
||||||
|
StateName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_RelationshipStates_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_RelationshipStates_StateName UNIQUE (StateName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.CharacterRelationships', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.CharacterRelationships
|
||||||
|
(
|
||||||
|
CharacterRelationshipID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_CharacterRelationships PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
CharacterAID int NOT NULL,
|
||||||
|
CharacterBID int NOT NULL,
|
||||||
|
RelationshipTypeID int NOT NULL,
|
||||||
|
IsPermanent bit NOT NULL CONSTRAINT DF_CharacterRelationships_IsPermanent DEFAULT 0,
|
||||||
|
StartSceneID int NULL,
|
||||||
|
EndSceneID int NULL,
|
||||||
|
IsKnownToReader bit NOT NULL CONSTRAINT DF_CharacterRelationships_IsKnownToReader DEFAULT 0,
|
||||||
|
Notes nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_CharacterRelationships_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_CharacterRelationships_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_CharacterRelationships_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_CharacterRelationships_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_CharacterRelationships_CharacterA FOREIGN KEY (CharacterAID) REFERENCES dbo.Characters(CharacterID),
|
||||||
|
CONSTRAINT FK_CharacterRelationships_CharacterB FOREIGN KEY (CharacterBID) REFERENCES dbo.Characters(CharacterID),
|
||||||
|
CONSTRAINT FK_CharacterRelationships_Types FOREIGN KEY (RelationshipTypeID) REFERENCES dbo.RelationshipTypes(RelationshipTypeID),
|
||||||
|
CONSTRAINT FK_CharacterRelationships_StartScene FOREIGN KEY (StartSceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_CharacterRelationships_EndScene FOREIGN KEY (EndSceneID) REFERENCES dbo.Scenes(SceneID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.RelationshipEvents', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.RelationshipEvents
|
||||||
|
(
|
||||||
|
RelationshipEventID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_RelationshipEvents PRIMARY KEY,
|
||||||
|
CharacterRelationshipID int NOT NULL,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
RelationshipStateID int NOT NULL,
|
||||||
|
Intensity int NULL,
|
||||||
|
IsKnownToOtherCharacter bit NOT NULL CONSTRAINT DF_RelationshipEvents_IsKnownToOtherCharacter DEFAULT 0,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_RelationshipEvents_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_RelationshipEvents_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_RelationshipEvents_Relationships FOREIGN KEY (CharacterRelationshipID) REFERENCES dbo.CharacterRelationships(CharacterRelationshipID),
|
||||||
|
CONSTRAINT FK_RelationshipEvents_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_RelationshipEvents_States FOREIGN KEY (RelationshipStateID) REFERENCES dbo.RelationshipStates(RelationshipStateID),
|
||||||
|
CONSTRAINT CK_RelationshipEvents_Intensity CHECK (Intensity IS NULL OR Intensity BETWEEN 1 AND 10)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Characters_ProjectID' AND object_id = OBJECT_ID(N'dbo.Characters'))
|
||||||
|
CREATE INDEX IX_Characters_ProjectID ON dbo.Characters(ProjectID, IsArchived, CharacterName);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneCharacters_SceneID' AND object_id = OBJECT_ID(N'dbo.SceneCharacters'))
|
||||||
|
CREATE INDEX IX_SceneCharacters_SceneID ON dbo.SceneCharacters(SceneID, CharacterID);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_CharacterKnowledge_SceneID' AND object_id = OBJECT_ID(N'dbo.CharacterKnowledge'))
|
||||||
|
CREATE INDEX IX_CharacterKnowledge_SceneID ON dbo.CharacterKnowledge(SceneID, CharacterID);
|
||||||
|
GO
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_RelationshipEvents_SceneID' AND object_id = OBJECT_ID(N'dbo.RelationshipEvents'))
|
||||||
|
CREATE INDEX IX_RelationshipEvents_SceneID ON dbo.RelationshipEvents(SceneID, CharacterRelationshipID);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.CharacterRoleInSceneTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'POV Character', 10), (N'Main Participant', 20), (N'Supporting Participant', 30), (N'Witness', 40),
|
||||||
|
(N'Background', 50), (N'Mentioned Only', 60), (N'Hidden', 70), (N'Overhearing', 80)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.PresenceTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Present', 10), (N'Arrives', 20), (N'Leaves', 30), (N'Nearby', 40), (N'Hidden', 50),
|
||||||
|
(N'Overhearing', 60), (N'Mentioned Only', 70), (N'Off-screen', 80), (N'Unknown', 90)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.CharacterAttributeTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Hair Colour', N'Text', 1, 10), (N'Hair Style', N'Text', 0, 20), (N'Injury', N'Text', 0, 30),
|
||||||
|
(N'Disguise', N'Text', 0, 40), (N'Public Name', N'Text', 1, 50), (N'Mental State', N'Text', 0, 60),
|
||||||
|
(N'Physical Condition', N'Text', 0, 70), (N'Reputation', N'Text', 0, 80), (N'Confidence', N'Number', 0, 90),
|
||||||
|
(N'Clothing Note', N'Text', 0, 100)
|
||||||
|
) AS source (AttributeName, DataType, IsNormallyStable, SortOrder)
|
||||||
|
ON target.ProjectID IS NULL AND target.AttributeName = source.AttributeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (AttributeName, DataType, IsNormallyStable, SortOrder) VALUES (source.AttributeName, source.DataType, source.IsNormallyStable, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.KnowledgeStates AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Unaware', 10), (N'Suspects', 20), (N'Partially Knows', 30), (N'Knows', 40),
|
||||||
|
(N'Misunderstands', 50), (N'Hiding', 60), (N'Lying About', 70)
|
||||||
|
) AS source (StateName, SortOrder)
|
||||||
|
ON target.StateName = source.StateName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (StateName, SortOrder) VALUES (source.StateName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.RelationshipTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Parent', 1, 10), (N'Child', 1, 20), (N'Sibling', 1, 30), (N'Half-sibling', 1, 40),
|
||||||
|
(N'Adoptive Parent', 1, 50), (N'Biological Parent', 1, 60), (N'Guardian', 1, 70),
|
||||||
|
(N'Friend', 0, 80), (N'Enemy', 0, 90), (N'Lover', 0, 100), (N'Ex-lover', 0, 110),
|
||||||
|
(N'Spouse', 1, 120), (N'Ally', 0, 130), (N'Rival', 0, 140), (N'Protector', 0, 150),
|
||||||
|
(N'Suspicious Of', 0, 160), (N'Afraid Of', 0, 170), (N'Manipulating', 0, 180), (N'Estranged From', 0, 190)
|
||||||
|
) AS source (TypeName, IsPermanentDefault, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, IsPermanentDefault, SortOrder) VALUES (source.TypeName, source.IsPermanentDefault, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.RelationshipStates AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Unknown', 10), (N'Positive', 20), (N'Neutral', 30), (N'Negative', 40), (N'Tense', 50),
|
||||||
|
(N'Friendly', 60), (N'Romantic', 70), (N'Sexual Tension', 80), (N'In Love', 90),
|
||||||
|
(N'Distrustful', 100), (N'Hostile', 110), (N'Protective', 120), (N'Estranged', 130),
|
||||||
|
(N'Broken', 140), (N'Reconciled', 150)
|
||||||
|
) AS source (StateName, SortOrder)
|
||||||
|
ON target.StateName = source.StateName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (StateName, SortOrder) VALUES (source.StateName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterLookup_All
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT CharacterRoleInSceneTypeID, TypeName, SortOrder, IsActive FROM dbo.CharacterRoleInSceneTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT PresenceTypeID, TypeName, SortOrder, IsActive FROM dbo.PresenceTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT CharacterAttributeTypeID, ProjectID, AttributeName, DataType, IsNormallyStable, SortOrder, IsActive FROM dbo.CharacterAttributeTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT KnowledgeStateID, StateName, SortOrder, IsActive FROM dbo.KnowledgeStates WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT RelationshipTypeID, TypeName, IsPermanentDefault, SortOrder, IsActive FROM dbo.RelationshipTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT RelationshipStateID, StateName, SortOrder, IsActive FROM dbo.RelationshipStates WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Character_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||||
|
Height, EyeColour, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Characters
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0
|
||||||
|
ORDER BY CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Character_Get
|
||||||
|
@CharacterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||||
|
Height, EyeColour, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Characters
|
||||||
|
WHERE CharacterID = @CharacterID AND IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Character_Save
|
||||||
|
@CharacterID int = NULL,
|
||||||
|
@ProjectID int,
|
||||||
|
@CharacterName nvarchar(200),
|
||||||
|
@ShortName nvarchar(100) = NULL,
|
||||||
|
@Sex nvarchar(50) = NULL,
|
||||||
|
@BirthDate date = NULL,
|
||||||
|
@AgeAtSeriesStart int = NULL,
|
||||||
|
@AgeReferenceSceneID int = NULL,
|
||||||
|
@Height nvarchar(50) = NULL,
|
||||||
|
@EyeColour nvarchar(50) = NULL,
|
||||||
|
@DefaultDescription nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @CharacterID IS NULL OR @CharacterID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.Characters (ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID, Height, EyeColour, DefaultDescription)
|
||||||
|
VALUES (@ProjectID, @CharacterName, @ShortName, @Sex, @BirthDate, @AgeAtSeriesStart, @AgeReferenceSceneID, @Height, @EyeColour, @DefaultDescription);
|
||||||
|
SET @CharacterID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.Characters
|
||||||
|
SET CharacterName = @CharacterName, ShortName = @ShortName, Sex = @Sex, BirthDate = @BirthDate,
|
||||||
|
AgeAtSeriesStart = @AgeAtSeriesStart, AgeReferenceSceneID = @AgeReferenceSceneID,
|
||||||
|
Height = @Height, EyeColour = @EyeColour, DefaultDescription = @DefaultDescription,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE CharacterID = @CharacterID;
|
||||||
|
END
|
||||||
|
SELECT @CharacterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Character_Archive
|
||||||
|
@CharacterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.Characters SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE CharacterID = @CharacterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneCharacter_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
|
||||||
|
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
|
||||||
|
sc.AppearanceNotes, sc.OutfitDescription, sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes,
|
||||||
|
sc.CreatedDate, sc.UpdatedDate
|
||||||
|
FROM dbo.SceneCharacters sc
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = sc.CharacterID
|
||||||
|
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||||
|
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||||
|
WHERE sc.SceneID = @SceneID AND c.IsArchived = 0
|
||||||
|
ORDER BY role.SortOrder, c.CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneCharacter_Save
|
||||||
|
@SceneCharacterID int = NULL,
|
||||||
|
@SceneID int,
|
||||||
|
@CharacterID int,
|
||||||
|
@RoleInSceneTypeID int = NULL,
|
||||||
|
@PresenceTypeID int = NULL,
|
||||||
|
@AppearanceNotes nvarchar(max) = NULL,
|
||||||
|
@OutfitDescription nvarchar(max) = NULL,
|
||||||
|
@PhysicalCondition nvarchar(max) = NULL,
|
||||||
|
@EmotionalState nvarchar(max) = NULL,
|
||||||
|
@KnowledgeNotes nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @SceneCharacterID IS NULL OR @SceneCharacterID = 0
|
||||||
|
BEGIN
|
||||||
|
SELECT @SceneCharacterID = SceneCharacterID FROM dbo.SceneCharacters WHERE SceneID = @SceneID AND CharacterID = @CharacterID;
|
||||||
|
END
|
||||||
|
IF @SceneCharacterID IS NULL OR @SceneCharacterID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.SceneCharacters (SceneID, CharacterID, RoleInSceneTypeID, PresenceTypeID, AppearanceNotes, OutfitDescription, PhysicalCondition, EmotionalState, KnowledgeNotes)
|
||||||
|
VALUES (@SceneID, @CharacterID, @RoleInSceneTypeID, @PresenceTypeID, @AppearanceNotes, @OutfitDescription, @PhysicalCondition, @EmotionalState, @KnowledgeNotes);
|
||||||
|
SET @SceneCharacterID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.SceneCharacters
|
||||||
|
SET CharacterID = @CharacterID, RoleInSceneTypeID = @RoleInSceneTypeID, PresenceTypeID = @PresenceTypeID,
|
||||||
|
AppearanceNotes = @AppearanceNotes, OutfitDescription = @OutfitDescription, PhysicalCondition = @PhysicalCondition,
|
||||||
|
EmotionalState = @EmotionalState, KnowledgeNotes = @KnowledgeNotes, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneCharacterID = @SceneCharacterID;
|
||||||
|
END
|
||||||
|
SELECT @SceneCharacterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneCharacter_Delete
|
||||||
|
@SceneCharacterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DELETE FROM dbo.SceneCharacters WHERE SceneCharacterID = @SceneCharacterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterAttributeEvent_ListByCharacter
|
||||||
|
@CharacterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT cae.CharacterAttributeEventID, cae.CharacterID, c.CharacterName, cae.CharacterAttributeTypeID, cat.AttributeName,
|
||||||
|
cae.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle, cae.AttributeValue, cae.Description, cae.CreatedDate, cae.UpdatedDate
|
||||||
|
FROM dbo.CharacterAttributeEvents cae
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = cae.CharacterID
|
||||||
|
INNER JOIN dbo.CharacterAttributeTypes cat ON cat.CharacterAttributeTypeID = cae.CharacterAttributeTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = cae.SceneID
|
||||||
|
INNER JOIN dbo.Chapters ch ON ch.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
|
||||||
|
WHERE cae.CharacterID = @CharacterID
|
||||||
|
ORDER BY b.SortOrder, ch.SortOrder, s.SortOrder, cat.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterAttributeEvent_Save
|
||||||
|
@CharacterAttributeEventID int = NULL,
|
||||||
|
@CharacterID int,
|
||||||
|
@CharacterAttributeTypeID int,
|
||||||
|
@SceneID int,
|
||||||
|
@AttributeValue nvarchar(500),
|
||||||
|
@Description nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @CharacterAttributeEventID IS NULL OR @CharacterAttributeEventID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.CharacterAttributeEvents (CharacterID, CharacterAttributeTypeID, SceneID, AttributeValue, Description)
|
||||||
|
VALUES (@CharacterID, @CharacterAttributeTypeID, @SceneID, @AttributeValue, @Description);
|
||||||
|
SET @CharacterAttributeEventID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.CharacterAttributeEvents
|
||||||
|
SET CharacterID = @CharacterID, CharacterAttributeTypeID = @CharacterAttributeTypeID, SceneID = @SceneID,
|
||||||
|
AttributeValue = @AttributeValue, Description = @Description, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE CharacterAttributeEventID = @CharacterAttributeEventID;
|
||||||
|
END
|
||||||
|
SELECT @CharacterAttributeEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterKnowledge_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ck.CharacterKnowledgeID, ck.CharacterID, c.CharacterName, ck.StoryAssetID, sa.AssetName, ck.PlotThreadID, pt.ThreadTitle,
|
||||||
|
ck.SceneID, ck.KnowledgeStateID, ks.StateName AS KnowledgeStateName, ck.SourceEventID, ck.Description, ck.CreatedDate, ck.UpdatedDate
|
||||||
|
FROM dbo.CharacterKnowledge ck
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = ck.CharacterID
|
||||||
|
INNER JOIN dbo.KnowledgeStates ks ON ks.KnowledgeStateID = ck.KnowledgeStateID
|
||||||
|
LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ck.StoryAssetID
|
||||||
|
LEFT JOIN dbo.PlotThreads pt ON pt.PlotThreadID = ck.PlotThreadID
|
||||||
|
WHERE ck.SceneID = @SceneID
|
||||||
|
ORDER BY c.CharacterName, ks.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterKnowledge_ListByCharacter
|
||||||
|
@CharacterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ck.CharacterKnowledgeID, ck.CharacterID, c.CharacterName, ck.StoryAssetID, sa.AssetName, ck.PlotThreadID, pt.ThreadTitle,
|
||||||
|
ck.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle,
|
||||||
|
ck.KnowledgeStateID, ks.StateName AS KnowledgeStateName, ck.SourceEventID, ck.Description, ck.CreatedDate, ck.UpdatedDate
|
||||||
|
FROM dbo.CharacterKnowledge ck
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = ck.CharacterID
|
||||||
|
INNER JOIN dbo.KnowledgeStates ks ON ks.KnowledgeStateID = ck.KnowledgeStateID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ck.SceneID
|
||||||
|
INNER JOIN dbo.Chapters ch ON ch.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
|
||||||
|
LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ck.StoryAssetID
|
||||||
|
LEFT JOIN dbo.PlotThreads pt ON pt.PlotThreadID = ck.PlotThreadID
|
||||||
|
WHERE ck.CharacterID = @CharacterID
|
||||||
|
ORDER BY b.SortOrder, ch.SortOrder, s.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterKnowledge_Save
|
||||||
|
@CharacterKnowledgeID int = NULL,
|
||||||
|
@CharacterID int,
|
||||||
|
@StoryAssetID int = NULL,
|
||||||
|
@PlotThreadID int = NULL,
|
||||||
|
@SceneID int,
|
||||||
|
@KnowledgeStateID int,
|
||||||
|
@SourceEventID int = NULL,
|
||||||
|
@Description nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @CharacterKnowledgeID IS NULL OR @CharacterKnowledgeID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.CharacterKnowledge (CharacterID, StoryAssetID, PlotThreadID, SceneID, KnowledgeStateID, SourceEventID, Description)
|
||||||
|
VALUES (@CharacterID, @StoryAssetID, @PlotThreadID, @SceneID, @KnowledgeStateID, @SourceEventID, @Description);
|
||||||
|
SET @CharacterKnowledgeID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.CharacterKnowledge
|
||||||
|
SET CharacterID = @CharacterID, StoryAssetID = @StoryAssetID, PlotThreadID = @PlotThreadID, SceneID = @SceneID,
|
||||||
|
KnowledgeStateID = @KnowledgeStateID, SourceEventID = @SourceEventID, Description = @Description, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE CharacterKnowledgeID = @CharacterKnowledgeID;
|
||||||
|
END
|
||||||
|
SELECT @CharacterKnowledgeID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Relationship_ListByCharacter
|
||||||
|
@CharacterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT cr.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||||
|
cr.CharacterBID, cb.CharacterName AS CharacterBName, cr.RelationshipTypeID, rt.TypeName AS RelationshipTypeName,
|
||||||
|
cr.IsPermanent, cr.StartSceneID, cr.EndSceneID, cr.IsKnownToReader, cr.Notes, cr.CreatedDate, cr.UpdatedDate, cr.IsArchived
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
|
||||||
|
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
|
||||||
|
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
|
||||||
|
WHERE cr.IsArchived = 0 AND @CharacterID IN (cr.CharacterAID, cr.CharacterBID)
|
||||||
|
ORDER BY ca.CharacterName, cb.CharacterName, rt.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Relationship_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT cr.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||||
|
cr.CharacterBID, cb.CharacterName AS CharacterBName, cr.RelationshipTypeID, rt.TypeName AS RelationshipTypeName,
|
||||||
|
cr.IsPermanent, cr.StartSceneID, cr.EndSceneID, cr.IsKnownToReader, cr.Notes, cr.CreatedDate, cr.UpdatedDate, cr.IsArchived
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
|
||||||
|
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
|
||||||
|
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
|
||||||
|
WHERE cr.ProjectID = @ProjectID AND cr.IsArchived = 0
|
||||||
|
ORDER BY ca.CharacterName, cb.CharacterName, rt.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Relationship_Save
|
||||||
|
@CharacterRelationshipID int = NULL,
|
||||||
|
@ProjectID int,
|
||||||
|
@CharacterAID int,
|
||||||
|
@CharacterBID int,
|
||||||
|
@RelationshipTypeID int,
|
||||||
|
@IsPermanent bit,
|
||||||
|
@StartSceneID int = NULL,
|
||||||
|
@EndSceneID int = NULL,
|
||||||
|
@IsKnownToReader bit,
|
||||||
|
@Notes nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @CharacterRelationshipID IS NULL OR @CharacterRelationshipID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.CharacterRelationships (ProjectID, CharacterAID, CharacterBID, RelationshipTypeID, IsPermanent, StartSceneID, EndSceneID, IsKnownToReader, Notes)
|
||||||
|
VALUES (@ProjectID, @CharacterAID, @CharacterBID, @RelationshipTypeID, @IsPermanent, @StartSceneID, @EndSceneID, @IsKnownToReader, @Notes);
|
||||||
|
SET @CharacterRelationshipID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.CharacterRelationships
|
||||||
|
SET CharacterAID = @CharacterAID, CharacterBID = @CharacterBID, RelationshipTypeID = @RelationshipTypeID,
|
||||||
|
IsPermanent = @IsPermanent, StartSceneID = @StartSceneID, EndSceneID = @EndSceneID,
|
||||||
|
IsKnownToReader = @IsKnownToReader, Notes = @Notes, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE CharacterRelationshipID = @CharacterRelationshipID;
|
||||||
|
END
|
||||||
|
SELECT @CharacterRelationshipID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Relationship_Archive
|
||||||
|
@CharacterRelationshipID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.CharacterRelationships SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE CharacterRelationshipID = @CharacterRelationshipID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.RelationshipEvent_ListByCharacter
|
||||||
|
@CharacterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT re.RelationshipEventID, re.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||||
|
cr.CharacterBID, cb.CharacterName AS CharacterBName, rt.TypeName AS RelationshipTypeName,
|
||||||
|
re.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle,
|
||||||
|
re.RelationshipStateID, rs.StateName AS RelationshipStateName, re.Intensity, re.IsKnownToOtherCharacter,
|
||||||
|
re.Description, re.CreatedDate, re.UpdatedDate
|
||||||
|
FROM dbo.RelationshipEvents re
|
||||||
|
INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID
|
||||||
|
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
|
||||||
|
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
|
||||||
|
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
|
||||||
|
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID
|
||||||
|
INNER JOIN dbo.Chapters ch ON ch.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
|
||||||
|
WHERE @CharacterID IN (cr.CharacterAID, cr.CharacterBID)
|
||||||
|
ORDER BY b.SortOrder, ch.SortOrder, s.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.RelationshipEvent_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT re.RelationshipEventID, re.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||||
|
cr.CharacterBID, cb.CharacterName AS CharacterBName, rt.TypeName AS RelationshipTypeName,
|
||||||
|
re.SceneID, re.RelationshipStateID, rs.StateName AS RelationshipStateName, re.Intensity, re.IsKnownToOtherCharacter,
|
||||||
|
re.Description, re.CreatedDate, re.UpdatedDate
|
||||||
|
FROM dbo.RelationshipEvents re
|
||||||
|
INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID
|
||||||
|
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
|
||||||
|
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
|
||||||
|
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
|
||||||
|
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
|
||||||
|
WHERE re.SceneID = @SceneID
|
||||||
|
ORDER BY ca.CharacterName, cb.CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.RelationshipEvent_Save
|
||||||
|
@RelationshipEventID int = NULL,
|
||||||
|
@CharacterRelationshipID int,
|
||||||
|
@SceneID int,
|
||||||
|
@RelationshipStateID int,
|
||||||
|
@Intensity int = NULL,
|
||||||
|
@IsKnownToOtherCharacter bit,
|
||||||
|
@Description nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @RelationshipEventID IS NULL OR @RelationshipEventID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.RelationshipEvents (CharacterRelationshipID, SceneID, RelationshipStateID, Intensity, IsKnownToOtherCharacter, Description)
|
||||||
|
VALUES (@CharacterRelationshipID, @SceneID, @RelationshipStateID, @Intensity, @IsKnownToOtherCharacter, @Description);
|
||||||
|
SET @RelationshipEventID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.RelationshipEvents
|
||||||
|
SET CharacterRelationshipID = @CharacterRelationshipID, SceneID = @SceneID, RelationshipStateID = @RelationshipStateID,
|
||||||
|
Intensity = @Intensity, IsKnownToOtherCharacter = @IsKnownToOtherCharacter,
|
||||||
|
Description = @Description, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE RelationshipEventID = @RelationshipEventID;
|
||||||
|
END
|
||||||
|
SELECT @RelationshipEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.CharacterTimeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT TOP (12) CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||||
|
Height, EyeColour, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Characters
|
||||||
|
WHERE ProjectID = @ProjectID AND IsArchived = 0
|
||||||
|
ORDER BY CharacterName;
|
||||||
|
|
||||||
|
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
|
||||||
|
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
|
||||||
|
sc.AppearanceNotes, sc.OutfitDescription, sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes,
|
||||||
|
sc.CreatedDate, sc.UpdatedDate
|
||||||
|
FROM dbo.SceneCharacters sc
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = sc.CharacterID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = sc.SceneID
|
||||||
|
INNER JOIN dbo.Chapters ch ON ch.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
|
||||||
|
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||||
|
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||||
|
WHERE c.ProjectID = @ProjectID AND c.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, ch.SortOrder, s.SortOrder, c.CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEvent_Save
|
||||||
|
@AssetCustodyEventID int = NULL,
|
||||||
|
@StoryAssetID int,
|
||||||
|
@SceneID int,
|
||||||
|
@AssetCustodyEventTypeID int,
|
||||||
|
@Description nvarchar(max) = NULL,
|
||||||
|
@CustodianNames nvarchar(max) = NULL,
|
||||||
|
@CustodianCharacterIds nvarchar(max) = NULL,
|
||||||
|
@CustodyRoleID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF @AssetCustodyEventID IS NULL OR @AssetCustodyEventID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.AssetCustodyEvents (StoryAssetID, SceneID, AssetCustodyEventTypeID, Description)
|
||||||
|
VALUES (@StoryAssetID, @SceneID, @AssetCustodyEventTypeID, @Description);
|
||||||
|
SET @AssetCustodyEventID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.AssetCustodyEvents
|
||||||
|
SET StoryAssetID = @StoryAssetID, SceneID = @SceneID, AssetCustodyEventTypeID = @AssetCustodyEventTypeID,
|
||||||
|
Description = @Description, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE AssetCustodyEventID = @AssetCustodyEventID;
|
||||||
|
END
|
||||||
|
|
||||||
|
DELETE FROM dbo.AssetCustodyEventCharacters WHERE AssetCustodyEventID = @AssetCustodyEventID;
|
||||||
|
|
||||||
|
INSERT dbo.AssetCustodyEventCharacters (AssetCustodyEventID, CharacterID, CharacterNameText, CustodyRoleID)
|
||||||
|
SELECT @AssetCustodyEventID, TRY_CAST(value AS int), NULL, @CustodyRoleID
|
||||||
|
FROM STRING_SPLIT(ISNULL(@CustodianCharacterIds, N''), N',')
|
||||||
|
WHERE TRY_CAST(value AS int) IS NOT NULL;
|
||||||
|
|
||||||
|
INSERT dbo.AssetCustodyEventCharacters (AssetCustodyEventID, CharacterNameText, CustodyRoleID)
|
||||||
|
SELECT @AssetCustodyEventID, LTRIM(RTRIM(value)), @CustodyRoleID
|
||||||
|
FROM STRING_SPLIT(REPLACE(ISNULL(@CustodianNames, N''), CHAR(10), N','), N',')
|
||||||
|
WHERE LTRIM(RTRIM(value)) <> N'';
|
||||||
|
|
||||||
|
SELECT @AssetCustodyEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEvent_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID,
|
||||||
|
ace.AssetCustodyEventTypeID, acet.TypeName AS CustodyEventTypeName, ace.Description,
|
||||||
|
STRING_AGG(CONCAT(COALESCE(c.CharacterName, aec.CharacterNameText), N' (', cr.RoleName, N')'), N', ') AS CustodianSummary,
|
||||||
|
ace.CreatedDate, ace.UpdatedDate
|
||||||
|
FROM dbo.AssetCustodyEvents ace
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetCustodyEventTypes acet ON acet.AssetCustodyEventTypeID = ace.AssetCustodyEventTypeID
|
||||||
|
LEFT JOIN dbo.AssetCustodyEventCharacters aec ON aec.AssetCustodyEventID = ace.AssetCustodyEventID
|
||||||
|
LEFT JOIN dbo.Characters c ON c.CharacterID = aec.CharacterID
|
||||||
|
LEFT JOIN dbo.CustodyRoles cr ON cr.CustodyRoleID = aec.CustodyRoleID
|
||||||
|
WHERE ace.SceneID = @SceneID
|
||||||
|
GROUP BY ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID, ace.AssetCustodyEventTypeID, acet.TypeName, ace.Description, ace.CreatedDate, ace.UpdatedDate
|
||||||
|
ORDER BY sa.AssetName, ace.AssetCustodyEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetCustodyEvent_ListByAsset
|
||||||
|
@StoryAssetID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID,
|
||||||
|
ace.AssetCustodyEventTypeID, acet.TypeName AS CustodyEventTypeName, ace.Description,
|
||||||
|
STRING_AGG(CONCAT(COALESCE(c.CharacterName, aec.CharacterNameText), N' (', cr.RoleName, N')'), N', ') AS CustodianSummary,
|
||||||
|
ace.CreatedDate, ace.UpdatedDate, b.BookTitle, ch.ChapterNumber, s.SceneNumber, s.SceneTitle
|
||||||
|
FROM dbo.AssetCustodyEvents ace
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetCustodyEventTypes acet ON acet.AssetCustodyEventTypeID = ace.AssetCustodyEventTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ace.SceneID
|
||||||
|
INNER JOIN dbo.Chapters ch ON ch.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
|
||||||
|
LEFT JOIN dbo.AssetCustodyEventCharacters aec ON aec.AssetCustodyEventID = ace.AssetCustodyEventID
|
||||||
|
LEFT JOIN dbo.Characters c ON c.CharacterID = aec.CharacterID
|
||||||
|
LEFT JOIN dbo.CustodyRoles cr ON cr.CustodyRoleID = aec.CustodyRoleID
|
||||||
|
WHERE ace.StoryAssetID = @StoryAssetID
|
||||||
|
GROUP BY ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID, ace.AssetCustodyEventTypeID, acet.TypeName, ace.Description, ace.CreatedDate, ace.UpdatedDate, b.BookTitle, ch.ChapterNumber, s.SceneNumber, s.SceneTitle, b.SortOrder, ch.SortOrder, s.SortOrder
|
||||||
|
ORDER BY b.SortOrder, ch.SortOrder, s.SortOrder, ace.AssetCustodyEventID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
732
PlotLine/Sql/007_Phase1F_Locations.sql
Normal file
732
PlotLine/Sql/007_Phase1F_Locations.sql
Normal file
@ -0,0 +1,732 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.LocationTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.LocationTypes
|
||||||
|
(
|
||||||
|
LocationTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_LocationTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_LocationTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_LocationTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Locations', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Locations
|
||||||
|
(
|
||||||
|
LocationID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Locations PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
ParentLocationID int NULL,
|
||||||
|
LocationName nvarchar(200) NOT NULL,
|
||||||
|
LocationTypeID int NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_Locations_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_Locations_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_Locations_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_Locations_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_Locations_Parent FOREIGN KEY (ParentLocationID) REFERENCES dbo.Locations(LocationID),
|
||||||
|
CONSTRAINT FK_Locations_Types FOREIGN KEY (LocationTypeID) REFERENCES dbo.LocationTypes(LocationTypeID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.LocationRelationshipTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.LocationRelationshipTypes
|
||||||
|
(
|
||||||
|
LocationRelationshipTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_LocationRelationshipTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_LocationRelationshipTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_LocationRelationshipTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.LocationRelationships', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.LocationRelationships
|
||||||
|
(
|
||||||
|
LocationRelationshipID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_LocationRelationships PRIMARY KEY,
|
||||||
|
FromLocationID int NOT NULL,
|
||||||
|
ToLocationID int NOT NULL,
|
||||||
|
LocationRelationshipTypeID int NOT NULL,
|
||||||
|
IsBidirectional bit NOT NULL CONSTRAINT DF_LocationRelationships_IsBidirectional DEFAULT 0,
|
||||||
|
Strength int NULL,
|
||||||
|
CanHearNormalSpeech bit NOT NULL CONSTRAINT DF_LocationRelationships_CanHearNormalSpeech DEFAULT 0,
|
||||||
|
CanHearLoudNoise bit NOT NULL CONSTRAINT DF_LocationRelationships_CanHearLoudNoise DEFAULT 0,
|
||||||
|
CanSeeMovement bit NOT NULL CONSTRAINT DF_LocationRelationships_CanSeeMovement DEFAULT 0,
|
||||||
|
CanSeeClearly bit NOT NULL CONSTRAINT DF_LocationRelationships_CanSeeClearly DEFAULT 0,
|
||||||
|
CanTravelDirectly bit NOT NULL CONSTRAINT DF_LocationRelationships_CanTravelDirectly DEFAULT 0,
|
||||||
|
Notes nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_LocationRelationships_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_LocationRelationships_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_LocationRelationships_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_LocationRelationships_From FOREIGN KEY (FromLocationID) REFERENCES dbo.Locations(LocationID),
|
||||||
|
CONSTRAINT FK_LocationRelationships_To FOREIGN KEY (ToLocationID) REFERENCES dbo.Locations(LocationID),
|
||||||
|
CONSTRAINT FK_LocationRelationships_Types FOREIGN KEY (LocationRelationshipTypeID) REFERENCES dbo.LocationRelationshipTypes(LocationRelationshipTypeID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.SceneAssetLocations', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.SceneAssetLocations
|
||||||
|
(
|
||||||
|
SceneAssetLocationID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneAssetLocations PRIMARY KEY,
|
||||||
|
SceneID int NOT NULL,
|
||||||
|
StoryAssetID int NOT NULL,
|
||||||
|
LocationID int NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_SceneAssetLocations_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_SceneAssetLocations_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_SceneAssetLocations_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_SceneAssetLocations_StoryAssets FOREIGN KEY (StoryAssetID) REFERENCES dbo.StoryAssets(StoryAssetID),
|
||||||
|
CONSTRAINT FK_SceneAssetLocations_Locations FOREIGN KEY (LocationID) REFERENCES dbo.Locations(LocationID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.LocationTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Country', 10), (N'Region', 20), (N'Town', 30), (N'Village', 40), (N'Street', 50),
|
||||||
|
(N'Building', 60), (N'Floor', 70), (N'Room', 80), (N'Outdoor Area', 90), (N'Vehicle Interior', 100),
|
||||||
|
(N'Landmark', 110), (N'Other', 120)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.LocationRelationshipTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Adjacent To', 10), (N'Above', 20), (N'Below', 30), (N'Connected To', 40), (N'Visible From', 50),
|
||||||
|
(N'Audible From', 60), (N'Near', 70), (N'Leads To', 80), (N'Overlooks', 90),
|
||||||
|
(N'Same Building As', 100), (N'Same Area As', 110), (N'Secret Passage', 120), (N'Road Connection', 130)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER VIEW dbo.LocationPaths
|
||||||
|
AS
|
||||||
|
WITH LocationTree AS
|
||||||
|
(
|
||||||
|
SELECT LocationID, ProjectID, ParentLocationID, LocationName, LocationTypeID, Description,
|
||||||
|
CAST(LocationName AS nvarchar(max)) AS LocationPath,
|
||||||
|
CAST(RIGHT('000000' + CAST(LocationID AS varchar(6)), 6) AS nvarchar(max)) AS SortPath,
|
||||||
|
0 AS Depth
|
||||||
|
FROM dbo.Locations
|
||||||
|
WHERE ParentLocationID IS NULL AND IsArchived = 0
|
||||||
|
UNION ALL
|
||||||
|
SELECT child.LocationID, child.ProjectID, child.ParentLocationID, child.LocationName, child.LocationTypeID, child.Description,
|
||||||
|
CAST(parent.LocationPath + N' > ' + child.LocationName AS nvarchar(max)),
|
||||||
|
CAST(parent.SortPath + N'.' + RIGHT('000000' + CAST(child.LocationID AS varchar(6)), 6) AS nvarchar(max)),
|
||||||
|
parent.Depth + 1
|
||||||
|
FROM dbo.Locations child
|
||||||
|
INNER JOIN LocationTree parent ON parent.LocationID = child.ParentLocationID
|
||||||
|
WHERE child.IsArchived = 0
|
||||||
|
)
|
||||||
|
SELECT LocationID, ProjectID, ParentLocationID, LocationName, LocationTypeID, Description, LocationPath, SortPath, Depth
|
||||||
|
FROM LocationTree;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationLookup_All
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT LocationTypeID, TypeName, SortOrder, IsActive FROM dbo.LocationTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT LocationRelationshipTypeID, TypeName, SortOrder, IsActive FROM dbo.LocationRelationshipTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Location_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT lp.LocationID, lp.ProjectID, lp.ParentLocationID, lp.LocationName, lp.LocationTypeID, lt.TypeName AS LocationTypeName,
|
||||||
|
lp.Description, lp.LocationPath, lp.Depth, l.CreatedDate, l.UpdatedDate, l.IsArchived
|
||||||
|
FROM dbo.LocationPaths lp
|
||||||
|
INNER JOIN dbo.Locations l ON l.LocationID = lp.LocationID
|
||||||
|
LEFT JOIN dbo.LocationTypes lt ON lt.LocationTypeID = lp.LocationTypeID
|
||||||
|
WHERE lp.ProjectID = @ProjectID
|
||||||
|
ORDER BY lp.SortPath;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationTree_GetByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
EXEC dbo.Location_ListByProject @ProjectID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Location_Get
|
||||||
|
@LocationID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT lp.LocationID, lp.ProjectID, lp.ParentLocationID, parent.LocationName AS ParentLocationName,
|
||||||
|
lp.LocationName, lp.LocationTypeID, lt.TypeName AS LocationTypeName, lp.Description, lp.LocationPath, lp.Depth,
|
||||||
|
l.CreatedDate, l.UpdatedDate, l.IsArchived
|
||||||
|
FROM dbo.LocationPaths lp
|
||||||
|
INNER JOIN dbo.Locations l ON l.LocationID = lp.LocationID
|
||||||
|
LEFT JOIN dbo.Locations parent ON parent.LocationID = lp.ParentLocationID
|
||||||
|
LEFT JOIN dbo.LocationTypes lt ON lt.LocationTypeID = lp.LocationTypeID
|
||||||
|
WHERE lp.LocationID = @LocationID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Location_Save
|
||||||
|
@LocationID int = NULL,
|
||||||
|
@ProjectID int,
|
||||||
|
@ParentLocationID int = NULL,
|
||||||
|
@LocationName nvarchar(200),
|
||||||
|
@LocationTypeID int = NULL,
|
||||||
|
@Description nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @LocationID IS NULL OR @LocationID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.Locations (ProjectID, ParentLocationID, LocationName, LocationTypeID, Description)
|
||||||
|
VALUES (@ProjectID, @ParentLocationID, @LocationName, @LocationTypeID, @Description);
|
||||||
|
SET @LocationID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.Locations
|
||||||
|
SET ParentLocationID = @ParentLocationID, LocationName = @LocationName, LocationTypeID = @LocationTypeID,
|
||||||
|
Description = @Description, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE LocationID = @LocationID;
|
||||||
|
END
|
||||||
|
SELECT @LocationID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Location_Archive
|
||||||
|
@LocationID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.Locations SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE LocationID = @LocationID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationRelationship_ListByLocation
|
||||||
|
@LocationID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT lr.LocationRelationshipID, lr.FromLocationID, fromLocation.LocationName AS FromLocationName,
|
||||||
|
lr.ToLocationID, toLocation.LocationName AS ToLocationName, lr.LocationRelationshipTypeID,
|
||||||
|
lrt.TypeName AS RelationshipTypeName, lr.IsBidirectional, lr.Strength,
|
||||||
|
lr.CanHearNormalSpeech, lr.CanHearLoudNoise, lr.CanSeeMovement, lr.CanSeeClearly, lr.CanTravelDirectly,
|
||||||
|
lr.Notes, lr.CreatedDate, lr.UpdatedDate, lr.IsArchived
|
||||||
|
FROM dbo.LocationRelationships lr
|
||||||
|
INNER JOIN dbo.Locations fromLocation ON fromLocation.LocationID = lr.FromLocationID
|
||||||
|
INNER JOIN dbo.Locations toLocation ON toLocation.LocationID = lr.ToLocationID
|
||||||
|
INNER JOIN dbo.LocationRelationshipTypes lrt ON lrt.LocationRelationshipTypeID = lr.LocationRelationshipTypeID
|
||||||
|
WHERE lr.IsArchived = 0 AND @LocationID IN (lr.FromLocationID, lr.ToLocationID)
|
||||||
|
ORDER BY lrt.SortOrder, fromLocation.LocationName, toLocation.LocationName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationRelationship_Save
|
||||||
|
@LocationRelationshipID int = NULL,
|
||||||
|
@FromLocationID int,
|
||||||
|
@ToLocationID int,
|
||||||
|
@LocationRelationshipTypeID int,
|
||||||
|
@IsBidirectional bit,
|
||||||
|
@Strength int = NULL,
|
||||||
|
@CanHearNormalSpeech bit,
|
||||||
|
@CanHearLoudNoise bit,
|
||||||
|
@CanSeeMovement bit,
|
||||||
|
@CanSeeClearly bit,
|
||||||
|
@CanTravelDirectly bit,
|
||||||
|
@Notes nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @LocationRelationshipID IS NULL OR @LocationRelationshipID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.LocationRelationships (FromLocationID, ToLocationID, LocationRelationshipTypeID, IsBidirectional, Strength,
|
||||||
|
CanHearNormalSpeech, CanHearLoudNoise, CanSeeMovement, CanSeeClearly, CanTravelDirectly, Notes)
|
||||||
|
VALUES (@FromLocationID, @ToLocationID, @LocationRelationshipTypeID, @IsBidirectional, @Strength,
|
||||||
|
@CanHearNormalSpeech, @CanHearLoudNoise, @CanSeeMovement, @CanSeeClearly, @CanTravelDirectly, @Notes);
|
||||||
|
SET @LocationRelationshipID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.LocationRelationships
|
||||||
|
SET FromLocationID = @FromLocationID, ToLocationID = @ToLocationID,
|
||||||
|
LocationRelationshipTypeID = @LocationRelationshipTypeID, IsBidirectional = @IsBidirectional,
|
||||||
|
Strength = @Strength, CanHearNormalSpeech = @CanHearNormalSpeech, CanHearLoudNoise = @CanHearLoudNoise,
|
||||||
|
CanSeeMovement = @CanSeeMovement, CanSeeClearly = @CanSeeClearly, CanTravelDirectly = @CanTravelDirectly,
|
||||||
|
Notes = @Notes, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE LocationRelationshipID = @LocationRelationshipID;
|
||||||
|
END
|
||||||
|
SELECT @LocationRelationshipID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_Save
|
||||||
|
@SceneID int = NULL, @ChapterID int, @SceneNumber decimal(10,2), @SceneTitle nvarchar(200), @Summary nvarchar(max) = NULL,
|
||||||
|
@TimeModeID int, @StartDateTime datetime2 = NULL, @EndDateTime datetime2 = NULL, @DurationAmount decimal(10,2) = NULL,
|
||||||
|
@DurationUnitID int = NULL, @RelativeTimeText nvarchar(200) = NULL, @TimeConfidenceID int,
|
||||||
|
@ScenePurposeNotes nvarchar(max) = NULL, @SceneOutcomeNotes nvarchar(max) = NULL, @RevisionStatusID int,
|
||||||
|
@PrimaryLocationID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @NextSortOrder int;
|
||||||
|
IF @SceneID IS NULL OR @SceneID = 0
|
||||||
|
BEGIN
|
||||||
|
SELECT @NextSortOrder = ISNULL(MAX(SortOrder), 0) + 10 FROM dbo.Scenes WHERE ChapterID = @ChapterID;
|
||||||
|
INSERT dbo.Scenes (ChapterID, SceneNumber, SceneTitle, Summary, SortOrder, TimeModeID, StartDateTime, EndDateTime,
|
||||||
|
DurationAmount, DurationUnitID, RelativeTimeText, TimeConfidenceID, ScenePurposeNotes, SceneOutcomeNotes, RevisionStatusID, PrimaryLocationID)
|
||||||
|
VALUES (@ChapterID, @SceneNumber, @SceneTitle, @Summary, @NextSortOrder, @TimeModeID, @StartDateTime, @EndDateTime,
|
||||||
|
@DurationAmount, @DurationUnitID, @RelativeTimeText, @TimeConfidenceID, @ScenePurposeNotes, @SceneOutcomeNotes, @RevisionStatusID, @PrimaryLocationID);
|
||||||
|
SET @SceneID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.Scenes
|
||||||
|
SET SceneNumber = @SceneNumber, SceneTitle = @SceneTitle, Summary = @Summary, TimeModeID = @TimeModeID,
|
||||||
|
StartDateTime = @StartDateTime, EndDateTime = @EndDateTime, DurationAmount = @DurationAmount, DurationUnitID = @DurationUnitID,
|
||||||
|
RelativeTimeText = @RelativeTimeText, TimeConfidenceID = @TimeConfidenceID, ScenePurposeNotes = @ScenePurposeNotes,
|
||||||
|
SceneOutcomeNotes = @SceneOutcomeNotes, RevisionStatusID = @RevisionStatusID, PrimaryLocationID = @PrimaryLocationID,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneID = @SceneID;
|
||||||
|
END
|
||||||
|
SELECT @SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneLocation_SavePrimary
|
||||||
|
@SceneID int,
|
||||||
|
@PrimaryLocationID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.Scenes
|
||||||
|
SET PrimaryLocationID = @PrimaryLocationID, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneID = @SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_Get
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
EXEC dbo.SceneMetric_EnsureDefaults @SceneID;
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID, s.PrimaryLocationID,
|
||||||
|
location.LocationName AS PrimaryLocationName, location.LocationPath AS PrimaryLocationPath,
|
||||||
|
s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime, s.EndDateTime, s.DurationAmount, s.DurationUnitID,
|
||||||
|
du.DurationUnitName, s.RelativeTimeText, s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes,
|
||||||
|
s.SceneOutcomeNotes, s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = s.PrimaryLocationID
|
||||||
|
WHERE s.SceneID = @SceneID AND s.IsArchived = 0;
|
||||||
|
SELECT ScenePurposeTypeID FROM dbo.ScenePurposes WHERE SceneID = @SceneID;
|
||||||
|
EXEC dbo.SceneMetric_ListByScene @SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneCharacter_Save
|
||||||
|
@SceneCharacterID int = NULL, @SceneID int, @CharacterID int, @RoleInSceneTypeID int = NULL, @PresenceTypeID int = NULL,
|
||||||
|
@LocationID int = NULL, @EntryLocationID int = NULL, @ExitLocationID int = NULL,
|
||||||
|
@AppearanceNotes nvarchar(max) = NULL, @OutfitDescription nvarchar(max) = NULL, @PhysicalCondition nvarchar(max) = NULL,
|
||||||
|
@EmotionalState nvarchar(max) = NULL, @KnowledgeNotes nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @SceneCharacterID IS NULL OR @SceneCharacterID = 0
|
||||||
|
SELECT @SceneCharacterID = SceneCharacterID FROM dbo.SceneCharacters WHERE SceneID = @SceneID AND CharacterID = @CharacterID;
|
||||||
|
IF @SceneCharacterID IS NULL OR @SceneCharacterID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.SceneCharacters (SceneID, CharacterID, RoleInSceneTypeID, PresenceTypeID, LocationID, EntryLocationID, ExitLocationID,
|
||||||
|
AppearanceNotes, OutfitDescription, PhysicalCondition, EmotionalState, KnowledgeNotes)
|
||||||
|
VALUES (@SceneID, @CharacterID, @RoleInSceneTypeID, @PresenceTypeID, @LocationID, @EntryLocationID, @ExitLocationID,
|
||||||
|
@AppearanceNotes, @OutfitDescription, @PhysicalCondition, @EmotionalState, @KnowledgeNotes);
|
||||||
|
SET @SceneCharacterID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.SceneCharacters
|
||||||
|
SET CharacterID = @CharacterID, RoleInSceneTypeID = @RoleInSceneTypeID, PresenceTypeID = @PresenceTypeID,
|
||||||
|
LocationID = @LocationID, EntryLocationID = @EntryLocationID, ExitLocationID = @ExitLocationID,
|
||||||
|
AppearanceNotes = @AppearanceNotes, OutfitDescription = @OutfitDescription, PhysicalCondition = @PhysicalCondition,
|
||||||
|
EmotionalState = @EmotionalState, KnowledgeNotes = @KnowledgeNotes, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneCharacterID = @SceneCharacterID;
|
||||||
|
END
|
||||||
|
SELECT @SceneCharacterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneCharacter_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
|
||||||
|
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
|
||||||
|
sc.LocationID, location.LocationName, location.LocationPath, sc.EntryLocationID, entry.LocationName AS EntryLocationName,
|
||||||
|
sc.ExitLocationID, exitLocation.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
|
||||||
|
sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, sc.CreatedDate, sc.UpdatedDate
|
||||||
|
FROM dbo.SceneCharacters sc
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = sc.CharacterID
|
||||||
|
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||||
|
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sc.LocationID
|
||||||
|
LEFT JOIN dbo.LocationPaths entry ON entry.LocationID = sc.EntryLocationID
|
||||||
|
LEFT JOIN dbo.LocationPaths exitLocation ON exitLocation.LocationID = sc.ExitLocationID
|
||||||
|
WHERE sc.SceneID = @SceneID AND c.IsArchived = 0
|
||||||
|
ORDER BY role.SortOrder, c.CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_Save
|
||||||
|
@StoryAssetID int = NULL, @ProjectID int, @AssetName nvarchar(200), @AssetKindID int, @Description nvarchar(max) = NULL,
|
||||||
|
@Importance int, @CurrentStateID int = NULL, @CurrentLocationID int = NULL, @IsResolved bit
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @StoryAssetID IS NULL OR @StoryAssetID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.StoryAssets (ProjectID, AssetName, AssetKindID, Description, Importance, CurrentStateID, CurrentLocationID, IsResolved)
|
||||||
|
VALUES (@ProjectID, @AssetName, @AssetKindID, @Description, @Importance, @CurrentStateID, @CurrentLocationID, @IsResolved);
|
||||||
|
SET @StoryAssetID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.StoryAssets
|
||||||
|
SET AssetName = @AssetName, AssetKindID = @AssetKindID, Description = @Description, Importance = @Importance,
|
||||||
|
CurrentStateID = @CurrentStateID, CurrentLocationID = @CurrentLocationID, IsResolved = @IsResolved, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE StoryAssetID = @StoryAssetID;
|
||||||
|
END
|
||||||
|
SELECT @StoryAssetID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneAssetLocation_Save
|
||||||
|
@SceneAssetLocationID int = NULL,
|
||||||
|
@SceneID int,
|
||||||
|
@StoryAssetID int,
|
||||||
|
@LocationID int,
|
||||||
|
@Description nvarchar(max) = NULL,
|
||||||
|
@UpdateCurrentLocation bit = 1
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @SceneAssetLocationID IS NULL OR @SceneAssetLocationID = 0
|
||||||
|
SELECT @SceneAssetLocationID = SceneAssetLocationID FROM dbo.SceneAssetLocations WHERE SceneID = @SceneID AND StoryAssetID = @StoryAssetID;
|
||||||
|
IF @SceneAssetLocationID IS NULL OR @SceneAssetLocationID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.SceneAssetLocations (SceneID, StoryAssetID, LocationID, Description)
|
||||||
|
VALUES (@SceneID, @StoryAssetID, @LocationID, @Description);
|
||||||
|
SET @SceneAssetLocationID = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.SceneAssetLocations SET LocationID = @LocationID, Description = @Description, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneAssetLocationID = @SceneAssetLocationID;
|
||||||
|
END
|
||||||
|
IF @UpdateCurrentLocation = 1
|
||||||
|
UPDATE dbo.StoryAssets SET CurrentLocationID = @LocationID, UpdatedDate = SYSUTCDATETIME() WHERE StoryAssetID = @StoryAssetID;
|
||||||
|
SELECT @SceneAssetLocationID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneAssetLocation_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sal.SceneAssetLocationID, sal.SceneID, sal.StoryAssetID, sa.AssetName, sal.LocationID,
|
||||||
|
location.LocationName, location.LocationPath, sal.Description, sal.CreatedDate, sal.UpdatedDate
|
||||||
|
FROM dbo.SceneAssetLocations sal
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID
|
||||||
|
INNER JOIN dbo.LocationPaths location ON location.LocationID = sal.LocationID
|
||||||
|
WHERE sal.SceneID = @SceneID
|
||||||
|
ORDER BY location.LocationPath, sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Timeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived FROM dbo.Projects WHERE ProjectID = @ProjectID AND IsArchived = 0;
|
||||||
|
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
|
||||||
|
FROM dbo.Books WHERE ProjectID = @ProjectID AND IsArchived = 0 AND (@BookID IS NULL OR BookID = @BookID) ORDER BY SortOrder, BookNumber;
|
||||||
|
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary, c.SortOrder,
|
||||||
|
c.RevisionStatusID, rs.StatusName AS RevisionStatusName, c.CreatedDate, c.UpdatedDate, c.IsArchived
|
||||||
|
FROM dbo.Chapters c INNER JOIN dbo.Books b ON b.BookID = c.BookID INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND c.IsArchived = 0 AND b.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder;
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID, s.PrimaryLocationID,
|
||||||
|
location.LocationName AS PrimaryLocationName, location.LocationPath AS PrimaryLocationPath,
|
||||||
|
s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime, s.EndDateTime, s.DurationAmount, s.DurationUnitID,
|
||||||
|
du.DurationUnitName, s.RelativeTimeText, s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes,
|
||||||
|
s.SceneOutcomeNotes, s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = s.PrimaryLocationID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND s.IsArchived = 0 AND c.IsArchived = 0 AND b.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder;
|
||||||
|
SELECT sp.SceneID, spt.ScenePurposeTypeID, spt.PurposeName, spt.SortOrder
|
||||||
|
FROM dbo.ScenePurposes sp INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = sp.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY spt.SortOrder;
|
||||||
|
SELECT MetricTypeID, ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive FROM dbo.SceneMetricTypes WHERE IsActive = 1 AND (ProjectID IS NULL OR ProjectID = @ProjectID) ORDER BY SortOrder;
|
||||||
|
SELECT smv.SceneID, smv.MetricTypeID, smt.MetricName, smv.Value, smv.Notes
|
||||||
|
FROM dbo.SceneMetricValues smv INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = smv.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND (@BookID IS NULL OR b.BookID = @BookID);
|
||||||
|
SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID, plt.TypeName AS PlotLineTypeName,
|
||||||
|
pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName, pl.SortOrder, pl.Colour, pl.IsMainPlot, pl.IsVisibleOnTimeline,
|
||||||
|
pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
|
||||||
|
FROM dbo.PlotLines pl INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID LEFT JOIN dbo.Books b ON b.BookID = pl.BookID LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
|
||||||
|
WHERE pl.ProjectID = @ProjectID AND pl.IsArchived = 0 AND pl.IsVisibleOnTimeline = 1 AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID)
|
||||||
|
ORDER BY pl.SortOrder, pl.PlotLineName;
|
||||||
|
SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pt.PlotLineID, pl.PlotLineName, pl.Colour, te.SceneID, te.EventTypeID,
|
||||||
|
tet.TypeName AS EventTypeName, tet.MarkerText, te.EventTitle, te.EventDescription, te.CreatedDate, te.UpdatedDate
|
||||||
|
FROM dbo.ThreadEvents te INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID INNER JOIN dbo.Scenes s ON s.SceneID = te.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND pl.IsArchived = 0 AND pt.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY pl.SortOrder, pt.ThreadTitle, b.SortOrder, c.SortOrder, s.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationRelationship_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT lr.LocationRelationshipID, lr.FromLocationID, fromLocation.LocationName AS FromLocationName,
|
||||||
|
lr.ToLocationID, toLocation.LocationName AS ToLocationName, lr.LocationRelationshipTypeID,
|
||||||
|
lrt.TypeName AS RelationshipTypeName, lr.IsBidirectional, lr.Strength,
|
||||||
|
lr.CanHearNormalSpeech, lr.CanHearLoudNoise, lr.CanSeeMovement, lr.CanSeeClearly, lr.CanTravelDirectly,
|
||||||
|
lr.Notes, lr.CreatedDate, lr.UpdatedDate, lr.IsArchived
|
||||||
|
FROM dbo.LocationRelationships lr
|
||||||
|
INNER JOIN dbo.Locations fromLocation ON fromLocation.LocationID = lr.FromLocationID
|
||||||
|
INNER JOIN dbo.Locations toLocation ON toLocation.LocationID = lr.ToLocationID
|
||||||
|
INNER JOIN dbo.LocationRelationshipTypes lrt ON lrt.LocationRelationshipTypeID = lr.LocationRelationshipTypeID
|
||||||
|
WHERE lr.IsArchived = 0 AND fromLocation.ProjectID = @ProjectID
|
||||||
|
ORDER BY lrt.SortOrder, fromLocation.LocationName, toLocation.LocationName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationRelationship_Archive
|
||||||
|
@LocationRelationshipID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.LocationRelationships
|
||||||
|
SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE LocationRelationshipID = @LocationRelationshipID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneAssetLocation_Delete
|
||||||
|
@SceneAssetLocationID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DELETE dbo.SceneAssetLocations WHERE SceneAssetLocationID = @SceneAssetLocationID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneAssetLocation_ListByLocation
|
||||||
|
@LocationID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sal.SceneAssetLocationID, sal.SceneID, sal.StoryAssetID, sa.AssetName, sal.LocationID,
|
||||||
|
location.LocationName, location.LocationPath, sal.Description, sal.CreatedDate, sal.UpdatedDate
|
||||||
|
FROM dbo.SceneAssetLocations sal
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID
|
||||||
|
INNER JOIN dbo.LocationPaths location ON location.LocationID = sal.LocationID
|
||||||
|
WHERE sal.LocationID = @LocationID
|
||||||
|
ORDER BY sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationScene_ListByLocation
|
||||||
|
@LocationID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID, s.PrimaryLocationID,
|
||||||
|
location.LocationName AS PrimaryLocationName, location.LocationPath AS PrimaryLocationPath,
|
||||||
|
s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime, s.EndDateTime, s.DurationAmount, s.DurationUnitID,
|
||||||
|
du.DurationUnitName, s.RelativeTimeText, s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes,
|
||||||
|
s.SceneOutcomeNotes, s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||||
|
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = s.PrimaryLocationID
|
||||||
|
WHERE s.PrimaryLocationID = @LocationID AND s.IsArchived = 0
|
||||||
|
ORDER BY s.SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationCharacter_ListByLocation
|
||||||
|
@LocationID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
|
||||||
|
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
|
||||||
|
sc.LocationID, location.LocationName, location.LocationPath, sc.EntryLocationID, entry.LocationName AS EntryLocationName,
|
||||||
|
sc.ExitLocationID, exitLocation.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
|
||||||
|
sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle,
|
||||||
|
sc.CreatedDate, sc.UpdatedDate
|
||||||
|
FROM dbo.SceneCharacters sc
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = sc.CharacterID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = sc.SceneID
|
||||||
|
INNER JOIN dbo.Chapters ch ON ch.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
|
||||||
|
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||||
|
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sc.LocationID
|
||||||
|
LEFT JOIN dbo.LocationPaths entry ON entry.LocationID = sc.EntryLocationID
|
||||||
|
LEFT JOIN dbo.LocationPaths exitLocation ON exitLocation.LocationID = sc.ExitLocationID
|
||||||
|
WHERE sc.LocationID = @LocationID AND c.IsArchived = 0
|
||||||
|
ORDER BY b.SortOrder, ch.SortOrder, s.SortOrder, c.CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.LocationCharacter_ListByScene
|
||||||
|
@SceneID int,
|
||||||
|
@LocationID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
|
||||||
|
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
|
||||||
|
sc.LocationID, location.LocationName, location.LocationPath, sc.EntryLocationID, entry.LocationName AS EntryLocationName,
|
||||||
|
sc.ExitLocationID, exitLocation.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
|
||||||
|
sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, sc.CreatedDate, sc.UpdatedDate
|
||||||
|
FROM dbo.SceneCharacters sc
|
||||||
|
INNER JOIN dbo.Characters c ON c.CharacterID = sc.CharacterID
|
||||||
|
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||||
|
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sc.LocationID
|
||||||
|
LEFT JOIN dbo.LocationPaths entry ON entry.LocationID = sc.EntryLocationID
|
||||||
|
LEFT JOIN dbo.LocationPaths exitLocation ON exitLocation.LocationID = sc.ExitLocationID
|
||||||
|
WHERE sc.SceneID = @SceneID AND (@LocationID IS NULL OR sc.LocationID = @LocationID) AND c.IsArchived = 0
|
||||||
|
ORDER BY role.SortOrder, c.CharacterName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneAssetLocation_ListBySceneLocation
|
||||||
|
@SceneID int,
|
||||||
|
@LocationID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sal.SceneAssetLocationID, sal.SceneID, sal.StoryAssetID, sa.AssetName, sal.LocationID,
|
||||||
|
location.LocationName, location.LocationPath, sal.Description, sal.CreatedDate, sal.UpdatedDate
|
||||||
|
FROM dbo.SceneAssetLocations sal
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID
|
||||||
|
INNER JOIN dbo.LocationPaths location ON location.LocationID = sal.LocationID
|
||||||
|
WHERE sal.SceneID = @SceneID AND (@LocationID IS NULL OR sal.LocationID = @LocationID)
|
||||||
|
ORDER BY location.LocationPath, sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
|
||||||
|
sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
|
||||||
|
location.LocationName AS CurrentLocationName, location.LocationPath AS CurrentLocationPath,
|
||||||
|
sa.IsResolved, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sa.CurrentLocationID
|
||||||
|
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0
|
||||||
|
ORDER BY sa.Importance DESC, sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_Get
|
||||||
|
@StoryAssetID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
|
||||||
|
sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
|
||||||
|
location.LocationName AS CurrentLocationName, location.LocationPath AS CurrentLocationPath,
|
||||||
|
sa.IsResolved, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sa.CurrentLocationID
|
||||||
|
WHERE sa.StoryAssetID = @StoryAssetID AND sa.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.AssetTimeline_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
|
||||||
|
sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
|
||||||
|
location.LocationName AS CurrentLocationName, location.LocationPath AS CurrentLocationPath,
|
||||||
|
sa.IsResolved, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sa.CurrentLocationID
|
||||||
|
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0 AND (sa.Importance >= 5 OR sa.IsResolved = 0)
|
||||||
|
ORDER BY sa.Importance DESC, sa.AssetName;
|
||||||
|
|
||||||
|
SELECT ae.AssetEventID, ae.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName,
|
||||||
|
ae.SceneID, ae.AssetEventTypeID, aet.TypeName AS AssetEventTypeName, aet.MarkerText,
|
||||||
|
ae.FromStateID, fs.StateName AS FromStateName, ae.ToStateID, ts.StateName AS ToStateName,
|
||||||
|
ae.EventTitle, ae.EventDescription, ae.CreatedDate, ae.UpdatedDate
|
||||||
|
FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ae.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
LEFT JOIN dbo.AssetStates fs ON fs.AssetStateID = ae.FromStateID
|
||||||
|
LEFT JOIN dbo.AssetStates ts ON ts.AssetStateID = ae.ToStateID
|
||||||
|
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
|
||||||
|
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, sa.AssetName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
845
PlotLine/Sql/008_Phase1G_ContinuityWarnings.sql
Normal file
845
PlotLine/Sql/008_Phase1G_ContinuityWarnings.sql
Normal file
@ -0,0 +1,845 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.WarningTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.WarningTypes
|
||||||
|
(
|
||||||
|
WarningTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WarningTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(150) NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_WarningTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_WarningTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.WarningSeverities', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.WarningSeverities
|
||||||
|
(
|
||||||
|
WarningSeverityID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WarningSeverities PRIMARY KEY,
|
||||||
|
SeverityName nvarchar(50) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_WarningSeverities_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_WarningSeverities_SeverityName UNIQUE (SeverityName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ValidationRuns', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ValidationRuns
|
||||||
|
(
|
||||||
|
ValidationRunID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ValidationRuns PRIMARY KEY,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
BookID int NULL,
|
||||||
|
StartedDate datetime2 NOT NULL CONSTRAINT DF_ValidationRuns_StartedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CompletedDate datetime2 NULL,
|
||||||
|
TriggeredBy nvarchar(100) NULL,
|
||||||
|
ScopeDescription nvarchar(200) NULL,
|
||||||
|
CONSTRAINT FK_ValidationRuns_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_ValidationRuns_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ContinuityWarnings', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.ContinuityWarnings
|
||||||
|
(
|
||||||
|
ContinuityWarningID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ContinuityWarnings PRIMARY KEY,
|
||||||
|
ValidationRunID int NULL,
|
||||||
|
ProjectID int NOT NULL,
|
||||||
|
BookID int NULL,
|
||||||
|
ChapterID int NULL,
|
||||||
|
SceneID int NULL,
|
||||||
|
EntityType nvarchar(80) NOT NULL,
|
||||||
|
EntityID int NULL,
|
||||||
|
WarningTypeID int NOT NULL,
|
||||||
|
WarningSeverityID int NOT NULL,
|
||||||
|
Message nvarchar(500) NOT NULL,
|
||||||
|
Details nvarchar(max) NULL,
|
||||||
|
IsDismissed bit NOT NULL CONSTRAINT DF_ContinuityWarnings_IsDismissed DEFAULT 0,
|
||||||
|
IsIntentional bit NOT NULL CONSTRAINT DF_ContinuityWarnings_IsIntentional DEFAULT 0,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_ContinuityWarnings_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
LastDetectedDate datetime2 NOT NULL CONSTRAINT DF_ContinuityWarnings_LastDetectedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
CONSTRAINT FK_ContinuityWarnings_ValidationRuns FOREIGN KEY (ValidationRunID) REFERENCES dbo.ValidationRuns(ValidationRunID),
|
||||||
|
CONSTRAINT FK_ContinuityWarnings_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||||
|
CONSTRAINT FK_ContinuityWarnings_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID),
|
||||||
|
CONSTRAINT FK_ContinuityWarnings_Chapters FOREIGN KEY (ChapterID) REFERENCES dbo.Chapters(ChapterID),
|
||||||
|
CONSTRAINT FK_ContinuityWarnings_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_ContinuityWarnings_Types FOREIGN KEY (WarningTypeID) REFERENCES dbo.WarningTypes(WarningTypeID),
|
||||||
|
CONSTRAINT FK_ContinuityWarnings_Severities FOREIGN KEY (WarningSeverityID) REFERENCES dbo.WarningSeverities(WarningSeverityID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.SceneDependencyTypes', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.SceneDependencyTypes
|
||||||
|
(
|
||||||
|
SceneDependencyTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneDependencyTypes PRIMARY KEY,
|
||||||
|
TypeName nvarchar(100) NOT NULL,
|
||||||
|
SortOrder int NOT NULL,
|
||||||
|
IsActive bit NOT NULL CONSTRAINT DF_SceneDependencyTypes_IsActive DEFAULT 1,
|
||||||
|
CONSTRAINT UQ_SceneDependencyTypes_TypeName UNIQUE (TypeName)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.SceneDependencies', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.SceneDependencies
|
||||||
|
(
|
||||||
|
SceneDependencyID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneDependencies PRIMARY KEY,
|
||||||
|
SourceSceneID int NOT NULL,
|
||||||
|
TargetSceneID int NOT NULL,
|
||||||
|
SceneDependencyTypeID int NOT NULL,
|
||||||
|
Description nvarchar(max) NULL,
|
||||||
|
CreatedDate datetime2 NOT NULL CONSTRAINT DF_SceneDependencies_CreatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_SceneDependencies_UpdatedDate DEFAULT SYSUTCDATETIME(),
|
||||||
|
IsArchived bit NOT NULL CONSTRAINT DF_SceneDependencies_IsArchived DEFAULT 0,
|
||||||
|
CONSTRAINT FK_SceneDependencies_SourceScene FOREIGN KEY (SourceSceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_SceneDependencies_TargetScene FOREIGN KEY (TargetSceneID) REFERENCES dbo.Scenes(SceneID),
|
||||||
|
CONSTRAINT FK_SceneDependencies_Types FOREIGN KEY (SceneDependencyTypeID) REFERENCES dbo.SceneDependencyTypes(SceneDependencyTypeID)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ContinuityWarnings_Project' AND object_id = OBJECT_ID(N'dbo.ContinuityWarnings'))
|
||||||
|
CREATE INDEX IX_ContinuityWarnings_Project ON dbo.ContinuityWarnings(ProjectID, BookID, SceneID, IsDismissed, IsIntentional, WarningSeverityID);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.WarningSeverities AS target
|
||||||
|
USING (VALUES (N'Info', 10), (N'Warning', 20), (N'Error', 30)) AS source (SeverityName, SortOrder)
|
||||||
|
ON target.SeverityName = source.SeverityName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (SeverityName, SortOrder) VALUES (source.SeverityName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.WarningTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Thread Resolved Before Introduced', N'Plot thread appears resolved before introduction.', 10),
|
||||||
|
(N'Question Answered Before Raised', N'Question event appears answered before raised.', 20),
|
||||||
|
(N'Clue Without Payoff', N'Clue has no payoff or resolution event.', 30),
|
||||||
|
(N'Thread Dormant', N'Open plot thread has gone quiet for many scenes.', 40),
|
||||||
|
(N'Asset Used Before Introduced', N'Asset appears used before it is introduced.', 50),
|
||||||
|
(N'Asset State Dependency Missing', N'Asset dependency may not have been satisfied.', 60),
|
||||||
|
(N'Asset Custody Conflict', N'Asset custody may not match use.', 70),
|
||||||
|
(N'Asset Location Conflict', N'Asset location changes without a movement event.', 80),
|
||||||
|
(N'Character Knowledge Conflict', N'Character knowledge may need a record.', 90),
|
||||||
|
(N'Scene Dependency Broken', N'Scene dependency order may be broken.', 100),
|
||||||
|
(N'Character Location Conflict', N'Character appears in conflicting scene locations.', 110),
|
||||||
|
(N'Scene Date Order Conflict', N'Narrative order and exact dates may conflict.', 120),
|
||||||
|
(N'Long Unexplained Time Gap', N'Long gap without time passage explanation.', 130),
|
||||||
|
(N'Sustained High Intensity', N'Metric remains high for many scenes.', 140),
|
||||||
|
(N'Sustained Low Intensity', N'Metric remains low for many scenes.', 150),
|
||||||
|
(N'Missing Scene Location', N'Scene has no primary location.', 160),
|
||||||
|
(N'Missing Scene Purpose', N'Scene has no purpose.', 170),
|
||||||
|
(N'Missing Scene Outcome', N'Scene has no outcome.', 180),
|
||||||
|
(N'Too Many Open Major Threads', N'Many major plot threads appear open at the same time.', 190)
|
||||||
|
) AS source (TypeName, Description, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, Description, SortOrder) VALUES (source.TypeName, source.Description, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.SceneDependencyTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Must Occur Before', 10), (N'Emotional Setup', 20), (N'Reveal Setup', 30), (N'Action Consequence', 40),
|
||||||
|
(N'Travel Dependency', 50), (N'Relationship Dependency', 60), (N'Knowledge Dependency', 70), (N'Object Dependency', 80)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.WarningLookup_All
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT WarningTypeID, TypeName, Description, SortOrder, IsActive FROM dbo.WarningTypes WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
SELECT WarningSeverityID, SeverityName, SortOrder, IsActive FROM dbo.WarningSeverities WHERE IsActive = 1 ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ValidationRun_Create
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@TriggeredBy nvarchar(100) = NULL,
|
||||||
|
@ScopeDescription nvarchar(200) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
INSERT dbo.ValidationRuns (ProjectID, BookID, TriggeredBy, ScopeDescription)
|
||||||
|
VALUES (@ProjectID, @BookID, @TriggeredBy, @ScopeDescription);
|
||||||
|
DECLARE @ValidationRunID int = CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
RETURN @ValidationRunID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ValidationRun_Complete
|
||||||
|
@ValidationRunID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.ValidationRuns SET CompletedDate = SYSUTCDATETIME() WHERE ValidationRunID = @ValidationRunID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ClearActive
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@SceneID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DELETE cw
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
WHERE cw.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
AND (@SceneID IS NULL OR cw.SceneID = @SceneID)
|
||||||
|
AND cw.IsDismissed = 0
|
||||||
|
AND cw.IsIntentional = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Upsert
|
||||||
|
@ValidationRunID int = NULL,
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@ChapterID int = NULL,
|
||||||
|
@SceneID int = NULL,
|
||||||
|
@EntityType nvarchar(80),
|
||||||
|
@EntityID int = NULL,
|
||||||
|
@WarningTypeName nvarchar(150),
|
||||||
|
@SeverityName nvarchar(50),
|
||||||
|
@Message nvarchar(500),
|
||||||
|
@Details nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @WarningTypeID int = (SELECT WarningTypeID FROM dbo.WarningTypes WHERE TypeName = @WarningTypeName);
|
||||||
|
DECLARE @WarningSeverityID int = (SELECT WarningSeverityID FROM dbo.WarningSeverities WHERE SeverityName = @SeverityName);
|
||||||
|
|
||||||
|
IF @WarningTypeID IS NULL OR @WarningSeverityID IS NULL
|
||||||
|
RETURN;
|
||||||
|
|
||||||
|
DECLARE @ExistingID int;
|
||||||
|
SELECT TOP (1) @ExistingID = ContinuityWarningID
|
||||||
|
FROM dbo.ContinuityWarnings
|
||||||
|
WHERE ProjectID = @ProjectID
|
||||||
|
AND ISNULL(BookID, 0) = ISNULL(@BookID, 0)
|
||||||
|
AND ISNULL(ChapterID, 0) = ISNULL(@ChapterID, 0)
|
||||||
|
AND ISNULL(SceneID, 0) = ISNULL(@SceneID, 0)
|
||||||
|
AND EntityType = @EntityType
|
||||||
|
AND ISNULL(EntityID, 0) = ISNULL(@EntityID, 0)
|
||||||
|
AND WarningTypeID = @WarningTypeID
|
||||||
|
AND IsDismissed = 0
|
||||||
|
AND IsIntentional = 0;
|
||||||
|
|
||||||
|
IF @ExistingID IS NULL
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.ContinuityWarnings (ValidationRunID, ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
|
||||||
|
VALUES (@ValidationRunID, @ProjectID, @BookID, @ChapterID, @SceneID, @EntityType, @EntityID, @WarningTypeID, @WarningSeverityID, @Message, @Details);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.ContinuityWarnings
|
||||||
|
SET ValidationRunID = @ValidationRunID,
|
||||||
|
WarningSeverityID = @WarningSeverityID,
|
||||||
|
Message = @Message,
|
||||||
|
Details = @Details,
|
||||||
|
LastDetectedDate = SYSUTCDATETIME()
|
||||||
|
WHERE ContinuityWarningID = @ExistingID;
|
||||||
|
END
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER VIEW dbo.NarrativeSceneOrder
|
||||||
|
AS
|
||||||
|
SELECT p.ProjectID, b.BookID, c.ChapterID, s.SceneID,
|
||||||
|
b.SortOrder AS BookSortOrder, c.SortOrder AS ChapterSortOrder, s.SortOrder AS SceneSortOrder,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY p.ProjectID ORDER BY b.SortOrder, b.BookNumber, c.SortOrder, c.ChapterNumber, s.SortOrder, s.SceneNumber, s.SceneID) AS GlobalSceneIndex,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY b.BookID ORDER BY c.SortOrder, c.ChapterNumber, s.SortOrder, s.SceneNumber, s.SceneID) AS BookSceneIndex,
|
||||||
|
b.BookTitle, b.BookNumber, c.ChapterNumber, c.ChapterTitle, s.SceneNumber, s.SceneTitle,
|
||||||
|
s.PrimaryLocationID, s.StartDateTime, s.RelativeTimeText, s.ScenePurposeNotes, s.SceneOutcomeNotes
|
||||||
|
FROM dbo.Projects p
|
||||||
|
INNER JOIN dbo.Books b ON b.ProjectID = p.ProjectID AND b.IsArchived = 0
|
||||||
|
INNER JOIN dbo.Chapters c ON c.BookID = b.BookID AND c.IsArchived = 0
|
||||||
|
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID AND s.IsArchived = 0
|
||||||
|
WHERE p.IsArchived = 0;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.NarrativeSceneOrder_GetByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT * FROM dbo.NarrativeSceneOrder
|
||||||
|
WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
|
||||||
|
ORDER BY GlobalSceneIndex;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@SceneID int = NULL,
|
||||||
|
@TriggeredBy nvarchar(100) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @ValidationRunID int;
|
||||||
|
DECLARE @Scope nvarchar(200) = CASE WHEN @SceneID IS NOT NULL THEN N'Scene validation' WHEN @BookID IS NOT NULL THEN N'Book validation' ELSE N'Project validation' END;
|
||||||
|
EXEC @ValidationRunID = dbo.ValidationRun_Create @ProjectID = @ProjectID, @BookID = @BookID, @TriggeredBy = @TriggeredBy, @ScopeDescription = @Scope;
|
||||||
|
EXEC dbo.ContinuityWarning_ClearActive @ProjectID = @ProjectID, @BookID = @BookID, @SceneID = @SceneID;
|
||||||
|
|
||||||
|
DECLARE @Warnings TABLE
|
||||||
|
(
|
||||||
|
BookID int NULL, ChapterID int NULL, SceneID int NULL, EntityType nvarchar(80), EntityID int NULL,
|
||||||
|
WarningTypeName nvarchar(150), SeverityName nvarchar(50), Message nvarchar(500), Details nvarchar(max) NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT * FROM dbo.NarrativeSceneOrder
|
||||||
|
WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) AND (@SceneID IS NULL OR SceneID = @SceneID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Missing Scene Location', N'Info',
|
||||||
|
N'Possible issue: this scene has no primary location yet.',
|
||||||
|
N'Add a primary location if the scene depends on where characters or assets are.'
|
||||||
|
FROM ScopeScenes WHERE PrimaryLocationID IS NULL;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.*
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR nso.SceneID = @SceneID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT ss.BookID, ss.ChapterID, ss.SceneID, N'Scene', ss.SceneID, N'Missing Scene Purpose', N'Info',
|
||||||
|
N'Possible issue: this scene has no purpose label or purpose notes.',
|
||||||
|
N'Add a scene purpose if this is not intentional.'
|
||||||
|
FROM ScopeScenes ss
|
||||||
|
WHERE ISNULL(NULLIF(LTRIM(RTRIM(ss.ScenePurposeNotes)), N''), N'') = N''
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = ss.SceneID);
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT * FROM dbo.NarrativeSceneOrder
|
||||||
|
WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) AND (@SceneID IS NULL OR SceneID = @SceneID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Missing Scene Outcome', N'Info',
|
||||||
|
N'Possible issue: this scene has no outcome notes.',
|
||||||
|
N'Add an outcome if the result of the scene needs tracking.'
|
||||||
|
FROM ScopeScenes WHERE ISNULL(NULLIF(LTRIM(RTRIM(SceneOutcomeNotes)), N''), N'') = N'';
|
||||||
|
|
||||||
|
;WITH Ordered AS
|
||||||
|
(
|
||||||
|
SELECT *, LAG(SceneID) OVER (ORDER BY GlobalSceneIndex) AS PreviousSceneID,
|
||||||
|
LAG(StartDateTime) OVER (ORDER BY GlobalSceneIndex) AS PreviousStartDateTime
|
||||||
|
FROM dbo.NarrativeSceneOrder
|
||||||
|
WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Scene Date Order Conflict', N'Warning',
|
||||||
|
N'Possible issue: this narrative scene has an earlier story date than the scene before it.',
|
||||||
|
CONCAT(N'Previous date: ', CONVERT(nvarchar(30), PreviousStartDateTime, 120), N'. Current date: ', CONVERT(nvarchar(30), StartDateTime, 120), N'.')
|
||||||
|
FROM Ordered
|
||||||
|
WHERE (@SceneID IS NULL OR SceneID = @SceneID)
|
||||||
|
AND PreviousStartDateTime IS NOT NULL AND StartDateTime IS NOT NULL AND StartDateTime < PreviousStartDateTime;
|
||||||
|
|
||||||
|
;WITH Ordered AS
|
||||||
|
(
|
||||||
|
SELECT *, LAG(StartDateTime) OVER (ORDER BY GlobalSceneIndex) AS PreviousStartDateTime
|
||||||
|
FROM dbo.NarrativeSceneOrder
|
||||||
|
WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Long Unexplained Time Gap', N'Info',
|
||||||
|
N'Possible issue: there is a long story-time gap before this scene without a clear time-passage note.',
|
||||||
|
CONCAT(N'Gap: ', DATEDIFF(day, PreviousStartDateTime, StartDateTime), N' days.')
|
||||||
|
FROM Ordered o
|
||||||
|
WHERE (@SceneID IS NULL OR SceneID = @SceneID)
|
||||||
|
AND PreviousStartDateTime IS NOT NULL AND StartDateTime IS NOT NULL
|
||||||
|
AND DATEDIFF(day, PreviousStartDateTime, StartDateTime) > 14
|
||||||
|
AND ISNULL(NULLIF(LTRIM(RTRIM(RelativeTimeText)), N''), N'') = N''
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.ScenePurposes sp
|
||||||
|
INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID
|
||||||
|
WHERE sp.SceneID = o.SceneID AND spt.PurposeName = N'Time Passage'
|
||||||
|
);
|
||||||
|
|
||||||
|
;WITH Events AS
|
||||||
|
(
|
||||||
|
SELECT pt.PlotThreadID, pt.ThreadTitle, pt.Importance, ts.IsOpenStatus, te.SceneID, tet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
|
||||||
|
FROM dbo.ThreadEvents te
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
|
||||||
|
WHERE (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
),
|
||||||
|
ThreadRanges AS
|
||||||
|
(
|
||||||
|
SELECT PlotThreadID, ThreadTitle, Importance, IsOpenStatus,
|
||||||
|
MIN(CASE WHEN TypeName = N'Introduced' THEN GlobalSceneIndex END) AS IntroducedIndex,
|
||||||
|
MIN(CASE WHEN TypeName IN (N'Resolved', N'Payoff') THEN GlobalSceneIndex END) AS ResolvedIndex,
|
||||||
|
MIN(CASE WHEN TypeName = N'Question Raised' THEN GlobalSceneIndex END) AS QuestionRaisedIndex,
|
||||||
|
MIN(CASE WHEN TypeName = N'Question Answered' THEN GlobalSceneIndex END) AS QuestionAnsweredIndex,
|
||||||
|
MAX(GlobalSceneIndex) AS LastEventIndex,
|
||||||
|
MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID, MIN(SceneID) AS AnySceneID
|
||||||
|
FROM Events GROUP BY PlotThreadID, ThreadTitle, Importance, IsOpenStatus
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, AnySceneID, N'PlotThread', PlotThreadID, N'Thread Resolved Before Introduced', N'Warning',
|
||||||
|
CONCAT(N'Possible issue: "', ThreadTitle, N'" appears resolved before it is introduced.'),
|
||||||
|
NULL
|
||||||
|
FROM ThreadRanges
|
||||||
|
WHERE ResolvedIndex IS NOT NULL AND IntroducedIndex IS NOT NULL AND ResolvedIndex < IntroducedIndex;
|
||||||
|
|
||||||
|
;WITH Events AS
|
||||||
|
(
|
||||||
|
SELECT pt.PlotThreadID, pt.ThreadTitle, te.SceneID, tet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
|
||||||
|
FROM dbo.ThreadEvents te
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
|
||||||
|
WHERE (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
),
|
||||||
|
ThreadRanges AS
|
||||||
|
(
|
||||||
|
SELECT PlotThreadID, ThreadTitle,
|
||||||
|
MIN(CASE WHEN TypeName = N'Question Raised' THEN GlobalSceneIndex END) AS QuestionRaisedIndex,
|
||||||
|
MIN(CASE WHEN TypeName = N'Question Answered' THEN GlobalSceneIndex END) AS QuestionAnsweredIndex,
|
||||||
|
MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID, MIN(SceneID) AS AnySceneID
|
||||||
|
FROM Events GROUP BY PlotThreadID, ThreadTitle
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, AnySceneID, N'PlotThread', PlotThreadID, N'Question Answered Before Raised', N'Warning',
|
||||||
|
CONCAT(N'Possible issue: "', ThreadTitle, N'" is answered before it is raised.'),
|
||||||
|
NULL
|
||||||
|
FROM ThreadRanges
|
||||||
|
WHERE QuestionAnsweredIndex IS NOT NULL AND QuestionRaisedIndex IS NOT NULL AND QuestionAnsweredIndex < QuestionRaisedIndex;
|
||||||
|
|
||||||
|
;WITH Clues AS
|
||||||
|
(
|
||||||
|
SELECT pt.PlotThreadID, pt.ThreadTitle, MIN(nso.SceneID) AS SceneID, MIN(nso.BookID) AS BookID, MIN(nso.ChapterID) AS ChapterID
|
||||||
|
FROM dbo.ThreadEvents te
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
|
||||||
|
WHERE tet.TypeName = N'Clue Planted' AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
GROUP BY pt.PlotThreadID, pt.ThreadTitle
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'PlotThread', PlotThreadID, N'Clue Without Payoff', N'Info',
|
||||||
|
CONCAT(N'Possible issue: clue thread "', ThreadTitle, N'" has no payoff, answer, reveal, or resolution yet.'),
|
||||||
|
NULL
|
||||||
|
FROM Clues c
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.ThreadEvents te
|
||||||
|
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
WHERE te.PlotThreadID = c.PlotThreadID AND tet.TypeName IN (N'Payoff', N'Question Answered', N'Revealed', N'Resolved')
|
||||||
|
);
|
||||||
|
|
||||||
|
;WITH LastThreadEvent AS
|
||||||
|
(
|
||||||
|
SELECT pt.PlotThreadID, pt.ThreadTitle, MAX(nso.GlobalSceneIndex) AS LastEventIndex, MAX(nso.BookID) AS BookID, MAX(nso.ChapterID) AS ChapterID, MAX(nso.SceneID) AS SceneID
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID AND ts.IsOpenStatus = 1
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
|
||||||
|
INNER JOIN dbo.ThreadEvents te ON te.PlotThreadID = pt.PlotThreadID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
|
||||||
|
WHERE pt.IsArchived = 0 AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
GROUP BY pt.PlotThreadID, pt.ThreadTitle
|
||||||
|
),
|
||||||
|
MaxScope AS
|
||||||
|
(
|
||||||
|
SELECT MAX(GlobalSceneIndex) AS MaxSceneIndex FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'PlotThread', PlotThreadID, N'Thread Dormant', N'Info',
|
||||||
|
CONCAT(N'Possible issue: "', ThreadTitle, N'" has no event for more than 8 scenes.'),
|
||||||
|
CONCAT(N'Scenes since last event: ', MaxSceneIndex - LastEventIndex, N'.')
|
||||||
|
FROM LastThreadEvent CROSS JOIN MaxScope
|
||||||
|
WHERE MaxSceneIndex - LastEventIndex > 8;
|
||||||
|
|
||||||
|
;WITH OpenMajor AS
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) AS OpenCount
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID AND ts.IsOpenStatus = 1
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
|
||||||
|
WHERE pt.IsArchived = 0 AND pt.Importance >= 8 AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT @BookID, NULL, NULL, N'Project', @ProjectID, N'Too Many Open Major Threads', N'Info',
|
||||||
|
CONCAT(N'Possible issue: there are ', OpenCount, N' high-importance open threads.'),
|
||||||
|
N'This may be intentional, but it can make the active story load feel heavy.'
|
||||||
|
FROM OpenMajor WHERE OpenCount > 10;
|
||||||
|
|
||||||
|
;WITH AssetEventsOrdered AS
|
||||||
|
(
|
||||||
|
SELECT ae.StoryAssetID, sa.AssetName, ae.SceneID, aet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
|
||||||
|
FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0
|
||||||
|
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID
|
||||||
|
WHERE (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
),
|
||||||
|
AssetFirsts AS
|
||||||
|
(
|
||||||
|
SELECT StoryAssetID, AssetName,
|
||||||
|
MIN(CASE WHEN TypeName IN (N'First Mentioned', N'Mentioned', N'Found') THEN GlobalSceneIndex END) AS IntroducedIndex,
|
||||||
|
MIN(CASE WHEN TypeName IN (N'Used', N'Opened', N'Revealed', N'Destroyed', N'Given', N'Taken', N'Hidden', N'Lost', N'Stolen') THEN GlobalSceneIndex END) AS UsedIndex,
|
||||||
|
MIN(CASE WHEN TypeName IN (N'Used', N'Opened', N'Revealed', N'Destroyed', N'Given', N'Taken', N'Hidden', N'Lost', N'Stolen') THEN SceneID END) AS UsedSceneID,
|
||||||
|
MIN(BookID) AS BookID, MIN(ChapterID) AS ChapterID
|
||||||
|
FROM AssetEventsOrdered GROUP BY StoryAssetID, AssetName
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, UsedSceneID, N'StoryAsset', StoryAssetID, N'Asset Used Before Introduced', N'Warning',
|
||||||
|
CONCAT(N'Possible issue: "', AssetName, N'" appears used before it is introduced.'),
|
||||||
|
NULL
|
||||||
|
FROM AssetFirsts
|
||||||
|
WHERE UsedIndex IS NOT NULL AND (IntroducedIndex IS NULL OR UsedIndex < IntroducedIndex);
|
||||||
|
|
||||||
|
;WITH TargetEvents AS
|
||||||
|
(
|
||||||
|
SELECT ad.AssetDependencyID, ad.SourceAssetID, source.AssetName AS SourceAssetName, ad.TargetAssetID, target.AssetName AS TargetAssetName,
|
||||||
|
ad.SourceRequiredStateID, ad.TargetBlockedStateID, ae.SceneID, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
|
||||||
|
FROM dbo.AssetDependencies ad
|
||||||
|
INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID AND adt.IsValidatingDependency = 1
|
||||||
|
INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID
|
||||||
|
INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID AND target.ProjectID = @ProjectID
|
||||||
|
INNER JOIN dbo.AssetEvents ae ON ae.StoryAssetID = ad.TargetAssetID AND ae.ToStateID = ad.TargetBlockedStateID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID
|
||||||
|
WHERE ad.IsArchived = 0 AND ad.SourceRequiredStateID IS NOT NULL AND ad.TargetBlockedStateID IS NOT NULL AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'AssetDependency', AssetDependencyID, N'Asset State Dependency Missing', N'Warning',
|
||||||
|
CONCAT(N'Possible issue: "', TargetAssetName, N'" reaches a blocked state before "', SourceAssetName, N'" reaches its required state.'),
|
||||||
|
NULL
|
||||||
|
FROM TargetEvents te
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.AssetEvents sourceEvent
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder sourceScene ON sourceScene.SceneID = sourceEvent.SceneID
|
||||||
|
WHERE sourceEvent.StoryAssetID = te.SourceAssetID
|
||||||
|
AND sourceEvent.ToStateID = te.SourceRequiredStateID
|
||||||
|
AND sourceScene.GlobalSceneIndex <= te.GlobalSceneIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
;WITH AssetPlaces AS
|
||||||
|
(
|
||||||
|
SELECT sal.StoryAssetID, sa.AssetName, sal.LocationID, sal.SceneID, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex,
|
||||||
|
LAG(sal.LocationID) OVER (PARTITION BY sal.StoryAssetID ORDER BY nso.GlobalSceneIndex) AS PreviousLocationID
|
||||||
|
FROM dbo.SceneAssetLocations sal
|
||||||
|
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sal.SceneID
|
||||||
|
WHERE (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'StoryAsset', StoryAssetID, N'Asset Location Conflict', N'Warning',
|
||||||
|
CONCAT(N'Possible issue: "', AssetName, N'" appears in a new location without a movement-style event.'),
|
||||||
|
N'Add a Move, Given, Taken, Found, or Location Changed event if the location change needs explaining.'
|
||||||
|
FROM AssetPlaces ap
|
||||||
|
WHERE PreviousLocationID IS NOT NULL AND PreviousLocationID <> LocationID
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
|
||||||
|
WHERE ae.StoryAssetID = ap.StoryAssetID AND ae.SceneID = ap.SceneID AND aet.TypeName IN (N'Moved', N'Given', N'Taken', N'Found', N'Location Changed')
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT DISTINCT nso.BookID, nso.ChapterID, sc.SceneID, N'Character', sc.CharacterID, N'Character Knowledge Conflict', N'Info',
|
||||||
|
CONCAT(N'Possible issue: ', ch.CharacterName, N' has knowledge notes in this scene but no structured knowledge record.'),
|
||||||
|
N'This is only a low-severity prompt to add Character Knowledge if useful.'
|
||||||
|
FROM dbo.SceneCharacters sc
|
||||||
|
INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||||
|
WHERE ch.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR sc.SceneID = @SceneID)
|
||||||
|
AND ISNULL(NULLIF(LTRIM(RTRIM(sc.KnowledgeNotes)), N''), N'') <> N''
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM dbo.CharacterKnowledge ck WHERE ck.SceneID = sc.SceneID AND ck.CharacterID = sc.CharacterID);
|
||||||
|
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT targetOrder.BookID, targetOrder.ChapterID, sd.TargetSceneID, N'SceneDependency', sd.SceneDependencyID, N'Scene Dependency Broken', N'Warning',
|
||||||
|
N'Possible issue: a scene dependency says an earlier setup should occur before this scene, but the order is reversed.',
|
||||||
|
sd.Description
|
||||||
|
FROM dbo.SceneDependencies sd
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder sourceOrder ON sourceOrder.SceneID = sd.SourceSceneID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder targetOrder ON targetOrder.SceneID = sd.TargetSceneID
|
||||||
|
WHERE sd.IsArchived = 0 AND sourceOrder.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR targetOrder.BookID = @BookID) AND (@SceneID IS NULL OR targetOrder.SceneID = @SceneID)
|
||||||
|
AND sourceOrder.GlobalSceneIndex > targetOrder.GlobalSceneIndex;
|
||||||
|
|
||||||
|
;WITH CharacterSceneLocations AS
|
||||||
|
(
|
||||||
|
SELECT sc.SceneID, sc.CharacterID, COUNT(DISTINCT sc.LocationID) AS LocationCount, MAX(ch.CharacterName) AS CharacterName,
|
||||||
|
MAX(nso.BookID) AS BookID, MAX(nso.ChapterID) AS ChapterID
|
||||||
|
FROM dbo.SceneCharacters sc
|
||||||
|
INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID AND ch.ProjectID = @ProjectID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||||
|
LEFT JOIN dbo.PresenceTypes pt ON pt.PresenceTypeID = sc.PresenceTypeID
|
||||||
|
WHERE sc.LocationID IS NOT NULL AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR sc.SceneID = @SceneID)
|
||||||
|
AND ISNULL(pt.TypeName, N'') NOT IN (N'Nearby', N'Hidden', N'Overhearing', N'Off-screen')
|
||||||
|
GROUP BY sc.SceneID, sc.CharacterID
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, SceneID, N'Character', CharacterID, N'Character Location Conflict', N'Warning',
|
||||||
|
CONCAT(N'Possible issue: ', CharacterName, N' appears in more than one location in the same scene.'),
|
||||||
|
NULL
|
||||||
|
FROM CharacterSceneLocations WHERE LocationCount > 1;
|
||||||
|
|
||||||
|
;WITH MetricRows AS
|
||||||
|
(
|
||||||
|
SELECT smv.MetricTypeID, smt.MetricName, smv.SceneID, smv.Value, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID ORDER BY nso.GlobalSceneIndex) AS rn,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID, CASE WHEN smv.Value >= 8 THEN 1 ELSE 0 END ORDER BY nso.GlobalSceneIndex) AS grpRn
|
||||||
|
FROM dbo.SceneMetricValues smv
|
||||||
|
INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = smv.SceneID
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
),
|
||||||
|
HighGroups AS
|
||||||
|
(
|
||||||
|
SELECT MetricTypeID, MetricName, rn - grpRn AS grp, COUNT(*) AS SceneCount, MIN(SceneID) AS FirstSceneID, MAX(SceneID) AS LastSceneID,
|
||||||
|
MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID
|
||||||
|
FROM MetricRows WHERE Value >= 8 GROUP BY MetricTypeID, MetricName, rn - grpRn HAVING COUNT(*) > 6
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, LastSceneID, N'Metric', MetricTypeID, N'Sustained High Intensity', N'Info',
|
||||||
|
CONCAT(N'Possible reader fatigue: ', MetricName, N' remains high for ', SceneCount, N' consecutive scenes.'),
|
||||||
|
CONCAT(N'Scene range: ', FirstSceneID, N' to ', LastSceneID, N'.')
|
||||||
|
FROM HighGroups;
|
||||||
|
|
||||||
|
;WITH MetricRows AS
|
||||||
|
(
|
||||||
|
SELECT smv.MetricTypeID, smt.MetricName, smv.SceneID, smv.Value, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID ORDER BY nso.GlobalSceneIndex) AS rn,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID, CASE WHEN smv.Value <= 2 THEN 1 ELSE 0 END ORDER BY nso.GlobalSceneIndex) AS grpRn
|
||||||
|
FROM dbo.SceneMetricValues smv
|
||||||
|
INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = smv.SceneID
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
AND smt.MetricName IN (N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action')
|
||||||
|
),
|
||||||
|
LowGroups AS
|
||||||
|
(
|
||||||
|
SELECT MetricTypeID, MetricName, rn - grpRn AS grp, COUNT(*) AS SceneCount, MIN(SceneID) AS FirstSceneID, MAX(SceneID) AS LastSceneID,
|
||||||
|
MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID
|
||||||
|
FROM MetricRows WHERE Value <= 2 GROUP BY MetricTypeID, MetricName, rn - grpRn HAVING COUNT(*) > 8
|
||||||
|
)
|
||||||
|
INSERT @Warnings
|
||||||
|
SELECT BookID, ChapterID, LastSceneID, N'Metric', MetricTypeID, N'Sustained Low Intensity', N'Info',
|
||||||
|
CONCAT(N'Possible issue: ', MetricName, N' remains low for ', SceneCount, N' consecutive scenes.'),
|
||||||
|
CONCAT(N'Scene range: ', FirstSceneID, N' to ', LastSceneID, N'.')
|
||||||
|
FROM LowGroups;
|
||||||
|
|
||||||
|
INSERT dbo.ContinuityWarnings (ValidationRunID, ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
|
||||||
|
SELECT @ValidationRunID, @ProjectID, w.BookID, w.ChapterID, w.SceneID, w.EntityType, w.EntityID, wt.WarningTypeID, ws.WarningSeverityID, w.Message, w.Details
|
||||||
|
FROM @Warnings w
|
||||||
|
INNER JOIN dbo.WarningTypes wt ON wt.TypeName = w.WarningTypeName
|
||||||
|
INNER JOIN dbo.WarningSeverities ws ON ws.SeverityName = w.SeverityName
|
||||||
|
WHERE NOT EXISTS
|
||||||
|
(
|
||||||
|
SELECT 1 FROM dbo.ContinuityWarnings existing
|
||||||
|
WHERE existing.ProjectID = @ProjectID
|
||||||
|
AND ISNULL(existing.BookID, 0) = ISNULL(w.BookID, 0)
|
||||||
|
AND ISNULL(existing.ChapterID, 0) = ISNULL(w.ChapterID, 0)
|
||||||
|
AND ISNULL(existing.SceneID, 0) = ISNULL(w.SceneID, 0)
|
||||||
|
AND existing.EntityType = w.EntityType
|
||||||
|
AND ISNULL(existing.EntityID, 0) = ISNULL(w.EntityID, 0)
|
||||||
|
AND existing.WarningTypeID = wt.WarningTypeID
|
||||||
|
AND (existing.IsDismissed = 1 OR existing.IsIntentional = 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
EXEC dbo.ValidationRun_Complete @ValidationRunID;
|
||||||
|
|
||||||
|
SELECT @ValidationRunID AS ValidationRunID,
|
||||||
|
SUM(CASE WHEN ws.SeverityName = N'Error' THEN 1 ELSE 0 END) AS ErrorCount,
|
||||||
|
SUM(CASE WHEN ws.SeverityName = N'Warning' THEN 1 ELSE 0 END) AS WarningCount,
|
||||||
|
SUM(CASE WHEN ws.SeverityName = N'Info' THEN 1 ELSE 0 END) AS InfoCount,
|
||||||
|
COUNT(*) AS TotalCount
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||||
|
WHERE cw.ValidationRunID = @ValidationRunID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateBook
|
||||||
|
@BookID int,
|
||||||
|
@TriggeredBy nvarchar(100) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @ProjectID int = (SELECT ProjectID FROM dbo.Books WHERE BookID = @BookID);
|
||||||
|
EXEC dbo.ContinuityValidation_ValidateProject @ProjectID = @ProjectID, @BookID = @BookID, @TriggeredBy = @TriggeredBy;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateScene
|
||||||
|
@SceneID int,
|
||||||
|
@TriggeredBy nvarchar(100) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @ProjectID int, @BookID int;
|
||||||
|
SELECT @ProjectID = b.ProjectID, @BookID = b.BookID
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE s.SceneID = @SceneID;
|
||||||
|
EXEC dbo.ContinuityValidation_ValidateProject @ProjectID = @ProjectID, @BookID = @BookID, @SceneID = @SceneID, @TriggeredBy = @TriggeredBy;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_List
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@SceneID int = NULL,
|
||||||
|
@EntityType nvarchar(80) = NULL,
|
||||||
|
@EntityID int = NULL,
|
||||||
|
@WarningTypeID int = NULL,
|
||||||
|
@WarningSeverityID int = NULL,
|
||||||
|
@IncludeDismissed bit = 0,
|
||||||
|
@IncludeIntentional bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT cw.ContinuityWarningID, cw.ValidationRunID, cw.ProjectID, cw.BookID, b.BookTitle, cw.ChapterID, c.ChapterNumber, c.ChapterTitle,
|
||||||
|
cw.SceneID, s.SceneNumber, s.SceneTitle, cw.EntityType, cw.EntityID, cw.WarningTypeID, wt.TypeName AS WarningTypeName,
|
||||||
|
cw.WarningSeverityID, ws.SeverityName, cw.Message, cw.Details, cw.IsDismissed, cw.IsIntentional, cw.CreatedDate, cw.LastDetectedDate
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||||
|
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = cw.BookID
|
||||||
|
LEFT JOIN dbo.Chapters c ON c.ChapterID = cw.ChapterID
|
||||||
|
LEFT JOIN dbo.Scenes s ON s.SceneID = cw.SceneID
|
||||||
|
WHERE cw.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
AND (@SceneID IS NULL OR cw.SceneID = @SceneID)
|
||||||
|
AND (@EntityType IS NULL OR cw.EntityType = @EntityType)
|
||||||
|
AND (@EntityID IS NULL OR cw.EntityID = @EntityID)
|
||||||
|
AND (@WarningTypeID IS NULL OR cw.WarningTypeID = @WarningTypeID)
|
||||||
|
AND (@WarningSeverityID IS NULL OR cw.WarningSeverityID = @WarningSeverityID)
|
||||||
|
AND (@IncludeDismissed = 1 OR cw.IsDismissed = 0)
|
||||||
|
AND (@IncludeIntentional = 1 OR cw.IsIntentional = 0)
|
||||||
|
ORDER BY ws.SortOrder DESC, cw.LastDetectedDate DESC, cw.ContinuityWarningID DESC;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@IncludeDismissed bit = 0,
|
||||||
|
@IncludeIntentional bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByBook
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int,
|
||||||
|
@IncludeDismissed bit = 0,
|
||||||
|
@IncludeIntentional bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @BookID = @BookID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByScene
|
||||||
|
@SceneID int,
|
||||||
|
@IncludeDismissed bit = 0,
|
||||||
|
@IncludeIntentional bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @ProjectID int = (SELECT b.ProjectID FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE s.SceneID = @SceneID);
|
||||||
|
EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @SceneID = @SceneID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByEntity
|
||||||
|
@ProjectID int,
|
||||||
|
@EntityType nvarchar(80),
|
||||||
|
@EntityID int,
|
||||||
|
@IncludeDismissed bit = 0,
|
||||||
|
@IncludeIntentional bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @EntityType = @EntityType, @EntityID = @EntityID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_SceneCounts
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT cw.SceneID, COUNT(*) AS WarningCount
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
WHERE cw.ProjectID = @ProjectID
|
||||||
|
AND cw.SceneID IS NOT NULL
|
||||||
|
AND cw.IsDismissed = 0
|
||||||
|
AND cw.IsIntentional = 0
|
||||||
|
AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
GROUP BY cw.SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_SetState
|
||||||
|
@ContinuityWarningID int,
|
||||||
|
@IsDismissed bit = NULL,
|
||||||
|
@IsIntentional bit = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.ContinuityWarnings
|
||||||
|
SET IsDismissed = COALESCE(@IsDismissed, IsDismissed),
|
||||||
|
IsIntentional = COALESCE(@IsIntentional, IsIntentional)
|
||||||
|
WHERE ContinuityWarningID = @ContinuityWarningID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Dismiss
|
||||||
|
@ContinuityWarningID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
EXEC dbo.ContinuityWarning_SetState @ContinuityWarningID = @ContinuityWarningID, @IsDismissed = 1;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_MarkIntentional
|
||||||
|
@ContinuityWarningID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
EXEC dbo.ContinuityWarning_SetState @ContinuityWarningID = @ContinuityWarningID, @IsIntentional = 1;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Restore
|
||||||
|
@ContinuityWarningID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.ContinuityWarnings
|
||||||
|
SET IsDismissed = 0, IsIntentional = 0
|
||||||
|
WHERE ContinuityWarningID = @ContinuityWarningID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
305
PlotLine/Sql/009_Phase1H_SceneDependenciesAndMoves.sql
Normal file
305
PlotLine/Sql/009_Phase1H_SceneDependenciesAndMoves.sql
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH('dbo.SceneDependencies', 'ProjectID') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.SceneDependencies ADD ProjectID int NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
UPDATE sd
|
||||||
|
SET ProjectID = b.ProjectID
|
||||||
|
FROM dbo.SceneDependencies sd
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = sd.SourceSceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE sd.ProjectID IS NULL;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH('dbo.SceneDependencies', 'IsHardDependency') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.SceneDependencies ADD IsHardDependency bit NOT NULL CONSTRAINT DF_SceneDependencies_IsHardDependency DEFAULT 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.SceneDependencyTypes AS target
|
||||||
|
USING (VALUES
|
||||||
|
(N'Must Occur Before', 10), (N'Emotional Setup', 20), (N'Reveal Setup', 30), (N'Action Consequence', 40),
|
||||||
|
(N'Travel Dependency', 50), (N'Relationship Dependency', 60), (N'Knowledge Dependency', 70), (N'Object Dependency', 80),
|
||||||
|
(N'Location Dependency', 90), (N'Time Dependency', 100)
|
||||||
|
) AS source (TypeName, SortOrder)
|
||||||
|
ON target.TypeName = source.TypeName
|
||||||
|
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneDependencyLookup_All
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT SceneDependencyTypeID, TypeName, SortOrder, IsActive
|
||||||
|
FROM dbo.SceneDependencyTypes
|
||||||
|
WHERE IsActive = 1
|
||||||
|
ORDER BY SortOrder;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneDependency_Save
|
||||||
|
@SceneDependencyID int = NULL,
|
||||||
|
@ProjectID int = NULL,
|
||||||
|
@SourceSceneID int,
|
||||||
|
@TargetSceneID int,
|
||||||
|
@SceneDependencyTypeID int,
|
||||||
|
@Description nvarchar(max) = NULL,
|
||||||
|
@IsHardDependency bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF @ProjectID IS NULL
|
||||||
|
BEGIN
|
||||||
|
SELECT @ProjectID = b.ProjectID
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE s.SceneID = @SourceSceneID;
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF @SourceSceneID = @TargetSceneID
|
||||||
|
RETURN;
|
||||||
|
|
||||||
|
IF @SceneDependencyID IS NULL OR @SceneDependencyID = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.SceneDependencies (ProjectID, SourceSceneID, TargetSceneID, SceneDependencyTypeID, Description, IsHardDependency)
|
||||||
|
VALUES (@ProjectID, @SourceSceneID, @TargetSceneID, @SceneDependencyTypeID, @Description, @IsHardDependency);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int);
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE dbo.SceneDependencies
|
||||||
|
SET SourceSceneID = @SourceSceneID,
|
||||||
|
TargetSceneID = @TargetSceneID,
|
||||||
|
SceneDependencyTypeID = @SceneDependencyTypeID,
|
||||||
|
Description = @Description,
|
||||||
|
IsHardDependency = @IsHardDependency,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneDependencyID = @SceneDependencyID;
|
||||||
|
|
||||||
|
SELECT @SceneDependencyID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneDependency_Archive
|
||||||
|
@SceneDependencyID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
UPDATE dbo.SceneDependencies
|
||||||
|
SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneDependencyID = @SceneDependencyID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneDependency_ListByScene
|
||||||
|
@SceneID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sd.SceneDependencyID, sd.ProjectID, sd.SourceSceneID, sd.TargetSceneID, sd.SceneDependencyTypeID,
|
||||||
|
sdt.TypeName AS SceneDependencyTypeName, sd.Description, sd.IsHardDependency, sd.CreatedDate, sd.UpdatedDate, sd.IsArchived,
|
||||||
|
sourceOrder.BookID AS SourceBookID, sourceOrder.BookTitle AS SourceBookTitle, sourceOrder.ChapterID AS SourceChapterID,
|
||||||
|
sourceOrder.ChapterNumber AS SourceChapterNumber, sourceOrder.SceneNumber AS SourceSceneNumber, sourceOrder.SceneTitle AS SourceSceneTitle,
|
||||||
|
targetOrder.BookID AS TargetBookID, targetOrder.BookTitle AS TargetBookTitle, targetOrder.ChapterID AS TargetChapterID,
|
||||||
|
targetOrder.ChapterNumber AS TargetChapterNumber, targetOrder.SceneNumber AS TargetSceneNumber, targetOrder.SceneTitle AS TargetSceneTitle,
|
||||||
|
CASE WHEN sd.TargetSceneID = @SceneID THEN N'DependsOn' ELSE N'Dependent' END AS DependencyDirection
|
||||||
|
FROM dbo.SceneDependencies sd
|
||||||
|
INNER JOIN dbo.SceneDependencyTypes sdt ON sdt.SceneDependencyTypeID = sd.SceneDependencyTypeID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder sourceOrder ON sourceOrder.SceneID = sd.SourceSceneID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder targetOrder ON targetOrder.SceneID = sd.TargetSceneID
|
||||||
|
WHERE sd.IsArchived = 0 AND @SceneID IN (sd.SourceSceneID, sd.TargetSceneID)
|
||||||
|
ORDER BY DependencyDirection, sourceOrder.GlobalSceneIndex, targetOrder.GlobalSceneIndex;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneDependency_ListByProject
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT sd.SceneDependencyID, sd.ProjectID, sd.SourceSceneID, sd.TargetSceneID, sd.SceneDependencyTypeID,
|
||||||
|
sdt.TypeName AS SceneDependencyTypeName, sd.Description, sd.IsHardDependency, sd.CreatedDate, sd.UpdatedDate, sd.IsArchived,
|
||||||
|
sourceOrder.BookID AS SourceBookID, sourceOrder.BookTitle AS SourceBookTitle, sourceOrder.ChapterID AS SourceChapterID,
|
||||||
|
sourceOrder.ChapterNumber AS SourceChapterNumber, sourceOrder.SceneNumber AS SourceSceneNumber, sourceOrder.SceneTitle AS SourceSceneTitle,
|
||||||
|
targetOrder.BookID AS TargetBookID, targetOrder.BookTitle AS TargetBookTitle, targetOrder.ChapterID AS TargetChapterID,
|
||||||
|
targetOrder.ChapterNumber AS TargetChapterNumber, targetOrder.SceneNumber AS TargetSceneNumber, targetOrder.SceneTitle AS TargetSceneTitle
|
||||||
|
FROM dbo.SceneDependencies sd
|
||||||
|
INNER JOIN dbo.SceneDependencyTypes sdt ON sdt.SceneDependencyTypeID = sd.SceneDependencyTypeID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder sourceOrder ON sourceOrder.SceneID = sd.SourceSceneID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder targetOrder ON targetOrder.SceneID = sd.TargetSceneID
|
||||||
|
WHERE sd.IsArchived = 0 AND sd.ProjectID = @ProjectID AND (@BookID IS NULL OR targetOrder.BookID = @BookID OR sourceOrder.BookID = @BookID)
|
||||||
|
ORDER BY sourceOrder.GlobalSceneIndex, targetOrder.GlobalSceneIndex;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneDependency_Counts
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT scene.SceneID, COUNT(*) AS DependencyCount
|
||||||
|
FROM dbo.NarrativeSceneOrder scene
|
||||||
|
INNER JOIN dbo.SceneDependencies sd ON scene.SceneID IN (sd.SourceSceneID, sd.TargetSceneID) AND sd.IsArchived = 0
|
||||||
|
WHERE scene.ProjectID = @ProjectID AND (@BookID IS NULL OR scene.BookID = @BookID)
|
||||||
|
GROUP BY scene.SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ChapterOption_ListByProject
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT c.ChapterID, b.BookID, b.BookTitle, b.BookNumber, c.ChapterNumber, c.ChapterTitle, c.SortOrder
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0
|
||||||
|
ORDER BY b.SortOrder, b.BookNumber, c.SortOrder, c.ChapterNumber;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_NormalizeSortOrder
|
||||||
|
@ChapterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
;WITH ordered AS
|
||||||
|
(
|
||||||
|
SELECT SceneID, ROW_NUMBER() OVER (ORDER BY SortOrder, SceneNumber, SceneID) AS rn
|
||||||
|
FROM dbo.Scenes
|
||||||
|
WHERE ChapterID = @ChapterID AND IsArchived = 0
|
||||||
|
)
|
||||||
|
UPDATE s
|
||||||
|
SET SortOrder = ordered.rn * 10, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN ordered ON ordered.SceneID = s.SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_RenumberChapter
|
||||||
|
@ChapterID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
;WITH ordered AS
|
||||||
|
(
|
||||||
|
SELECT SceneID, ROW_NUMBER() OVER (ORDER BY SortOrder, SceneNumber, SceneID) AS rn
|
||||||
|
FROM dbo.Scenes
|
||||||
|
WHERE ChapterID = @ChapterID AND IsArchived = 0
|
||||||
|
)
|
||||||
|
UPDATE s
|
||||||
|
SET SceneNumber = ordered.rn, SortOrder = ordered.rn * 10, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN ordered ON ordered.SceneID = s.SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Chapter_RenumberBook
|
||||||
|
@BookID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
;WITH ordered AS
|
||||||
|
(
|
||||||
|
SELECT ChapterID, ROW_NUMBER() OVER (ORDER BY SortOrder, ChapterNumber, ChapterID) AS rn
|
||||||
|
FROM dbo.Chapters
|
||||||
|
WHERE BookID = @BookID AND IsArchived = 0
|
||||||
|
)
|
||||||
|
UPDATE c
|
||||||
|
SET ChapterNumber = ordered.rn, SortOrder = ordered.rn * 10, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN ordered ON ordered.ChapterID = c.ChapterID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneMovePreview_Get
|
||||||
|
@SceneID int,
|
||||||
|
@TargetChapterID int,
|
||||||
|
@Position nvarchar(20) = N'End'
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SELECT s.SceneID, s.SceneNumber, s.SceneTitle, s.ChapterID AS SourceChapterID, sourceChapter.ChapterNumber AS SourceChapterNumber,
|
||||||
|
sourceChapter.ChapterTitle AS SourceChapterTitle, sourceBook.BookID AS SourceBookID, sourceBook.BookTitle AS SourceBookTitle,
|
||||||
|
@TargetChapterID AS TargetChapterID, targetChapter.ChapterNumber AS TargetChapterNumber, targetChapter.ChapterTitle AS TargetChapterTitle,
|
||||||
|
targetBook.BookID AS TargetBookID, targetBook.BookTitle AS TargetBookTitle, @Position AS Position,
|
||||||
|
(SELECT COUNT(*) FROM dbo.SceneDependencies WHERE IsArchived = 0 AND @SceneID IN (SourceSceneID, TargetSceneID)) AS DependencyCount,
|
||||||
|
(SELECT COUNT(*) FROM dbo.ContinuityWarnings WHERE IsDismissed = 0 AND IsIntentional = 0 AND SceneID = @SceneID) AS ActiveWarningCount
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters sourceChapter ON sourceChapter.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books sourceBook ON sourceBook.BookID = sourceChapter.BookID
|
||||||
|
INNER JOIN dbo.Chapters targetChapter ON targetChapter.ChapterID = @TargetChapterID
|
||||||
|
INNER JOIN dbo.Books targetBook ON targetBook.BookID = targetChapter.BookID
|
||||||
|
WHERE s.SceneID = @SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Scene_MoveToChapter
|
||||||
|
@SceneID int,
|
||||||
|
@TargetChapterID int,
|
||||||
|
@Position nvarchar(20) = N'End',
|
||||||
|
@Renumber bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @SourceChapterID int = (SELECT ChapterID FROM dbo.Scenes WHERE SceneID = @SceneID);
|
||||||
|
DECLARE @NewSortOrder int;
|
||||||
|
|
||||||
|
IF @Position = N'Start'
|
||||||
|
SELECT @NewSortOrder = ISNULL(MIN(SortOrder), 10) - 5 FROM dbo.Scenes WHERE ChapterID = @TargetChapterID AND IsArchived = 0;
|
||||||
|
ELSE
|
||||||
|
SELECT @NewSortOrder = ISNULL(MAX(SortOrder), 0) + 10 FROM dbo.Scenes WHERE ChapterID = @TargetChapterID AND IsArchived = 0 AND SceneID <> @SceneID;
|
||||||
|
|
||||||
|
UPDATE dbo.Scenes
|
||||||
|
SET ChapterID = @TargetChapterID, SortOrder = @NewSortOrder, UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE SceneID = @SceneID;
|
||||||
|
|
||||||
|
EXEC dbo.Scene_NormalizeSortOrder @SourceChapterID;
|
||||||
|
EXEC dbo.Scene_NormalizeSortOrder @TargetChapterID;
|
||||||
|
|
||||||
|
IF @Renumber = 1
|
||||||
|
BEGIN
|
||||||
|
EXEC dbo.Scene_RenumberChapter @SourceChapterID;
|
||||||
|
EXEC dbo.Scene_RenumberChapter @TargetChapterID;
|
||||||
|
END;
|
||||||
|
|
||||||
|
SELECT @SceneID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.SceneDependency_FinaliseWarnings
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @ErrorSeverityID int = (SELECT WarningSeverityID FROM dbo.WarningSeverities WHERE SeverityName = N'Error');
|
||||||
|
DECLARE @WarningSeverityID int = (SELECT WarningSeverityID FROM dbo.WarningSeverities WHERE SeverityName = N'Warning');
|
||||||
|
DECLARE @WarningTypeID int = (SELECT WarningTypeID FROM dbo.WarningTypes WHERE TypeName = N'Scene Dependency Broken');
|
||||||
|
|
||||||
|
UPDATE cw
|
||||||
|
SET WarningSeverityID = CASE WHEN sd.IsHardDependency = 1 THEN @ErrorSeverityID ELSE @WarningSeverityID END,
|
||||||
|
Message = CONCAT(N'Possible scene order issue: ''', sourceOrder.SceneTitle, N''' is now after ''', targetOrder.SceneTitle, N'''.'),
|
||||||
|
Details = COALESCE(sd.Description, cw.Details),
|
||||||
|
LastDetectedDate = SYSUTCDATETIME()
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.SceneDependencies sd ON sd.SceneDependencyID = cw.EntityID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder sourceOrder ON sourceOrder.SceneID = sd.SourceSceneID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder targetOrder ON targetOrder.SceneID = sd.TargetSceneID
|
||||||
|
WHERE cw.ProjectID = @ProjectID
|
||||||
|
AND cw.WarningTypeID = @WarningTypeID
|
||||||
|
AND cw.EntityType = N'SceneDependency'
|
||||||
|
AND (@BookID IS NULL OR cw.BookID = @BookID);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
280
PlotLine/Sql/010_Phase1I_Analytics.sql
Normal file
280
PlotLine/Sql/010_Phase1I_Analytics.sql
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.Analytics_StoryHealth
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@StartChapterNumber decimal(10,2) = NULL,
|
||||||
|
@EndChapterNumber decimal(10,2) = NULL,
|
||||||
|
@StartSceneNumber decimal(10,2) = NULL,
|
||||||
|
@EndSceneNumber decimal(10,2) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.*
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
WHERE nso.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||||
|
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||||
|
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||||
|
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||||
|
)
|
||||||
|
SELECT ss.SceneID, ss.BookID, ss.BookTitle, ss.ChapterID, ss.ChapterNumber, ss.ChapterTitle,
|
||||||
|
ss.SceneNumber, ss.SceneTitle, ss.GlobalSceneIndex, ss.BookSceneIndex, smt.MetricTypeID,
|
||||||
|
smt.MetricName, smv.Value, smt.MinValue, smt.MaxValue,
|
||||||
|
CASE WHEN smt.MaxValue = smt.MinValue THEN 0 ELSE CAST(((smv.Value - smt.MinValue) * 100.0) / (smt.MaxValue - smt.MinValue) AS int) END AS [Percent]
|
||||||
|
FROM ScopeScenes ss
|
||||||
|
INNER JOIN dbo.SceneMetricValues smv ON smv.SceneID = ss.SceneID
|
||||||
|
INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
|
||||||
|
WHERE smt.IsActive = 1
|
||||||
|
AND smt.MetricName IN (N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action', N'Romance', N'Violence', N'Sexual Charge', N'Mystery', N'Hope / Lightness', N'Comedy', N'Darkness')
|
||||||
|
ORDER BY smt.SortOrder, ss.GlobalSceneIndex;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID, nso.ChapterID
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
WHERE nso.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||||
|
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||||
|
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||||
|
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||||
|
)
|
||||||
|
SELECT rs.RevisionStatusID, rs.StatusName, COUNT(s.SceneID) AS SceneCount,
|
||||||
|
SUM(CASE WHEN c.ChapterID IS NOT NULL THEN 1 ELSE 0 END) AS ChapterCount
|
||||||
|
FROM dbo.RevisionStatuses rs
|
||||||
|
LEFT JOIN dbo.Scenes s ON s.RevisionStatusID = rs.RevisionStatusID AND s.SceneID IN (SELECT SceneID FROM ScopeScenes)
|
||||||
|
LEFT JOIN dbo.Chapters c ON c.RevisionStatusID = rs.RevisionStatusID AND c.ChapterID IN (SELECT DISTINCT ChapterID FROM ScopeScenes)
|
||||||
|
GROUP BY rs.RevisionStatusID, rs.StatusName, rs.SortOrder
|
||||||
|
ORDER BY rs.SortOrder;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID, nso.BookID, nso.BookTitle, nso.ChapterID, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
WHERE nso.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||||
|
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||||
|
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||||
|
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||||
|
)
|
||||||
|
SELECT ss.SceneID, ss.BookID, ss.BookTitle, ss.ChapterID, ss.ChapterNumber, ss.SceneNumber, ss.SceneTitle,
|
||||||
|
rs.StatusName AS RevisionStatusName
|
||||||
|
FROM ScopeScenes ss
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ss.SceneID
|
||||||
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||||
|
WHERE rs.StatusName IN (N'Needs Work', N'Cut Candidate', N'Drafted')
|
||||||
|
ORDER BY rs.SortOrder, ss.BookID, ss.ChapterNumber, ss.SceneNumber;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
WHERE nso.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||||
|
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||||
|
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||||
|
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||||
|
),
|
||||||
|
Total AS (SELECT COUNT(*) AS TotalScenes FROM ScopeScenes)
|
||||||
|
SELECT spt.ScenePurposeTypeID, spt.PurposeName, COUNT(sp.SceneID) AS SceneCount,
|
||||||
|
CASE WHEN Total.TotalScenes = 0 THEN 0 ELSE CAST(COUNT(sp.SceneID) * 100.0 / Total.TotalScenes AS decimal(5,1)) END AS PercentOfScenes
|
||||||
|
FROM dbo.ScenePurposeTypes spt
|
||||||
|
CROSS JOIN Total
|
||||||
|
LEFT JOIN dbo.ScenePurposes sp ON sp.ScenePurposeTypeID = spt.ScenePurposeTypeID AND sp.SceneID IN (SELECT SceneID FROM ScopeScenes)
|
||||||
|
WHERE spt.IsArchived = 0
|
||||||
|
GROUP BY spt.ScenePurposeTypeID, spt.PurposeName, spt.SortOrder, Total.TotalScenes
|
||||||
|
ORDER BY spt.SortOrder;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID, nso.BookID, nso.BookTitle, nso.ChapterID, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = nso.SceneID
|
||||||
|
WHERE nso.ProjectID = @ProjectID
|
||||||
|
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||||
|
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||||
|
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||||
|
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||||
|
AND (
|
||||||
|
NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = nso.SceneID)
|
||||||
|
OR ISNULL(NULLIF(LTRIM(RTRIM(s.SceneOutcomeNotes)), N''), N'') = N''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SELECT ss.SceneID, ss.BookID, ss.BookTitle, ss.ChapterID, ss.ChapterNumber, ss.SceneNumber, ss.SceneTitle,
|
||||||
|
CASE WHEN NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = ss.SceneID) THEN 1 ELSE 0 END AS MissingPurpose,
|
||||||
|
CASE WHEN ISNULL(NULLIF(LTRIM(RTRIM(s.SceneOutcomeNotes)), N''), N'') = N'' THEN 1 ELSE 0 END AS MissingOutcome
|
||||||
|
FROM ScopeScenes ss
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = ss.SceneID
|
||||||
|
ORDER BY ss.BookID, ss.ChapterNumber, ss.SceneNumber;
|
||||||
|
|
||||||
|
;WITH EventScope AS
|
||||||
|
(
|
||||||
|
SELECT pt.PlotThreadID, MAX(nso.GlobalSceneIndex) AS LastTouchedIndex, MAX(nso.SceneID) AS LastTouchedSceneID,
|
||||||
|
MAX(CONCAT(N'Ch ', nso.ChapterNumber, N' / Scene ', nso.SceneNumber, N': ', nso.SceneTitle)) AS LastTouchedSceneLabel
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.ThreadEvents te ON te.PlotThreadID = pt.PlotThreadID
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
GROUP BY pt.PlotThreadID
|
||||||
|
)
|
||||||
|
SELECT pt.PlotThreadID, pt.ThreadTitle, pl.PlotLineID, pl.PlotLineName, pt.Importance,
|
||||||
|
ts.StatusName AS ThreadStatusName, ts.IsOpenStatus, ts.IsResolvedStatus,
|
||||||
|
pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
|
||||||
|
EventScope.LastTouchedSceneID, EventScope.LastTouchedSceneLabel,
|
||||||
|
CASE WHEN ts.IsOpenStatus = 1 AND pt.Importance >= 8 THEN 1 ELSE 0 END AS IsHighImportanceOpen,
|
||||||
|
CASE WHEN ts.IsOpenStatus = 1 AND EventScope.LastTouchedIndex IS NOT NULL
|
||||||
|
AND (SELECT MAX(GlobalSceneIndex) FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)) - EventScope.LastTouchedIndex > 8 THEN 1 ELSE 0 END AS IsDormant,
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName = N'Clue Planted'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName IN (N'Payoff', N'Question Answered', N'Revealed', N'Resolved')
|
||||||
|
) THEN 1 ELSE 0 END AS HasClueWithoutPayoff,
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName = N'Question Raised'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||||
|
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName = N'Question Answered'
|
||||||
|
) THEN 1 ELSE 0 END AS HasQuestionWithoutAnswer
|
||||||
|
FROM dbo.PlotThreads pt
|
||||||
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
|
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
|
||||||
|
LEFT JOIN EventScope ON EventScope.PlotThreadID = pt.PlotThreadID
|
||||||
|
WHERE pl.ProjectID = @ProjectID AND pt.IsArchived = 0 AND pl.IsArchived = 0
|
||||||
|
AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID)
|
||||||
|
ORDER BY ts.IsOpenStatus DESC, pt.Importance DESC, pl.PlotLineName, pt.ThreadTitle;
|
||||||
|
|
||||||
|
;WITH LastAssetEvent AS
|
||||||
|
(
|
||||||
|
SELECT ae.StoryAssetID, MAX(nso.GlobalSceneIndex) AS LastEventIndex, MAX(ae.SceneID) AS LastEventSceneID,
|
||||||
|
MAX(CONCAT(N'Ch ', nso.ChapterNumber, N' / Scene ', nso.SceneNumber, N': ', nso.SceneTitle)) AS LastEventSceneLabel
|
||||||
|
FROM dbo.AssetEvents ae
|
||||||
|
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
GROUP BY ae.StoryAssetID
|
||||||
|
)
|
||||||
|
SELECT sa.StoryAssetID, sa.AssetName, ak.KindName, sa.Importance, sa.CurrentStateID,
|
||||||
|
ast.StateName AS CurrentStateName, sa.CurrentLocationID, location.LocationPath AS CurrentLocationPath,
|
||||||
|
sa.IsResolved, LastAssetEvent.LastEventSceneID, LastAssetEvent.LastEventSceneLabel,
|
||||||
|
CASE WHEN EXISTS (SELECT 1 FROM dbo.AssetDependencies ad WHERE ad.IsArchived = 0 AND (ad.SourceAssetID = sa.StoryAssetID OR ad.TargetAssetID = sa.StoryAssetID)) THEN 1 ELSE 0 END AS HasDependencies,
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||||
|
WHERE cw.ProjectID = @ProjectID AND cw.EntityType IN (N'StoryAsset', N'AssetDependency') AND cw.EntityID IN (sa.StoryAssetID)
|
||||||
|
AND wt.TypeName LIKE N'Asset%' AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||||
|
) THEN 1 ELSE 0 END AS HasDependencyWarnings
|
||||||
|
FROM dbo.StoryAssets sa
|
||||||
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
|
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sa.CurrentLocationID
|
||||||
|
LEFT JOIN LastAssetEvent ON LastAssetEvent.StoryAssetID = sa.StoryAssetID
|
||||||
|
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0
|
||||||
|
ORDER BY sa.IsResolved, sa.Importance DESC, sa.AssetName;
|
||||||
|
|
||||||
|
SELECT ws.SeverityName, COUNT(*) AS WarningCount
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||||
|
WHERE cw.ProjectID = @ProjectID AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||||
|
GROUP BY ws.SeverityName, ws.SortOrder
|
||||||
|
ORDER BY ws.SortOrder DESC;
|
||||||
|
|
||||||
|
SELECT TOP (12) wt.TypeName AS WarningTypeName, COUNT(*) AS WarningCount
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||||
|
WHERE cw.ProjectID = @ProjectID AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||||
|
GROUP BY wt.TypeName
|
||||||
|
ORDER BY COUNT(*) DESC, wt.TypeName;
|
||||||
|
|
||||||
|
SELECT TOP (12) cw.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, b.BookTitle,
|
||||||
|
COUNT(*) AS WarningCount, MAX(ws.SortOrder) AS HighestSeveritySort
|
||||||
|
FROM dbo.ContinuityWarnings cw
|
||||||
|
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = cw.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE cw.ProjectID = @ProjectID AND cw.SceneID IS NOT NULL AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||||
|
AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||||
|
GROUP BY cw.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, b.BookTitle
|
||||||
|
ORDER BY COUNT(*) DESC, MAX(ws.SortOrder) DESC;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID, nso.BookID, nso.BookTitle, nso.ChapterID, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle,
|
||||||
|
COALESCE(s.POVCharacterID, c.POVCharacterID) AS POVCharacterID
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = nso.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = nso.ChapterID
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||||
|
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||||
|
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||||
|
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||||
|
)
|
||||||
|
SELECT COALESCE(ch.CharacterName, N'No POV') AS POVName, ss.POVCharacterID, COUNT(*) AS SceneCount
|
||||||
|
FROM ScopeScenes ss
|
||||||
|
LEFT JOIN dbo.Characters ch ON ch.CharacterID = ss.POVCharacterID
|
||||||
|
GROUP BY ss.POVCharacterID, ch.CharacterName
|
||||||
|
ORDER BY COUNT(*) DESC, POVName;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID, nso.ChapterID
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
)
|
||||||
|
SELECT c.ChapterID, c.ChapterNumber, c.ChapterTitle, COUNT(DISTINCT COALESCE(s.POVCharacterID, c.POVCharacterID)) AS POVCount
|
||||||
|
FROM dbo.Chapters c
|
||||||
|
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID
|
||||||
|
WHERE c.ChapterID IN (SELECT ChapterID FROM ScopeScenes)
|
||||||
|
GROUP BY c.ChapterID, c.ChapterNumber, c.ChapterTitle
|
||||||
|
HAVING COUNT(DISTINCT COALESCE(s.POVCharacterID, c.POVCharacterID)) > 1
|
||||||
|
ORDER BY c.ChapterNumber;
|
||||||
|
|
||||||
|
SELECT ch.CharacterID, ch.CharacterName, COUNT(DISTINCT sc.SceneID) AS SceneCount
|
||||||
|
FROM dbo.Characters ch
|
||||||
|
LEFT JOIN dbo.SceneCharacters sc ON sc.CharacterID = ch.CharacterID
|
||||||
|
LEFT JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||||
|
WHERE ch.ProjectID = @ProjectID AND ch.IsArchived = 0 AND (@BookID IS NULL OR nso.BookID = @BookID OR sc.SceneID IS NULL)
|
||||||
|
GROUP BY ch.CharacterID, ch.CharacterName
|
||||||
|
ORDER BY COUNT(DISTINCT sc.SceneID) DESC, ch.CharacterName;
|
||||||
|
|
||||||
|
;WITH SceneCharacterCounts AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID, nso.SceneNumber, nso.SceneTitle, nso.ChapterNumber, COUNT(sc.SceneCharacterID) AS CharacterCount
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
LEFT JOIN dbo.SceneCharacters sc ON sc.SceneID = nso.SceneID
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
GROUP BY nso.SceneID, nso.SceneNumber, nso.SceneTitle, nso.ChapterNumber
|
||||||
|
)
|
||||||
|
SELECT TOP (12) * FROM SceneCharacterCounts WHERE CharacterCount >= 5 ORDER BY CharacterCount DESC, ChapterNumber, SceneNumber;
|
||||||
|
|
||||||
|
;WITH ScopeScenes AS
|
||||||
|
(
|
||||||
|
SELECT nso.SceneID, s.PrimaryLocationID
|
||||||
|
FROM dbo.NarrativeSceneOrder nso
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = nso.SceneID
|
||||||
|
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||||
|
)
|
||||||
|
SELECT COALESCE(lp.LocationName, N'No location') AS LocationName, lp.LocationID, COALESCE(lp.LocationPath, N'No location') AS LocationPath,
|
||||||
|
COUNT(*) AS SceneCount
|
||||||
|
FROM ScopeScenes ss
|
||||||
|
LEFT JOIN dbo.LocationPaths lp ON lp.LocationID = ss.PrimaryLocationID
|
||||||
|
GROUP BY lp.LocationID, lp.LocationName, lp.LocationPath
|
||||||
|
ORDER BY COUNT(*) DESC, LocationName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
737
PlotLine/ViewModels/CoreViewModels.cs
Normal file
737
PlotLine/ViewModels/CoreViewModels.cs
Normal file
@ -0,0 +1,737 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using PlotLine.Models;
|
||||||
|
|
||||||
|
namespace PlotLine.ViewModels;
|
||||||
|
|
||||||
|
public sealed class ProjectEditViewModel
|
||||||
|
{
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Project / Series name")]
|
||||||
|
public string ProjectName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ProjectDetailViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public IReadOnlyList<Book> Books { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BookEditViewModel
|
||||||
|
{
|
||||||
|
public int BookID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Book title")]
|
||||||
|
public string BookTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Display(Name = "Book number")]
|
||||||
|
public int BookNumber { get; set; } = 1;
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BookDetailViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public Book Book { get; set; } = new();
|
||||||
|
public IReadOnlyList<Chapter> Chapters { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ChapterEditViewModel
|
||||||
|
{
|
||||||
|
public int ChapterID { get; set; }
|
||||||
|
public int BookID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Chapter number")]
|
||||||
|
public decimal ChapterNumber { get; set; } = 1;
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Chapter title")]
|
||||||
|
public string ChapterTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Revision status")]
|
||||||
|
public int RevisionStatusID { get; set; }
|
||||||
|
|
||||||
|
public Book? Book { get; set; }
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
public IReadOnlyList<SelectListItem> RevisionStatuses { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ChapterDetailViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public Book Book { get; set; } = new();
|
||||||
|
public Chapter Chapter { get; set; } = new();
|
||||||
|
public IReadOnlyList<Scene> Scenes { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SceneEditViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int ChapterID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Scene number")]
|
||||||
|
public decimal SceneNumber { get; set; } = 1;
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Scene title")]
|
||||||
|
public string SceneTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Time mode")]
|
||||||
|
public int TimeModeID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Start date/time")]
|
||||||
|
public DateTime? StartDateTime { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "End date/time")]
|
||||||
|
public DateTime? EndDateTime { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Duration")]
|
||||||
|
public decimal? DurationAmount { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Duration unit")]
|
||||||
|
public int? DurationUnitID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Relative time")]
|
||||||
|
public string? RelativeTimeText { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Time confidence")]
|
||||||
|
public int TimeConfidenceID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Purpose notes")]
|
||||||
|
public string? ScenePurposeNotes { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Outcome notes")]
|
||||||
|
public string? SceneOutcomeNotes { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Revision status")]
|
||||||
|
public int RevisionStatusID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Primary location")]
|
||||||
|
public int? PrimaryLocationID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Scene purposes")]
|
||||||
|
public List<int> SelectedPurposeTypeIds { get; set; } = [];
|
||||||
|
|
||||||
|
public List<SceneMetricInputViewModel> Metrics { get; set; } = [];
|
||||||
|
public IReadOnlyList<ThreadEvent> ThreadEvents { get; set; } = [];
|
||||||
|
public ThreadEventCreateViewModel NewThreadEvent { get; set; } = new();
|
||||||
|
public IReadOnlyList<AssetEvent> AssetEvents { get; set; } = [];
|
||||||
|
public IReadOnlyList<AssetCustodyEvent> AssetCustodyEvents { get; set; } = [];
|
||||||
|
public IReadOnlyList<SceneAssetLocation> SceneAssetLocations { get; set; } = [];
|
||||||
|
public AssetEventCreateViewModel NewAssetEvent { get; set; } = new();
|
||||||
|
public AssetCustodyCreateViewModel NewAssetCustodyEvent { get; set; } = new();
|
||||||
|
public SceneAssetLocationCreateViewModel NewSceneAssetLocation { get; set; } = new();
|
||||||
|
public IReadOnlyList<SceneCharacter> SceneCharacters { get; set; } = [];
|
||||||
|
public IReadOnlyList<CharacterAttributeEvent> CharacterAttributeEvents { get; set; } = [];
|
||||||
|
public IReadOnlyList<CharacterKnowledgeItem> CharacterKnowledge { get; set; } = [];
|
||||||
|
public IReadOnlyList<RelationshipEvent> RelationshipEvents { get; set; } = [];
|
||||||
|
public SceneCharacterCreateViewModel NewSceneCharacter { get; set; } = new();
|
||||||
|
public CharacterAttributeEventCreateViewModel NewCharacterAttributeEvent { get; set; } = new();
|
||||||
|
public CharacterKnowledgeCreateViewModel NewCharacterKnowledge { get; set; } = new();
|
||||||
|
public RelationshipEventCreateViewModel NewRelationshipEvent { get; set; } = new();
|
||||||
|
public IReadOnlyList<SelectListItem> PlotLineOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> PlotThreadOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> ThreadTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> ThreadEventTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetKindOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetStateOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetEventTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetCustodyEventTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> CustodyRoleOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> CharacterOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> CharacterRoleOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> PresenceTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> CharacterAttributeTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> KnowledgeStateOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> RelationshipOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> RelationshipTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> RelationshipStateOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> LocationOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> SceneOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> ChapterOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> SceneDependencyTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<string> AssetDependencyMessages { get; set; } = [];
|
||||||
|
public IReadOnlyList<string> LocationConsistencyMessages { get; set; } = [];
|
||||||
|
public IReadOnlyList<ContinuityWarning> Warnings { get; set; } = [];
|
||||||
|
public IReadOnlyList<SceneDependency> DependenciesThisSceneNeeds { get; set; } = [];
|
||||||
|
public IReadOnlyList<SceneDependency> DependenciesNeedingThisScene { get; set; } = [];
|
||||||
|
public SceneDependencyCreateViewModel NewDependency { get; set; } = new();
|
||||||
|
public SceneMoveRequestViewModel MoveRequest { get; set; } = new();
|
||||||
|
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
public bool ReturnToTimeline { get; set; }
|
||||||
|
|
||||||
|
public Chapter? Chapter { get; set; }
|
||||||
|
public Book? Book { get; set; }
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
public IReadOnlyList<SelectListItem> RevisionStatuses { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> TimeModes { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> DurationUnits { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> TimeConfidences { get; set; } = [];
|
||||||
|
public IReadOnlyList<ScenePurposeType> ScenePurposeTypes { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class TimelineViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public int? SelectedBookID { get; set; }
|
||||||
|
public int? SelectedSceneID { get; set; }
|
||||||
|
public SceneEditViewModel? SelectedScene { get; set; }
|
||||||
|
public IReadOnlyList<BookTimelineViewModel> Books { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<Scene> OrderedScenes { get; set; } = [];
|
||||||
|
public IReadOnlyList<MetricGraphViewModel> MetricGraphs { get; set; } = [];
|
||||||
|
public IReadOnlyList<PlotLineLaneViewModel> PlotLanes { get; set; } = [];
|
||||||
|
public IReadOnlyList<AssetLaneViewModel> AssetLanes { get; set; } = [];
|
||||||
|
public IReadOnlyList<CharacterLaneViewModel> CharacterLanes { get; set; } = [];
|
||||||
|
public ContinuityValidationSummary? LastValidationSummary { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BookTimelineViewModel
|
||||||
|
{
|
||||||
|
public Book Book { get; set; } = new();
|
||||||
|
public IReadOnlyList<ChapterTimelineViewModel> Chapters { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ChapterTimelineViewModel
|
||||||
|
{
|
||||||
|
public Chapter Chapter { get; set; } = new();
|
||||||
|
public IReadOnlyList<Scene> Scenes { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SceneMetricInputViewModel
|
||||||
|
{
|
||||||
|
public int MetricTypeID { get; set; }
|
||||||
|
public string MetricName { get; set; } = string.Empty;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public int MinValue { get; set; }
|
||||||
|
public int MaxValue { get; set; }
|
||||||
|
public int Value { get; set; }
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MetricGraphViewModel
|
||||||
|
{
|
||||||
|
public int MetricTypeID { get; set; }
|
||||||
|
public string MetricName { get; set; } = string.Empty;
|
||||||
|
public IReadOnlyList<MetricGraphPointViewModel> Points { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MetricGraphPointViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public decimal SceneNumber { get; set; }
|
||||||
|
public string SceneTitle { get; set; } = string.Empty;
|
||||||
|
public int Value { get; set; }
|
||||||
|
public int Percent { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlotLineEditViewModel
|
||||||
|
{
|
||||||
|
public int PlotLineID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Plot line name")]
|
||||||
|
public string PlotLineName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Display(Name = "Type")]
|
||||||
|
public int PlotLineTypeID { get; set; }
|
||||||
|
|
||||||
|
public int? BookID { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Parent plot line")]
|
||||||
|
public int? ParentPlotLineID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Sort order")]
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Colour")]
|
||||||
|
public string Colour { get; set; } = "#2f6f63";
|
||||||
|
|
||||||
|
[Display(Name = "Main plot")]
|
||||||
|
public bool IsMainPlot { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Visible on timeline")]
|
||||||
|
public bool IsVisibleOnTimeline { get; set; } = true;
|
||||||
|
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
public IReadOnlyList<SelectListItem> PlotLineTypes { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> ParentPlotLineOptions { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlotLineListViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public IReadOnlyList<PlotLineItem> PlotLines { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlotThreadEditViewModel
|
||||||
|
{
|
||||||
|
public int PlotThreadID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Plot line")]
|
||||||
|
public int PlotLineID { get; set; }
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Thread title")]
|
||||||
|
public string ThreadTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Display(Name = "Type")]
|
||||||
|
public int ThreadTypeID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Status")]
|
||||||
|
public int ThreadStatusID { get; set; }
|
||||||
|
|
||||||
|
[Range(1, 10)]
|
||||||
|
public int Importance { get; set; } = 5;
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Introduced scene")]
|
||||||
|
public int? IntroducedSceneID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Planned resolution scene")]
|
||||||
|
public int? PlannedResolutionSceneID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Actual resolution scene")]
|
||||||
|
public int? ActualResolutionSceneID { get; set; }
|
||||||
|
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
public IReadOnlyList<SelectListItem> PlotLineOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> ThreadTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> ThreadStatusOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> SceneOptions { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlotThreadListViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public IReadOnlyList<PlotThread> PlotThreads { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ThreadEventCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int? PlotLineID { get; set; }
|
||||||
|
public int? PlotThreadID { get; set; }
|
||||||
|
public string? NewThreadTitle { get; set; }
|
||||||
|
public int? NewThreadTypeID { get; set; }
|
||||||
|
public int EventTypeID { get; set; }
|
||||||
|
public string EventTitle { get; set; } = string.Empty;
|
||||||
|
public string? EventDescription { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlotLineLaneViewModel
|
||||||
|
{
|
||||||
|
public PlotLineItem PlotLine { get; set; } = new();
|
||||||
|
public IReadOnlyList<PlotLaneSceneViewModel> SceneSlots { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlotLaneSceneViewModel
|
||||||
|
{
|
||||||
|
public Scene Scene { get; set; } = new();
|
||||||
|
public IReadOnlyList<ThreadEvent> Events { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StoryAssetListViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public IReadOnlyList<StoryAsset> Assets { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StoryAssetEditViewModel
|
||||||
|
{
|
||||||
|
public int StoryAssetID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Asset name")]
|
||||||
|
public string AssetName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Display(Name = "Kind")]
|
||||||
|
public int AssetKindID { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
[Range(1, 10)]
|
||||||
|
public int Importance { get; set; } = 5;
|
||||||
|
|
||||||
|
[Display(Name = "Current state")]
|
||||||
|
public int? CurrentStateID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Current location")]
|
||||||
|
public int? CurrentLocationID { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Resolved")]
|
||||||
|
public bool IsResolved { get; set; }
|
||||||
|
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
public IReadOnlyList<SelectListItem> AssetKindOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetStateOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> LocationOptions { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StoryAssetDetailViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public StoryAsset Asset { get; set; } = new();
|
||||||
|
public IReadOnlyList<StoryAsset> ProjectAssets { get; set; } = [];
|
||||||
|
public IReadOnlyList<AssetEvent> Events { get; set; } = [];
|
||||||
|
public IReadOnlyList<AssetDependency> Dependencies { get; set; } = [];
|
||||||
|
public IReadOnlyList<AssetCustodyEvent> CustodyEvents { get; set; } = [];
|
||||||
|
public AssetDependencyEditViewModel NewDependency { get; set; } = new();
|
||||||
|
public IReadOnlyList<SelectListItem> AssetOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetDependencyTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> AssetStateOptions { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AssetEventCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int? StoryAssetID { get; set; }
|
||||||
|
public string? NewAssetName { get; set; }
|
||||||
|
public int? NewAssetKindID { get; set; }
|
||||||
|
public int AssetEventTypeID { get; set; }
|
||||||
|
public int? FromStateID { get; set; }
|
||||||
|
public int? ToStateID { get; set; }
|
||||||
|
public string EventTitle { get; set; } = string.Empty;
|
||||||
|
public string? EventDescription { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AssetCustodyCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int StoryAssetID { get; set; }
|
||||||
|
public int AssetCustodyEventTypeID { get; set; }
|
||||||
|
public int CustodyRoleID { get; set; }
|
||||||
|
public List<int> CustodianCharacterIds { get; set; } = [];
|
||||||
|
public string CustodianNames { get; set; } = string.Empty;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SceneAssetLocationCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneAssetLocationID { get; set; }
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int StoryAssetID { get; set; }
|
||||||
|
public int LocationID { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public bool UpdateCurrentLocation { get; set; } = true;
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AssetDependencyEditViewModel
|
||||||
|
{
|
||||||
|
public int AssetDependencyID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
public int SourceAssetID { get; set; }
|
||||||
|
public int TargetAssetID { get; set; }
|
||||||
|
public int AssetDependencyTypeID { get; set; }
|
||||||
|
public int? SourceRequiredStateID { get; set; }
|
||||||
|
public int? TargetBlockedStateID { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AssetLaneViewModel
|
||||||
|
{
|
||||||
|
public StoryAsset Asset { get; set; } = new();
|
||||||
|
public IReadOnlyList<AssetLaneSceneViewModel> SceneSlots { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AssetLaneSceneViewModel
|
||||||
|
{
|
||||||
|
public Scene Scene { get; set; } = new();
|
||||||
|
public IReadOnlyList<AssetEvent> Events { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterListViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public IReadOnlyList<Character> Characters { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterEditViewModel
|
||||||
|
{
|
||||||
|
public int CharacterID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Character name")]
|
||||||
|
public string CharacterName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Display(Name = "Short name")]
|
||||||
|
public string? ShortName { get; set; }
|
||||||
|
|
||||||
|
public string? Sex { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Birth date")]
|
||||||
|
public DateTime? BirthDate { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Age at series start")]
|
||||||
|
public int? AgeAtSeriesStart { get; set; }
|
||||||
|
|
||||||
|
public string? Height { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Eye colour")]
|
||||||
|
public string? EyeColour { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Default description")]
|
||||||
|
public string? DefaultDescription { get; set; }
|
||||||
|
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterDetailViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public Character Character { get; set; } = new();
|
||||||
|
public string DisplayAge { get; set; } = "Unknown";
|
||||||
|
public IReadOnlyList<SceneCharacter> Appearances { get; set; } = [];
|
||||||
|
public IReadOnlyList<CharacterAttributeEvent> AttributeEvents { get; set; } = [];
|
||||||
|
public IReadOnlyList<CharacterKnowledgeItem> KnowledgeItems { get; set; } = [];
|
||||||
|
public IReadOnlyList<CharacterRelationship> Relationships { get; set; } = [];
|
||||||
|
public IReadOnlyList<RelationshipEvent> RelationshipEvents { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SceneCharacterCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int? CharacterID { get; set; }
|
||||||
|
public string? NewCharacterName { get; set; }
|
||||||
|
public int? RoleInSceneTypeID { get; set; }
|
||||||
|
public int? PresenceTypeID { get; set; }
|
||||||
|
public int? LocationID { get; set; }
|
||||||
|
public int? EntryLocationID { get; set; }
|
||||||
|
public int? ExitLocationID { get; set; }
|
||||||
|
public string? AppearanceNotes { get; set; }
|
||||||
|
public string? OutfitDescription { get; set; }
|
||||||
|
public string? PhysicalCondition { get; set; }
|
||||||
|
public string? EmotionalState { get; set; }
|
||||||
|
public string? KnowledgeNotes { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterAttributeEventCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int CharacterID { get; set; }
|
||||||
|
public int CharacterAttributeTypeID { get; set; }
|
||||||
|
public string AttributeValue { get; set; } = string.Empty;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterKnowledgeCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int CharacterID { get; set; }
|
||||||
|
public int? StoryAssetID { get; set; }
|
||||||
|
public int? PlotThreadID { get; set; }
|
||||||
|
public int KnowledgeStateID { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RelationshipEventCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int? CharacterRelationshipID { get; set; }
|
||||||
|
public int? CharacterAID { get; set; }
|
||||||
|
public int? CharacterBID { get; set; }
|
||||||
|
public int? RelationshipTypeID { get; set; }
|
||||||
|
public int RelationshipStateID { get; set; }
|
||||||
|
public int? Intensity { get; set; }
|
||||||
|
public bool IsKnownToOtherCharacter { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterLaneViewModel
|
||||||
|
{
|
||||||
|
public Character Character { get; set; } = new();
|
||||||
|
public IReadOnlyList<CharacterLaneSceneViewModel> SceneSlots { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterLaneSceneViewModel
|
||||||
|
{
|
||||||
|
public Scene Scene { get; set; } = new();
|
||||||
|
public IReadOnlyList<SceneCharacter> Appearances { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LocationListViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public IReadOnlyList<LocationItem> Locations { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LocationEditViewModel
|
||||||
|
{
|
||||||
|
public int LocationID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
public int? ParentLocationID { get; set; }
|
||||||
|
|
||||||
|
[Required, StringLength(200)]
|
||||||
|
[Display(Name = "Location name")]
|
||||||
|
public string LocationName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Display(Name = "Location type")]
|
||||||
|
public int? LocationTypeID { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public Project? Project { get; set; }
|
||||||
|
public IReadOnlyList<SelectListItem> ParentLocationOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> LocationTypeOptions { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LocationDetailViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public LocationItem Location { get; set; } = new();
|
||||||
|
public IReadOnlyList<LocationItem> ChildLocations { get; set; } = [];
|
||||||
|
public IReadOnlyList<LocationRelationship> Relationships { get; set; } = [];
|
||||||
|
public IReadOnlyList<Scene> Scenes { get; set; } = [];
|
||||||
|
public IReadOnlyList<SceneCharacter> Characters { get; set; } = [];
|
||||||
|
public IReadOnlyList<SceneAssetLocation> Assets { get; set; } = [];
|
||||||
|
public LocationRelationshipEditViewModel NewRelationship { get; set; } = new();
|
||||||
|
public IReadOnlyList<SelectListItem> LocationOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> RelationshipTypeOptions { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LocationRelationshipEditViewModel
|
||||||
|
{
|
||||||
|
public int LocationRelationshipID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
public int FromLocationID { get; set; }
|
||||||
|
public int ToLocationID { get; set; }
|
||||||
|
public int LocationRelationshipTypeID { get; set; }
|
||||||
|
public bool IsBidirectional { get; set; }
|
||||||
|
public int? Strength { get; set; }
|
||||||
|
public bool CanHearNormalSpeech { get; set; }
|
||||||
|
public bool CanHearLoudNoise { get; set; }
|
||||||
|
public bool CanSeeMovement { get; set; }
|
||||||
|
public bool CanSeeClearly { get; set; }
|
||||||
|
public bool CanTravelDirectly { get; set; }
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class WarningDashboardViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public int? BookID { get; set; }
|
||||||
|
public int? SceneID { get; set; }
|
||||||
|
public int? WarningTypeID { get; set; }
|
||||||
|
public int? WarningSeverityID { get; set; }
|
||||||
|
public string? EntityType { get; set; }
|
||||||
|
public bool IncludeDismissed { get; set; }
|
||||||
|
public bool IncludeIntentional { get; set; }
|
||||||
|
public IReadOnlyList<ContinuityWarning> Warnings { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> WarningTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> SeverityOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<SelectListItem> EntityTypeOptions { get; set; } = [];
|
||||||
|
public IReadOnlyDictionary<string, int> CountsBySeverity { get; set; } = new Dictionary<string, int>();
|
||||||
|
public IReadOnlyDictionary<string, int> CountsByType { get; set; } = new Dictionary<string, int>();
|
||||||
|
public ContinuityValidationSummary? LastValidationSummary { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SceneDependencyCreateViewModel
|
||||||
|
{
|
||||||
|
public int SceneDependencyID { get; set; }
|
||||||
|
public int ProjectID { get; set; }
|
||||||
|
public int CurrentSceneID { get; set; }
|
||||||
|
public int RelatedSceneID { get; set; }
|
||||||
|
public string Direction { get; set; } = "CurrentDependsOnRelated";
|
||||||
|
public int SceneDependencyTypeID { get; set; }
|
||||||
|
public bool IsHardDependency { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SceneMoveRequestViewModel
|
||||||
|
{
|
||||||
|
public int SceneID { get; set; }
|
||||||
|
public int TargetChapterID { get; set; }
|
||||||
|
public string Position { get; set; } = "End";
|
||||||
|
public bool RenumberScenes { get; set; }
|
||||||
|
public int? ReturnProjectID { get; set; }
|
||||||
|
public int? ReturnBookID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SceneMovePreviewViewModel
|
||||||
|
{
|
||||||
|
public SceneMovePreview Preview { get; set; } = new();
|
||||||
|
public SceneMoveRequestViewModel Request { get; set; } = new();
|
||||||
|
public IReadOnlyList<ContinuityWarning> CurrentWarnings { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AnalyticsViewModel
|
||||||
|
{
|
||||||
|
public Project Project { get; set; } = new();
|
||||||
|
public int? BookID { get; set; }
|
||||||
|
public decimal? StartChapterNumber { get; set; }
|
||||||
|
public decimal? EndChapterNumber { get; set; }
|
||||||
|
public decimal? StartSceneNumber { get; set; }
|
||||||
|
public decimal? EndSceneNumber { get; set; }
|
||||||
|
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsMetricGraphViewModel> MetricGraphs { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsRevisionSummary> RevisionSummary { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsRevisionScene> RevisionAttentionScenes { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsPurposeSummary> PurposeSummary { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsSceneGap> SceneGaps { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsThreadHealth> ThreadHealth { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsAssetHealth> AssetHealth { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsWarningSeverityCount> WarningSeverityCounts { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsWarningTypeCount> WarningTypeCounts { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsWarningHotspot> WarningHotspots { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsPovSummary> PovSummary { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsMultiPovChapter> MultiPovChapters { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsCharacterAppearance> CharacterAppearances { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsBusyScene> BusyScenes { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsLocationUsage> LocationUsage { get; set; } = [];
|
||||||
|
public IReadOnlyList<AnalyticsReviewNote> PacingReviewNotes { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AnalyticsMetricGraphViewModel
|
||||||
|
{
|
||||||
|
public string MetricName { get; set; } = string.Empty;
|
||||||
|
public IReadOnlyList<AnalyticsMetricPoint> Points { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AnalyticsReviewNote
|
||||||
|
{
|
||||||
|
public string MetricName { get; set; } = string.Empty;
|
||||||
|
public string SceneRange { get; set; } = string.Empty;
|
||||||
|
public string ValuePattern { get; set; } = string.Empty;
|
||||||
|
public string ReviewNote { get; set; } = string.Empty;
|
||||||
|
public int? SceneID { get; set; }
|
||||||
|
}
|
||||||
303
PlotLine/Views/Analytics/Index.cshtml
Normal file
303
PlotLine/Views/Analytics/Index.cshtml
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
@model AnalyticsViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Story Health";
|
||||||
|
var openThreads = Model.ThreadHealth.Where(x => x.IsOpenStatus).ToList();
|
||||||
|
var dormantThreads = Model.ThreadHealth.Where(x => x.IsDormant).ToList();
|
||||||
|
var unresolvedAssets = Model.AssetHealth.Where(x => !x.IsResolved).ToList();
|
||||||
|
var metricScenes = Model.MetricGraphs
|
||||||
|
.SelectMany(graph => graph.Points)
|
||||||
|
.GroupBy(point => point.SceneID)
|
||||||
|
.Select(group => group.OrderBy(point => point.GlobalSceneIndex).First())
|
||||||
|
.OrderBy(point => point.GlobalSceneIndex)
|
||||||
|
.Select(point => new
|
||||||
|
{
|
||||||
|
sceneId = point.SceneID,
|
||||||
|
axisLabel = $"{point.ChapterNumber:g}.{point.SceneNumber:g}",
|
||||||
|
chapterLabel = $"Chapter {point.ChapterNumber:g}",
|
||||||
|
sceneNumber = point.SceneNumber.ToString("g"),
|
||||||
|
sceneTitle = point.SceneTitle,
|
||||||
|
bookTitle = point.BookTitle,
|
||||||
|
timelineUrl = Url.Action("Index", "Timeline", new { projectId = Model.Project.ProjectID, bookId = point.BookID, selectedSceneId = point.SceneID })
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
var metricShapePayload = new
|
||||||
|
{
|
||||||
|
scenes = metricScenes,
|
||||||
|
metrics = Model.MetricGraphs.Select(graph => new
|
||||||
|
{
|
||||||
|
metricName = graph.MetricName,
|
||||||
|
points = graph.Points.Select(point => new { sceneId = point.SceneID, value = point.Value })
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Workspace context</span>
|
||||||
|
<strong>@Model.Project.ProjectName</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Workspace navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Timeline</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Analytics / Story health</p>
|
||||||
|
<h1>@Model.Project.ProjectName</h1>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Timeline</a>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Warnings</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="warning-filter-panel">
|
||||||
|
<form method="get" class="row g-2 align-items-end">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Book</label>
|
||||||
|
<select name="bookId" asp-items="Model.BookOptions" class="form-select form-select-sm">
|
||||||
|
<option value="">All books</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Start chapter</label>
|
||||||
|
<input class="form-control form-control-sm" name="startChapterNumber" value="@Model.StartChapterNumber" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">End chapter</label>
|
||||||
|
<input class="form-control form-control-sm" name="endChapterNumber" value="@Model.EndChapterNumber" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Start scene</label>
|
||||||
|
<input class="form-control form-control-sm" name="startSceneNumber" value="@Model.StartSceneNumber" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">End scene</label>
|
||||||
|
<input class="form-control form-control-sm" name="endSceneNumber" value="@Model.EndSceneNumber" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
<button class="btn btn-outline-primary btn-sm" type="submit">View</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="health-kpi-grid">
|
||||||
|
<article class="plain-card"><span class="eyebrow">Open threads</span><strong>@openThreads.Count</strong></article>
|
||||||
|
<article class="plain-card"><span class="eyebrow">Dormant threads</span><strong>@dormantThreads.Count</strong></article>
|
||||||
|
<article class="plain-card"><span class="eyebrow">Unresolved assets</span><strong>@unresolvedAssets.Count</strong></article>
|
||||||
|
<article class="plain-card"><span class="eyebrow">Warning hotspots</span><strong>@Model.WarningHotspots.Count</strong></article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="analytics-section">
|
||||||
|
<div class="analytics-section-heading">
|
||||||
|
<h2>Scene metric shape</h2>
|
||||||
|
<span class="muted">@Model.MetricGraphs.Count metric@(Model.MetricGraphs.Count == 1 ? "" : "s")</span>
|
||||||
|
</div>
|
||||||
|
@if (!Model.MetricGraphs.Any() || !metricScenes.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">Add scene metrics to begin seeing peaks and troughs across the story.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="metric-shape-chart" data-metric-shape-chart>
|
||||||
|
<p class="metric-shape-intro">Compare selected pacing and emotional metrics across scenes in narrative order. Click a point to open that scene on the timeline.</p>
|
||||||
|
<div class="metric-toggle-row" data-metric-toggles></div>
|
||||||
|
<div class="metric-chart-frame">
|
||||||
|
<canvas aria-label="Comparative scene metric line graph"></canvas>
|
||||||
|
<p class="muted d-none" data-chart-fallback>Metric chart could not load. The raw metric rows are still available below.</p>
|
||||||
|
</div>
|
||||||
|
<script type="application/json">@Html.Raw(System.Text.Json.JsonSerializer.Serialize(metricShapePayload))</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="metric-raw-details">
|
||||||
|
<summary>Show raw metric rows</summary>
|
||||||
|
<div class="analytics-metric-stack mt-2">
|
||||||
|
@foreach (var graph in Model.MetricGraphs)
|
||||||
|
{
|
||||||
|
<div class="analytics-metric-row">
|
||||||
|
<div class="analytics-metric-label">@graph.MetricName</div>
|
||||||
|
<div class="analytics-sparkline" style="--point-count:@Math.Max(graph.Points.Count, 1)">
|
||||||
|
@foreach (var point in graph.Points)
|
||||||
|
{
|
||||||
|
<a class="analytics-bar"
|
||||||
|
style="--bar-height:@Math.Max(point.Percent, 4)%"
|
||||||
|
title="@graph.MetricName - Scene @point.SceneNumber: @point.SceneTitle (@point.Value)"
|
||||||
|
asp-controller="Timeline"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-projectId="@Model.Project.ProjectID"
|
||||||
|
asp-route-bookId="@point.BookID"
|
||||||
|
asp-route-selectedSceneId="@point.SceneID">
|
||||||
|
<span>@point.Value</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="analytics-grid-two">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Pacing review</h2>
|
||||||
|
@if (!Model.PacingReviewNotes.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No sustained pacing runs found in this scope.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var note in Model.PacingReviewNotes)
|
||||||
|
{
|
||||||
|
<div class="analytics-list-item">
|
||||||
|
<strong>@note.MetricName</strong>
|
||||||
|
<span>@note.SceneRange</span>
|
||||||
|
<span class="muted">@note.ValuePattern</span>
|
||||||
|
<p>@note.ReviewNote</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Revision summary</h2>
|
||||||
|
@foreach (var status in Model.RevisionSummary.Where(x => x.SceneCount > 0 || x.ChapterCount > 0))
|
||||||
|
{
|
||||||
|
<div class="analytics-count-row">
|
||||||
|
<span>@status.StatusName</span>
|
||||||
|
<strong>@status.SceneCount scenes</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<h3>Needs attention</h3>
|
||||||
|
@foreach (var scene in Model.RevisionAttentionScenes.Take(8))
|
||||||
|
{
|
||||||
|
<a class="analytics-link-row" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@scene.BookID" asp-route-selectedSceneId="@scene.SceneID">
|
||||||
|
<span>@scene.RevisionStatusName</span>
|
||||||
|
<strong>Ch @scene.ChapterNumber / Scene @scene.SceneNumber: @scene.SceneTitle</strong>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="analytics-grid-two">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Scene purpose balance</h2>
|
||||||
|
@foreach (var purpose in Model.PurposeSummary)
|
||||||
|
{
|
||||||
|
<div class="purpose-stat-row">
|
||||||
|
<span>@purpose.PurposeName</span>
|
||||||
|
<div class="purpose-stat-bar"><span style="width:@Math.Min(100, purpose.PercentOfScenes)%"></span></div>
|
||||||
|
<strong>@purpose.SceneCount</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<h3>Missing purpose/outcome</h3>
|
||||||
|
@foreach (var gap in Model.SceneGaps.Take(8))
|
||||||
|
{
|
||||||
|
<a class="analytics-link-row" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@gap.BookID" asp-route-selectedSceneId="@gap.SceneID">
|
||||||
|
<span>@(gap.MissingPurpose ? "No purpose" : "") @(gap.MissingOutcome ? "No outcome" : "")</span>
|
||||||
|
<strong>Ch @gap.ChapterNumber / Scene @gap.SceneNumber: @gap.SceneTitle</strong>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Warning hotspots</h2>
|
||||||
|
@foreach (var count in Model.WarningSeverityCounts)
|
||||||
|
{
|
||||||
|
<div class="warning-count-row"><span class="severity-pill @count.SeverityName.ToLowerInvariant()">@count.SeverityName</span><strong>@count.WarningCount</strong></div>
|
||||||
|
}
|
||||||
|
@foreach (var hotspot in Model.WarningHotspots.Take(8))
|
||||||
|
{
|
||||||
|
<a class="analytics-link-row" asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID" asp-route-sceneId="@hotspot.SceneID">
|
||||||
|
<span>@hotspot.WarningCount warning@(hotspot.WarningCount == 1 ? "" : "s")</span>
|
||||||
|
<strong>Ch @hotspot.ChapterNumber / Scene @hotspot.SceneNumber: @hotspot.SceneTitle</strong>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="analytics-grid-two">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Plot thread health</h2>
|
||||||
|
@foreach (var thread in Model.ThreadHealth.Where(x => x.IsHighImportanceOpen || x.IsDormant || x.HasClueWithoutPayoff || x.HasQuestionWithoutAnswer).Take(12))
|
||||||
|
{
|
||||||
|
<div class="analytics-list-item">
|
||||||
|
<strong>@thread.ThreadTitle</strong>
|
||||||
|
<span>@thread.PlotLineName / @thread.ThreadStatusName / importance @thread.Importance</span>
|
||||||
|
<span class="muted">@thread.LastTouchedSceneLabel</span>
|
||||||
|
<div class="purpose-chip-row">
|
||||||
|
@if (thread.IsDormant) { <span class="purpose-chip">Dormant</span> }
|
||||||
|
@if (thread.HasClueWithoutPayoff) { <span class="purpose-chip">Clue without payoff</span> }
|
||||||
|
@if (thread.HasQuestionWithoutAnswer) { <span class="purpose-chip">Question open</span> }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Story asset health</h2>
|
||||||
|
@foreach (var asset in Model.AssetHealth.Where(x => !x.IsResolved || x.HasDependencies || x.Importance >= 8).Take(12))
|
||||||
|
{
|
||||||
|
<a class="analytics-link-row" asp-controller="StoryAssets" asp-action="Details" asp-route-id="@asset.StoryAssetID">
|
||||||
|
<span>@asset.KindName / importance @asset.Importance</span>
|
||||||
|
<strong>@asset.AssetName</strong>
|
||||||
|
<span>@(asset.CurrentStateName ?? "No state") / @(asset.CurrentLocationPath ?? "No location")</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="analytics-grid-three">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>POV distribution</h2>
|
||||||
|
@foreach (var pov in Model.PovSummary)
|
||||||
|
{
|
||||||
|
<div class="analytics-count-row"><span>@pov.POVName</span><strong>@pov.SceneCount</strong></div>
|
||||||
|
}
|
||||||
|
@foreach (var chapter in Model.MultiPovChapters.Take(6))
|
||||||
|
{
|
||||||
|
<p class="muted">Ch @chapter.ChapterNumber: @chapter.ChapterTitle has @chapter.POVCount POVs.</p>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Character appearances</h2>
|
||||||
|
@foreach (var character in Model.CharacterAppearances.Take(10))
|
||||||
|
{
|
||||||
|
<a class="analytics-link-row" asp-controller="Characters" asp-action="Details" asp-route-id="@character.CharacterID">
|
||||||
|
<span>@character.SceneCount scenes</span>
|
||||||
|
<strong>@character.CharacterName</strong>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@foreach (var busy in Model.BusyScenes.Take(4))
|
||||||
|
{
|
||||||
|
<p class="muted">Busy scene: Ch @busy.ChapterNumber / Scene @busy.SceneNumber has @busy.CharacterCount characters.</p>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Location usage</h2>
|
||||||
|
@foreach (var location in Model.LocationUsage.Take(10))
|
||||||
|
{
|
||||||
|
@if (location.LocationID.HasValue)
|
||||||
|
{
|
||||||
|
<a class="analytics-link-row" asp-controller="Locations" asp-action="Details" asp-route-id="@location.LocationID">
|
||||||
|
<span>@location.SceneCount scenes</span>
|
||||||
|
<strong>@location.LocationName</strong>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="analytics-count-row"><span>@location.LocationName</span><strong>@location.SceneCount</strong></div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
81
PlotLine/Views/Books/Details.cshtml
Normal file
81
PlotLine/Views/Books/Details.cshtml
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
@model BookDetailViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.Book.BookTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||||
|
<span>@Model.Book.BookTitle</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Current book</span>
|
||||||
|
<strong>@Model.Project.ProjectName / @Model.Book.BookTitle</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Book navigation">
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow"><a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a></p>
|
||||||
|
<h1>Book @Model.Book.BookNumber: @Model.Book.BookTitle</h1>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.Book.Description))
|
||||||
|
{
|
||||||
|
<p class="lead-text">@Model.Book.Description</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Edit" asp-route-id="@Model.Book.BookID">Edit book</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Timeline</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Story health</a>
|
||||||
|
<a class="btn btn-primary" asp-controller="Chapters" asp-action="Create" asp-route-bookId="@Model.Book.BookID">Add chapter</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
<h2>Chapters</h2>
|
||||||
|
@if (!Model.Chapters.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No chapters yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Chapter</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var chapter in Model.Chapters)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="narrow">@chapter.ChapterNumber</td>
|
||||||
|
<td><a asp-controller="Chapters" asp-action="Details" asp-route-id="@chapter.ChapterID">@chapter.ChapterTitle</a></td>
|
||||||
|
<td><span class="status-pill">@chapter.RevisionStatusName</span></td>
|
||||||
|
<td>@chapter.Summary</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-controller="Chapters" asp-action="Edit" asp-route-id="@chapter.ChapterID">Edit</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
38
PlotLine/Views/Books/Edit.cshtml
Normal file
38
PlotLine/Views/Books/Edit.cshtml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@model BookEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.BookID == 0 ? "New Book" : "Edit Book";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">@Model.Project?.ProjectName</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Projects" asp-action="Details" asp-route-id="@Model.ProjectID">Back to project</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="BookID" type="hidden" />
|
||||||
|
<input asp-for="ProjectID" type="hidden" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label asp-for="BookNumber" class="form-label"></label>
|
||||||
|
<input asp-for="BookNumber" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<label asp-for="BookTitle" class="form-label"></label>
|
||||||
|
<input asp-for="BookTitle" class="form-control" />
|
||||||
|
<span asp-validation-for="BookTitle" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Description" class="form-label"></label>
|
||||||
|
<textarea asp-for="Description" class="form-control" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary mt-3" type="submit">Save book</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
101
PlotLine/Views/Chapters/Details.cshtml
Normal file
101
PlotLine/Views/Chapters/Details.cshtml
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
@model ChapterDetailViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.Chapter.ChapterTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||||
|
<a asp-controller="Books" asp-action="Details" asp-route-id="@Model.Book.BookID">@Model.Book.BookTitle</a>
|
||||||
|
<span>Chapter @Model.Chapter.ChapterNumber</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Current chapter</span>
|
||||||
|
<strong>@Model.Book.BookTitle / Chapter @Model.Chapter.ChapterNumber</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Chapter navigation">
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID" asp-route-startChapterNumber="@Model.Chapter.ChapterNumber" asp-route-endChapterNumber="@Model.Chapter.ChapterNumber">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||||
|
/ <a asp-controller="Books" asp-action="Details" asp-route-id="@Model.Book.BookID">@Model.Book.BookTitle</a>
|
||||||
|
</p>
|
||||||
|
<h1>Chapter @Model.Chapter.ChapterNumber: @Model.Chapter.ChapterTitle</h1>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.Chapter.Summary))
|
||||||
|
{
|
||||||
|
<p class="lead-text">@Model.Chapter.Summary</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Edit" asp-route-id="@Model.Chapter.ChapterID">Edit chapter</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Timeline</a>
|
||||||
|
<a class="btn btn-primary" asp-controller="Scenes" asp-action="Create" asp-route-chapterId="@Model.Chapter.ChapterID">Add scene</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
<h2>Scenes</h2>
|
||||||
|
@if (!Model.Scenes.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No scenes yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Scene</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
<th>Order</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var scene in Model.Scenes)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="narrow">@scene.SceneNumber</td>
|
||||||
|
<td><a asp-controller="Scenes" asp-action="Edit" asp-route-id="@scene.SceneID">@scene.SceneTitle</a></td>
|
||||||
|
<td><span class="status-pill">@scene.RevisionStatusName</span></td>
|
||||||
|
<td>@scene.TimeLabel</td>
|
||||||
|
<td>@scene.Summary</td>
|
||||||
|
<td>
|
||||||
|
<div class="order-buttons">
|
||||||
|
<form asp-controller="Scenes" asp-action="MoveUp" method="post">
|
||||||
|
<input type="hidden" name="id" value="@scene.SceneID" />
|
||||||
|
<input type="hidden" name="chapterId" value="@Model.Chapter.ChapterID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Up</button>
|
||||||
|
</form>
|
||||||
|
<form asp-controller="Scenes" asp-action="MoveDown" method="post">
|
||||||
|
<input type="hidden" name="id" value="@scene.SceneID" />
|
||||||
|
<input type="hidden" name="chapterId" value="@Model.Chapter.ChapterID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Down</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID" asp-route-selectedSceneId="@scene.SceneID">Open in timeline</a>
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-controller="Scenes" asp-action="Edit" asp-route-id="@scene.SceneID">Edit</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
42
PlotLine/Views/Chapters/Edit.cshtml
Normal file
42
PlotLine/Views/Chapters/Edit.cshtml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
@model ChapterEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.ChapterID == 0 ? "New Chapter" : "Edit Chapter";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">@Model.Book?.BookTitle</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Books" asp-action="Details" asp-route-id="@Model.BookID">Back to book</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="ChapterID" type="hidden" />
|
||||||
|
<input asp-for="BookID" type="hidden" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label asp-for="ChapterNumber" class="form-label"></label>
|
||||||
|
<input asp-for="ChapterNumber" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="ChapterTitle" class="form-label"></label>
|
||||||
|
<input asp-for="ChapterTitle" class="form-control" />
|
||||||
|
<span asp-validation-for="ChapterTitle" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label asp-for="RevisionStatusID" class="form-label"></label>
|
||||||
|
<select asp-for="RevisionStatusID" asp-items="Model.RevisionStatuses" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Summary" class="form-label"></label>
|
||||||
|
<textarea asp-for="Summary" class="form-control" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary mt-3" type="submit">Save chapter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
156
PlotLine/Views/Characters/Details.cshtml
Normal file
156
PlotLine/Views/Characters/Details.cshtml
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
@model CharacterDetailViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.Character.CharacterName;
|
||||||
|
var firstAppearance = Model.Appearances.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||||
|
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<span>@Model.Character.CharacterName</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Project context</span>
|
||||||
|
<strong>@Model.Project.ProjectName</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@firstAppearance?.SceneID">Show on timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Character</p>
|
||||||
|
<h1>@Model.Character.CharacterName</h1>
|
||||||
|
<p class="lead-text">@Model.Character.DefaultDescription</p>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@firstAppearance?.SceneID">Back to timeline</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-action="Edit" asp-route-id="@Model.Character.CharacterID">Edit character</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="asset-detail-grid">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Profile</h2>
|
||||||
|
<p><strong>Short name:</strong> @Model.Character.ShortName</p>
|
||||||
|
<p><strong>Sex:</strong> @Model.Character.Sex</p>
|
||||||
|
<p><strong>Age:</strong> @Model.DisplayAge</p>
|
||||||
|
<p><strong>Height:</strong> @Model.Character.Height</p>
|
||||||
|
<p><strong>Eye colour:</strong> @Model.Character.EyeColour</p>
|
||||||
|
</article>
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Relationships</h2>
|
||||||
|
@if (!Model.Relationships.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No relationships yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var relationship in Model.Relationships)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<strong>@relationship.CharacterAName -> @relationship.CharacterBName</strong>
|
||||||
|
<span>@relationship.RelationshipTypeName</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section mt-3">
|
||||||
|
<h2>Scene appearances</h2>
|
||||||
|
@if (!Model.Appearances.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No scene appearances yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var appearance in Model.Appearances)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<strong>@appearance.BookTitle / Ch @appearance.ChapterNumber / Scene @appearance.SceneNumber: @appearance.SceneTitle</strong>
|
||||||
|
<span>@appearance.RoleInSceneTypeName / @appearance.PresenceTypeName</span>
|
||||||
|
<span>@appearance.OutfitDescription @appearance.PhysicalCondition @appearance.EmotionalState</span>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@appearance.SceneID">Open in timeline</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section mt-3">
|
||||||
|
<h2>Attribute history</h2>
|
||||||
|
@if (!Model.AttributeEvents.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No attribute events yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var item in Model.AttributeEvents)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<strong>@item.AttributeName: @item.AttributeValue</strong>
|
||||||
|
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
||||||
|
<span>@item.Description</span>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@item.SceneID">Open in timeline</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section mt-3">
|
||||||
|
<h2>Knowledge</h2>
|
||||||
|
@if (!Model.KnowledgeItems.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No knowledge entries yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var item in Model.KnowledgeItems)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<strong>@item.KnowledgeStateName: @(item.AssetName ?? item.ThreadTitle)</strong>
|
||||||
|
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
||||||
|
<span>@item.Description</span>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@item.SceneID">Open in timeline</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section mt-3">
|
||||||
|
<h2>Relationship events</h2>
|
||||||
|
@if (!Model.RelationshipEvents.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No relationship events yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var item in Model.RelationshipEvents)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<strong>@item.CharacterAName -> @item.CharacterBName: @item.RelationshipStateName</strong>
|
||||||
|
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
||||||
|
<span>Intensity @item.Intensity. @item.Description</span>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@item.SceneID">Open in timeline</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
61
PlotLine/Views/Characters/Edit.cshtml
Normal file
61
PlotLine/Views/Characters/Edit.cshtml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
@model CharacterEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.CharacterID == 0 ? "New Character" : "Edit Character";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Character</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="CharacterID" type="hidden" />
|
||||||
|
<input asp-for="ProjectID" type="hidden" />
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="CharacterName" class="form-label"></label>
|
||||||
|
<input asp-for="CharacterName" class="form-control" />
|
||||||
|
<span asp-validation-for="CharacterName" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="ShortName" class="form-label"></label>
|
||||||
|
<input asp-for="ShortName" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Sex" class="form-label"></label>
|
||||||
|
<input asp-for="Sex" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="BirthDate" class="form-label"></label>
|
||||||
|
<input asp-for="BirthDate" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="AgeAtSeriesStart" class="form-label"></label>
|
||||||
|
<input asp-for="AgeAtSeriesStart" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Height" class="form-label"></label>
|
||||||
|
<input asp-for="Height" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="EyeColour" class="form-label"></label>
|
||||||
|
<input asp-for="EyeColour" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="DefaultDescription" class="form-label"></label>
|
||||||
|
<textarea asp-for="DefaultDescription" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-row mt-3">
|
||||||
|
<button class="btn btn-primary" type="submit">Save character</button>
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
61
PlotLine/Views/Characters/Index.cshtml
Normal file
61
PlotLine/Views/Characters/Index.cshtml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
@model CharacterListViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Characters";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title"><span class="eyebrow">Project context</span><strong>@Model.Project.ProjectName</strong></div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Characters</p>
|
||||||
|
<h1>@Model.Project.ProjectName</h1>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a class="btn btn-primary" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">Add character</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
@if (!Model.Characters.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No characters yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-card-grid">
|
||||||
|
@foreach (var character in Model.Characters)
|
||||||
|
{
|
||||||
|
<article class="asset-card character-card">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Character</p>
|
||||||
|
<h2><a asp-action="Details" asp-route-id="@character.CharacterID">@character.CharacterName</a></h2>
|
||||||
|
<p class="muted">@character.DefaultDescription</p>
|
||||||
|
</div>
|
||||||
|
<div class="asset-card-footer">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(character.ShortName))
|
||||||
|
{
|
||||||
|
<span class="purpose-chip">@character.ShortName</span>
|
||||||
|
}
|
||||||
|
@if (character.AgeAtSeriesStart.HasValue)
|
||||||
|
{
|
||||||
|
<span class="status-pill">Age @character.AgeAtSeriesStart</span>
|
||||||
|
}
|
||||||
|
<a class="btn btn-outline-secondary btn-sm ms-auto" asp-action="Edit" asp-route-id="@character.CharacterID">Edit</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
8
PlotLine/Views/Home/Index.cshtml
Normal file
8
PlotLine/Views/Home/Index.cshtml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Home Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="display-4">Welcome</h1>
|
||||||
|
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
|
||||||
|
</div>
|
||||||
6
PlotLine/Views/Home/Privacy.cshtml
Normal file
6
PlotLine/Views/Home/Privacy.cshtml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Privacy Policy";
|
||||||
|
}
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
|
||||||
|
<p>Use this page to detail your site's privacy policy.</p>
|
||||||
166
PlotLine/Views/Locations/Details.cshtml
Normal file
166
PlotLine/Views/Locations/Details.cshtml
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
@model LocationDetailViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.Location.LocationName;
|
||||||
|
var firstScene = Model.Scenes.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||||
|
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<span>@Model.Location.LocationName</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Project context</span>
|
||||||
|
<strong>@Model.Project.ProjectName</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@firstScene?.SceneID">Show scene usage</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Location detail</p>
|
||||||
|
<h1>@Model.Location.LocationName</h1>
|
||||||
|
<p class="muted">@Model.Location.LocationPath</p>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Edit" asp-route-id="@Model.Location.LocationID">Edit</a>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@firstScene?.SceneID">Back to timeline</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">All locations</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="edit-panel">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<p class="eyebrow">Type</p>
|
||||||
|
<p>@(Model.Location.LocationTypeName ?? "Not set")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<p class="eyebrow">Parent</p>
|
||||||
|
<p>@(Model.Location.ParentLocationName ?? "Top level")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="eyebrow">Description</p>
|
||||||
|
<p>@(Model.Location.Description ?? "No description yet.")</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="edit-panel">
|
||||||
|
<h2>Child locations</h2>
|
||||||
|
@if (!Model.ChildLocations.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No child locations yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="location-chip-list">
|
||||||
|
@foreach (var child in Model.ChildLocations)
|
||||||
|
{
|
||||||
|
<a class="location-chip" asp-action="Details" asp-route-id="@child.LocationID">@child.LocationName</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="edit-panel">
|
||||||
|
<h2>Relationships</h2>
|
||||||
|
@if (!Model.Relationships.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No relationships defined yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="location-relationship-list">
|
||||||
|
@foreach (var relationship in Model.Relationships)
|
||||||
|
{
|
||||||
|
<div class="location-relationship-card">
|
||||||
|
<div>
|
||||||
|
<strong>@relationship.FromLocationName</strong>
|
||||||
|
<span>@relationship.RelationshipTypeName.ToLowerInvariant()</span>
|
||||||
|
<strong>@relationship.ToLocationName</strong>
|
||||||
|
@if (relationship.IsBidirectional)
|
||||||
|
{
|
||||||
|
<span class="location-badge">both ways</span>
|
||||||
|
}
|
||||||
|
<p class="muted">@relationship.Notes</p>
|
||||||
|
</div>
|
||||||
|
<form asp-action="ArchiveRelationship" method="post">
|
||||||
|
<input type="hidden" name="id" value="@relationship.LocationRelationshipID" />
|
||||||
|
<input type="hidden" name="returnLocationId" value="@Model.Location.LocationID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Archive</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-action="AddRelationship" method="post" class="asset-create-form mt-3">
|
||||||
|
<input asp-for="NewRelationship.ProjectID" type="hidden" name="ProjectID" />
|
||||||
|
<input asp-for="NewRelationship.FromLocationID" type="hidden" name="FromLocationID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label" for="ToLocationID">To location</label>
|
||||||
|
<select class="form-select form-select-sm" id="ToLocationID" name="ToLocationID" asp-items="Model.LocationOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" for="LocationRelationshipTypeID">Relationship</label>
|
||||||
|
<select class="form-select form-select-sm" id="LocationRelationshipTypeID" name="LocationRelationshipTypeID" asp-items="Model.RelationshipTypeOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label" for="Strength">Strength</label>
|
||||||
|
<input class="form-control form-control-sm" id="Strength" name="Strength" type="number" min="1" max="10" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12 location-flags">
|
||||||
|
<label><input type="checkbox" name="IsBidirectional" value="true" /> Bidirectional</label>
|
||||||
|
<label><input type="checkbox" name="CanHearNormalSpeech" value="true" /> Can hear normal speech</label>
|
||||||
|
<label><input type="checkbox" name="CanHearLoudNoise" value="true" /> Can hear loud noise</label>
|
||||||
|
<label><input type="checkbox" name="CanSeeMovement" value="true" /> Can see movement</label>
|
||||||
|
<label><input type="checkbox" name="CanSeeClearly" value="true" /> Can see clearly</label>
|
||||||
|
<label><input type="checkbox" name="CanTravelDirectly" value="true" /> Can travel directly</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<textarea class="form-control form-control-sm" name="Notes" rows="2" placeholder="Relationship notes"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add relationship</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="edit-panel">
|
||||||
|
<h2>Used here</h2>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h3>Scenes</h3>
|
||||||
|
@foreach (var scene in Model.Scenes)
|
||||||
|
{
|
||||||
|
<p><strong>Scene @scene.SceneNumber</strong> <a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@scene.SceneID">@scene.SceneTitle</a></p>
|
||||||
|
}
|
||||||
|
@if (!Model.Scenes.Any()) { <p class="muted">No scenes set here.</p> }
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h3>Characters</h3>
|
||||||
|
@foreach (var character in Model.Characters)
|
||||||
|
{
|
||||||
|
<p><strong>@character.CharacterName</strong> <span class="muted">Scene @character.SceneNumber</span></p>
|
||||||
|
}
|
||||||
|
@if (!Model.Characters.Any()) { <p class="muted">No character placements yet.</p> }
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h3>Assets</h3>
|
||||||
|
@foreach (var asset in Model.Assets)
|
||||||
|
{
|
||||||
|
<p><strong>@asset.AssetName</strong> <span class="muted">@asset.Description</span></p>
|
||||||
|
}
|
||||||
|
@if (!Model.Assets.Any()) { <p class="muted">No asset placements yet.</p> }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
49
PlotLine/Views/Locations/Edit.cshtml
Normal file
49
PlotLine/Views/Locations/Edit.cshtml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
@model LocationEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.LocationID == 0 ? "New Location" : "Edit Location";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Location</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="LocationID" type="hidden" />
|
||||||
|
<input asp-for="ProjectID" type="hidden" />
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="LocationName" class="form-label"></label>
|
||||||
|
<input asp-for="LocationName" class="form-control" />
|
||||||
|
<span asp-validation-for="LocationName" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="LocationTypeID" class="form-label"></label>
|
||||||
|
<select asp-for="LocationTypeID" asp-items="Model.LocationTypeOptions" class="form-select">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="ParentLocationID" class="form-label"></label>
|
||||||
|
<select asp-for="ParentLocationID" asp-items="Model.ParentLocationOptions" class="form-select">
|
||||||
|
<option value="">Top level</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Description" class="form-label"></label>
|
||||||
|
<textarea asp-for="Description" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-row mt-3">
|
||||||
|
<button class="btn btn-primary" type="submit">Save location</button>
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
59
PlotLine/Views/Locations/Index.cshtml
Normal file
59
PlotLine/Views/Locations/Index.cshtml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
@model LocationListViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Locations";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title"><span class="eyebrow">Project context</span><strong>@Model.Project.ProjectName</strong></div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Locations</p>
|
||||||
|
<h1>@Model.Project.ProjectName</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-primary" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">New location</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!Model.Locations.Any())
|
||||||
|
{
|
||||||
|
<section class="empty-panel">
|
||||||
|
<h2>No locations yet</h2>
|
||||||
|
<p>Add buildings, floors, rooms, streets, and other places that matter to the story.</p>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<section class="edit-panel location-tree">
|
||||||
|
@foreach (var location in Model.Locations)
|
||||||
|
{
|
||||||
|
<div class="location-tree-row" style="--location-depth:@location.Depth">
|
||||||
|
<div>
|
||||||
|
<a asp-action="Details" asp-route-id="@location.LocationID"><strong>@location.LocationName</strong></a>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(location.LocationTypeName))
|
||||||
|
{
|
||||||
|
<span class="location-badge">@location.LocationTypeName</span>
|
||||||
|
}
|
||||||
|
<span class="muted">@location.LocationPath</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-row compact-buttons">
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@location.LocationID">Edit</a>
|
||||||
|
<form asp-action="Archive" method="post">
|
||||||
|
<input type="hidden" name="id" value="@location.LocationID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
}
|
||||||
70
PlotLine/Views/PlotLines/Edit.cshtml
Normal file
70
PlotLine/Views/PlotLines/Edit.cshtml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
@model PlotLineEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.PlotLineID == 0 ? "New Plot Line" : "Edit Plot Line";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">@Model.Project?.ProjectName</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Back to plot lines</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="PlotLineID" type="hidden" />
|
||||||
|
<input asp-for="ProjectID" type="hidden" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="PlotLineName" class="form-label"></label>
|
||||||
|
<input asp-for="PlotLineName" class="form-control" />
|
||||||
|
<span asp-validation-for="PlotLineName" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="PlotLineTypeID" class="form-label"></label>
|
||||||
|
<select asp-for="PlotLineTypeID" asp-items="Model.PlotLineTypes" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="BookID" class="form-label"></label>
|
||||||
|
<select asp-for="BookID" asp-items="Model.BookOptions" class="form-select">
|
||||||
|
<option value="">Project-wide</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="ParentPlotLineID" class="form-label"></label>
|
||||||
|
<select asp-for="ParentPlotLineID" asp-items="Model.ParentPlotLineOptions" class="form-select">
|
||||||
|
<option value="">None</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label asp-for="SortOrder" class="form-label"></label>
|
||||||
|
<input asp-for="SortOrder" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label asp-for="Colour" class="form-label"></label>
|
||||||
|
<input asp-for="Colour" type="color" class="form-control form-control-color" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Description" class="form-label"></label>
|
||||||
|
<textarea asp-for="Description" class="form-control" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="purpose-check">
|
||||||
|
<input asp-for="IsMainPlot" />
|
||||||
|
<span>Main plot</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="purpose-check">
|
||||||
|
<input asp-for="IsVisibleOnTimeline" />
|
||||||
|
<span>Visible on timeline</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary mt-3" type="submit">Save plot line</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
66
PlotLine/Views/PlotLines/Index.cshtml
Normal file
66
PlotLine/Views/PlotLines/Index.cshtml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
@model PlotLineListViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Plot Lines";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title"><span class="eyebrow">Project context</span><strong>@Model.Project.ProjectName</strong></div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow"><a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a></p>
|
||||||
|
<h1>Plot lines</h1>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a class="btn btn-primary" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">Add plot line</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
@if (!Model.PlotLines.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No plot lines yet. Add one to create the first timeline lane.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Colour</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Book</th>
|
||||||
|
<th>Timeline</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var plotLine in Model.PlotLines)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><span class="colour-swatch" style="background:@plotLine.Colour"></span></td>
|
||||||
|
<td><a asp-action="Edit" asp-route-id="@plotLine.PlotLineID">@plotLine.PlotLineName</a></td>
|
||||||
|
<td>@plotLine.PlotLineTypeName</td>
|
||||||
|
<td>@(plotLine.BookTitle ?? "Project-wide")</td>
|
||||||
|
<td>@(plotLine.IsVisibleOnTimeline ? "Visible" : "Hidden")</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-controller="PlotThreads" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID" asp-route-plotLineId="@plotLine.PlotLineID">Add thread</a>
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@plotLine.PlotLineID">Edit</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
68
PlotLine/Views/PlotThreads/Edit.cshtml
Normal file
68
PlotLine/Views/PlotThreads/Edit.cshtml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
@model PlotThreadEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.PlotThreadID == 0 ? "New Plot Thread" : "Edit Plot Thread";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">@Model.Project?.ProjectName</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Back to plot threads</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="PlotThreadID" type="hidden" />
|
||||||
|
<input asp-for="ProjectID" type="hidden" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="PlotLineID" class="form-label"></label>
|
||||||
|
<select asp-for="PlotLineID" asp-items="Model.PlotLineOptions" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="ThreadTitle" class="form-label"></label>
|
||||||
|
<input asp-for="ThreadTitle" class="form-control" />
|
||||||
|
<span asp-validation-for="ThreadTitle" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="ThreadTypeID" class="form-label"></label>
|
||||||
|
<select asp-for="ThreadTypeID" asp-items="Model.ThreadTypeOptions" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="ThreadStatusID" class="form-label"></label>
|
||||||
|
<select asp-for="ThreadStatusID" asp-items="Model.ThreadStatusOptions" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Importance" class="form-label"></label>
|
||||||
|
<input asp-for="Importance" type="number" min="1" max="10" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Summary" class="form-label"></label>
|
||||||
|
<textarea asp-for="Summary" class="form-control" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="IntroducedSceneID" class="form-label"></label>
|
||||||
|
<select asp-for="IntroducedSceneID" asp-items="Model.SceneOptions" class="form-select">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="PlannedResolutionSceneID" class="form-label"></label>
|
||||||
|
<select asp-for="PlannedResolutionSceneID" asp-items="Model.SceneOptions" class="form-select">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="ActualResolutionSceneID" class="form-label"></label>
|
||||||
|
<select asp-for="ActualResolutionSceneID" asp-items="Model.SceneOptions" class="form-select">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary mt-3" type="submit">Save thread</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
66
PlotLine/Views/PlotThreads/Index.cshtml
Normal file
66
PlotLine/Views/PlotThreads/Index.cshtml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
@model PlotThreadListViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Plot Threads";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title"><span class="eyebrow">Project context</span><strong>@Model.Project.ProjectName</strong></div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow"><a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a></p>
|
||||||
|
<h1>Plot threads</h1>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="PlotLines" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Plot lines</a>
|
||||||
|
<a class="btn btn-primary" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">Add thread</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
@if (!Model.PlotThreads.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No plot threads yet. Threads are the unresolved or developing strands that create markers on plot line lanes.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Thread</th>
|
||||||
|
<th>Plot line</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Importance</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var thread in Model.PlotThreads)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><a asp-action="Edit" asp-route-id="@thread.PlotThreadID">@thread.ThreadTitle</a></td>
|
||||||
|
<td>@thread.PlotLineName</td>
|
||||||
|
<td>@thread.ThreadTypeName</td>
|
||||||
|
<td><span class="status-pill">@thread.ThreadStatusName</span></td>
|
||||||
|
<td>@thread.Importance</td>
|
||||||
|
<td class="text-end"><a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@thread.PlotThreadID">Edit</a></td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
84
PlotLine/Views/Projects/Details.cshtml
Normal file
84
PlotLine/Views/Projects/Details.cshtml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
@model ProjectDetailViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.Project.ProjectName;
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
<span>@Model.Project.ProjectName</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Current project</span>
|
||||||
|
<strong>@Model.Project.ProjectName</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Project / Series</p>
|
||||||
|
<h1>@Model.Project.ProjectName</h1>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.Project.Description))
|
||||||
|
{
|
||||||
|
<p class="lead-text">@Model.Project.Description</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Edit" asp-route-id="@Model.Project.ProjectID">Edit project</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="PlotLines" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Plot lines</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Plot threads</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story assets</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a class="btn btn-primary" asp-controller="Books" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">Add book</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
<h2>Books</h2>
|
||||||
|
@if (!Model.Books.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No books yet. Add the first book to begin the hierarchy.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order</th>
|
||||||
|
<th>Book</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var book in Model.Books)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="narrow">@book.BookNumber</td>
|
||||||
|
<td><a asp-controller="Books" asp-action="Details" asp-route-id="@book.BookID">@book.BookTitle</a></td>
|
||||||
|
<td>@book.Description</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-controller="Books" asp-action="Edit" asp-route-id="@book.BookID">Edit</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
31
PlotLine/Views/Projects/Edit.cshtml
Normal file
31
PlotLine/Views/Projects/Edit.cshtml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@model ProjectEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.ProjectID == 0 ? "New Project" : "Edit Project";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Project / Series</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index">Back to projects</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="ProjectID" type="hidden" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="ProjectName" class="form-label"></label>
|
||||||
|
<input asp-for="ProjectName" class="form-control" />
|
||||||
|
<span asp-validation-for="ProjectName" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="Description" class="form-label"></label>
|
||||||
|
<textarea asp-for="Description" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" type="submit">Save project</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
37
PlotLine/Views/Projects/Index.cshtml
Normal file
37
PlotLine/Views/Projects/Index.cshtml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
@model IReadOnlyList<Project>
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Projects";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Series planning</p>
|
||||||
|
<h1>Projects</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-primary" asp-action="Create">New project</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!Model.Any())
|
||||||
|
{
|
||||||
|
<section class="empty-panel">
|
||||||
|
<h2>Start with a series or standalone novel</h2>
|
||||||
|
<p>Create a project, then add books, chapters and scenes in narrative order.</p>
|
||||||
|
<a class="btn btn-primary" asp-action="Create">Create your first project</a>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="content-grid">
|
||||||
|
@foreach (var project in Model)
|
||||||
|
{
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>@project.ProjectName</h2>
|
||||||
|
<p>@(string.IsNullOrWhiteSpace(project.Description) ? "No description yet." : project.Description)</p>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-primary btn-sm" asp-action="Details" asp-route-id="@project.ProjectID">Open</a>
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@project.ProjectID">Edit</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
53
PlotLine/Views/Scenes/Edit.cshtml
Normal file
53
PlotLine/Views/Scenes/Edit.cshtml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
@model SceneEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.SceneID == 0 ? "New Scene" : "Edit Scene";
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
@if (Model.Project is not null)
|
||||||
|
{
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||||
|
}
|
||||||
|
@if (Model.Book is not null)
|
||||||
|
{
|
||||||
|
<a asp-controller="Books" asp-action="Details" asp-route-id="@Model.Book.BookID">@Model.Book.BookTitle</a>
|
||||||
|
}
|
||||||
|
@if (Model.Chapter is not null)
|
||||||
|
{
|
||||||
|
<a asp-controller="Chapters" asp-action="Details" asp-route-id="@Model.ChapterID">Chapter @Model.Chapter.ChapterNumber</a>
|
||||||
|
}
|
||||||
|
<span>@(Model.SceneID == 0 ? "New scene" : $"Scene {Model.SceneNumber}")</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
@if (Model.Project is not null)
|
||||||
|
{
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Working context</span>
|
||||||
|
<strong>@Model.Project.ProjectName@if (Model.Book is not null) { <span> / @Model.Book.BookTitle</span> }</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Scene navigation">
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book?.BookID" asp-route-selectedSceneId="@Model.SceneID">Open scene in timeline</a>
|
||||||
|
<a asp-controller="Chapters" asp-action="Details" asp-route-id="@Model.ChapterID">Back to chapter</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book?.BookID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book?.BookID" asp-route-sceneId="@Model.SceneID">Warnings</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">@Model.Book?.BookTitle / Chapter @Model.Chapter?.ChapterNumber</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Chapters" asp-action="Details" asp-route-id="@Model.ChapterID">Back to chapter</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="edit-panel scene-editor">
|
||||||
|
<partial name="_SceneInspector" model="Model" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
67
PlotLine/Views/Scenes/MovePreview.cshtml
Normal file
67
PlotLine/Views/Scenes/MovePreview.cshtml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
@model SceneMovePreviewViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Move Scene Preview";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Move scene</p>
|
||||||
|
<h1>@Model.Preview.SceneTitle</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="edit-panel move-preview-panel">
|
||||||
|
<h2>Preview</h2>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p class="eyebrow">Current position</p>
|
||||||
|
<p><strong>@Model.Preview.SourceBookTitle</strong></p>
|
||||||
|
<p>Chapter @Model.Preview.SourceChapterNumber: @Model.Preview.SourceChapterTitle</p>
|
||||||
|
<p>Scene @Model.Preview.SceneNumber: @Model.Preview.SceneTitle</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p class="eyebrow">Proposed position</p>
|
||||||
|
<p><strong>@Model.Preview.TargetBookTitle</strong></p>
|
||||||
|
<p>Chapter @Model.Preview.TargetChapterNumber: @Model.Preview.TargetChapterTitle</p>
|
||||||
|
<p>Position: @Model.Preview.Position.ToLowerInvariant()</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="focused-validation-summary">
|
||||||
|
<span>@Model.Preview.DependencyCount dependenc@(Model.Preview.DependencyCount == 1 ? "y" : "ies") attached</span>
|
||||||
|
<span>@Model.Preview.ActiveWarningCount active warning@(Model.Preview.ActiveWarningCount == 1 ? "" : "s") on this scene</span>
|
||||||
|
<span>After confirming, PlotLine will run validation for the target book.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.CurrentWarnings.Any())
|
||||||
|
{
|
||||||
|
<div class="warning-mini-list mt-3">
|
||||||
|
@foreach (var warning in Model.CurrentWarnings.Take(5))
|
||||||
|
{
|
||||||
|
<div class="warning-mini-card">
|
||||||
|
<span class="severity-pill @warning.SeverityName.ToLowerInvariant()">@warning.SeverityName</span>
|
||||||
|
<strong>@warning.WarningTypeName</strong>
|
||||||
|
<p>@warning.Message</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="button-row mt-3">
|
||||||
|
<form asp-action="ConfirmMove" method="post">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.Request.SceneID" />
|
||||||
|
<input type="hidden" name="TargetChapterID" value="@Model.Request.TargetChapterID" />
|
||||||
|
<input type="hidden" name="Position" value="@Model.Request.Position" />
|
||||||
|
<input type="hidden" name="RenumberScenes" value="@Model.Request.RenumberScenes.ToString().ToLowerInvariant()" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.Request.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.Request.ReturnBookID" />
|
||||||
|
<button class="btn btn-primary" type="submit">Confirm move</button>
|
||||||
|
</form>
|
||||||
|
<a class="btn btn-outline-secondary"
|
||||||
|
asp-controller="Timeline"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-projectId="@Model.Request.ReturnProjectID"
|
||||||
|
asp-route-bookId="@Model.Request.ReturnBookID"
|
||||||
|
asp-route-selectedSceneId="@Model.Request.SceneID">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
900
PlotLine/Views/Scenes/_SceneInspector.cshtml
Normal file
900
PlotLine/Views/Scenes/_SceneInspector.cshtml
Normal file
@ -0,0 +1,900 @@
|
|||||||
|
@model SceneEditViewModel
|
||||||
|
@{
|
||||||
|
var revisionStatus = Model.RevisionStatuses.FirstOrDefault(x => x.Value == Model.RevisionStatusID.ToString())?.Text ?? "Not set";
|
||||||
|
var primaryLocation = Model.LocationOptions.FirstOrDefault(x => x.Value == Model.PrimaryLocationID?.ToString())?.Text ?? "No primary location";
|
||||||
|
var povCharacter = Model.SceneCharacters.FirstOrDefault(x => x.RoleInSceneTypeName?.Contains("POV", StringComparison.OrdinalIgnoreCase) == true)?.CharacterName
|
||||||
|
?? Model.SceneCharacters.FirstOrDefault()?.CharacterName
|
||||||
|
?? "No POV";
|
||||||
|
var timeLabel = !string.IsNullOrWhiteSpace(Model.RelativeTimeText)
|
||||||
|
? Model.RelativeTimeText
|
||||||
|
: Model.StartDateTime?.ToString("dd MMM yyyy HH:mm")
|
||||||
|
?? Model.StartDateTime?.ToString("dd MMM yyyy")
|
||||||
|
?? "Time not set";
|
||||||
|
var selectedPurposeLabels = Model.ScenePurposeTypes
|
||||||
|
.Where(x => Model.SelectedPurposeTypeIds.Contains(x.ScenePurposeTypeID))
|
||||||
|
.Select(x => x.PurposeName)
|
||||||
|
.ToList();
|
||||||
|
var dependencyCount = Model.DependenciesThisSceneNeeds.Count + Model.DependenciesNeedingThisScene.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="scene-inspector-root">
|
||||||
|
<section class="inspector-section scene-overview-card" data-section-title="Overview" data-section-id="inspector-overview" data-inspector-search-text="@($"{Model.SceneTitle} {povCharacter} {primaryLocation} {timeLabel} {revisionStatus} {string.Join(' ', selectedPurposeLabels)}")">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Working context</p>
|
||||||
|
<h3>@(string.IsNullOrWhiteSpace(Model.SceneTitle) ? "Untitled scene" : Model.SceneTitle)</h3>
|
||||||
|
<p class="muted">
|
||||||
|
@if (Model.Chapter is not null)
|
||||||
|
{
|
||||||
|
<span>Chapter @Model.Chapter.ChapterNumber</span>
|
||||||
|
}
|
||||||
|
<span>Scene @Model.SceneNumber</span>
|
||||||
|
<span>@povCharacter</span>
|
||||||
|
<span>@primaryLocation</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="overview-chip-grid">
|
||||||
|
<span class="overview-chip">@revisionStatus</span>
|
||||||
|
<span class="overview-chip">@timeLabel</span>
|
||||||
|
<span class="overview-chip @(Model.Warnings.Any() ? "attention" : "")">@Model.Warnings.Count warning@(Model.Warnings.Count == 1 ? "" : "s")</span>
|
||||||
|
<span class="overview-chip">@Model.SceneCharacters.Count character@(Model.SceneCharacters.Count == 1 ? "" : "s")</span>
|
||||||
|
<span class="overview-chip">@Model.AssetEvents.Count asset event@(Model.AssetEvents.Count == 1 ? "" : "s")</span>
|
||||||
|
<span class="overview-chip">@Model.ThreadEvents.Count thread event@(Model.ThreadEvents.Count == 1 ? "" : "s")</span>
|
||||||
|
<span class="overview-chip">@dependencyCount dependenc@(dependencyCount == 1 ? "y" : "ies")</span>
|
||||||
|
</div>
|
||||||
|
@if (selectedPurposeLabels.Any())
|
||||||
|
{
|
||||||
|
<div class="purpose-chip-row">
|
||||||
|
@foreach (var purpose in selectedPurposeLabels)
|
||||||
|
{
|
||||||
|
<span class="purpose-chip">@purpose</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.SceneOutcomeNotes))
|
||||||
|
{
|
||||||
|
<p class="overview-outcome">@Model.SceneOutcomeNotes</p>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="inspector-quick-nav" data-inspector-nav>
|
||||||
|
<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>
|
||||||
|
<a href="#inspector-timing">Time</a>
|
||||||
|
<a href="#inspector-purpose">Purpose</a>
|
||||||
|
<a href="#inspector-metrics">Metrics</a>
|
||||||
|
<a href="#inspector-warnings">Warnings</a>
|
||||||
|
<a href="#inspector-dependencies">Dependencies</a>
|
||||||
|
<a href="#inspector-move">Move</a>
|
||||||
|
<a href="#inspector-thread-events">Threads</a>
|
||||||
|
<a href="#inspector-asset-events">Assets</a>
|
||||||
|
<a href="#inspector-asset-locations">Locations</a>
|
||||||
|
<a href="#inspector-asset-custody">Custody</a>
|
||||||
|
<a href="#inspector-characters">Characters</a>
|
||||||
|
<a href="#inspector-character-attributes">Attributes</a>
|
||||||
|
<a href="#inspector-character-knowledge">Knowledge</a>
|
||||||
|
<a href="#inspector-relationships">Relationships</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="Save" method="post" class="scene-inspector-form">
|
||||||
|
<input asp-for="SceneID" type="hidden" />
|
||||||
|
<input asp-for="ChapterID" type="hidden" />
|
||||||
|
<input asp-for="ReturnProjectID" type="hidden" />
|
||||||
|
<input asp-for="ReturnBookID" type="hidden" />
|
||||||
|
<input asp-for="ReturnToTimeline" type="hidden" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||||
|
|
||||||
|
<div class="inspector-section" data-section-title="Timing & Structure" data-section-id="inspector-timing">
|
||||||
|
<h3>Timing & Structure</h3>
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-4">
|
||||||
|
<label asp-for="SceneNumber" class="form-label"></label>
|
||||||
|
<input asp-for="SceneNumber" class="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<label asp-for="RevisionStatusID" class="form-label"></label>
|
||||||
|
<select asp-for="RevisionStatusID" asp-items="Model.RevisionStatuses" class="form-select form-select-sm"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="SceneTitle" class="form-label"></label>
|
||||||
|
<input asp-for="SceneTitle" class="form-control" />
|
||||||
|
<span asp-validation-for="SceneTitle" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Summary" class="form-label"></label>
|
||||||
|
<textarea asp-for="Summary" class="form-control" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="PrimaryLocationID" class="form-label"></label>
|
||||||
|
<select asp-for="PrimaryLocationID" asp-items="Model.LocationOptions" class="form-select form-select-sm">
|
||||||
|
<option value="">No primary location</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.LocationConsistencyMessages.Any())
|
||||||
|
{
|
||||||
|
<div class="location-helper mt-2">
|
||||||
|
<h3>Location checks</h3>
|
||||||
|
<ul class="location-warning-list">
|
||||||
|
@foreach (var message in Model.LocationConsistencyMessages)
|
||||||
|
{
|
||||||
|
<li>@message</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h4 class="inspector-subheading">Time</h4>
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label asp-for="TimeModeID" class="form-label"></label>
|
||||||
|
<select asp-for="TimeModeID" asp-items="Model.TimeModes" class="form-select form-select-sm"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label asp-for="TimeConfidenceID" class="form-label"></label>
|
||||||
|
<select asp-for="TimeConfidenceID" asp-items="Model.TimeConfidences" class="form-select form-select-sm"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label asp-for="StartDateTime" class="form-label"></label>
|
||||||
|
<input asp-for="StartDateTime" class="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label asp-for="EndDateTime" class="form-label"></label>
|
||||||
|
<input asp-for="EndDateTime" class="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<label asp-for="DurationAmount" class="form-label"></label>
|
||||||
|
<input asp-for="DurationAmount" class="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<label asp-for="DurationUnitID" class="form-label"></label>
|
||||||
|
<select asp-for="DurationUnitID" asp-items="Model.DurationUnits" class="form-select form-select-sm">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="RelativeTimeText" class="form-label"></label>
|
||||||
|
<input asp-for="RelativeTimeText" class="form-control form-control-sm" placeholder="e.g. Two days later" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inspector-section" data-section-title="Scene Purpose & Outcome" data-section-id="inspector-purpose">
|
||||||
|
<h3>Purposes</h3>
|
||||||
|
<div class="purpose-list inspector-purpose-list">
|
||||||
|
@foreach (var purpose in Model.ScenePurposeTypes)
|
||||||
|
{
|
||||||
|
<label class="purpose-check">
|
||||||
|
@if (Model.SelectedPurposeTypeIds.Contains(purpose.ScenePurposeTypeID))
|
||||||
|
{
|
||||||
|
<input type="checkbox" name="SelectedPurposeTypeIds" value="@purpose.ScenePurposeTypeID" checked />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" name="SelectedPurposeTypeIds" value="@purpose.ScenePurposeTypeID" />
|
||||||
|
}
|
||||||
|
<span>@purpose.PurposeName</span>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="purpose-notes-block mt-2">
|
||||||
|
<h3>Notes</h3>
|
||||||
|
<label asp-for="ScenePurposeNotes" class="form-label"></label>
|
||||||
|
<textarea asp-for="ScenePurposeNotes" class="form-control mb-2" rows="4"></textarea>
|
||||||
|
<label asp-for="SceneOutcomeNotes" class="form-label"></label>
|
||||||
|
<textarea asp-for="SceneOutcomeNotes" class="form-control" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inspector-section" data-section-title="Scene Metrics" data-section-id="inspector-metrics">
|
||||||
|
<h3>Scene Metrics</h3>
|
||||||
|
<div class="metric-slider-stack">
|
||||||
|
@for (var index = 0; index < Model.Metrics.Count; index++)
|
||||||
|
{
|
||||||
|
<div class="metric-slider">
|
||||||
|
<input asp-for="Metrics[index].MetricTypeID" type="hidden" />
|
||||||
|
<input asp-for="Metrics[index].MetricName" type="hidden" />
|
||||||
|
<input asp-for="Metrics[index].Description" type="hidden" />
|
||||||
|
<input asp-for="Metrics[index].MinValue" type="hidden" />
|
||||||
|
<input asp-for="Metrics[index].MaxValue" type="hidden" />
|
||||||
|
<div class="metric-slider-label">
|
||||||
|
<label asp-for="Metrics[index].Value">@Model.Metrics[index].MetricName</label>
|
||||||
|
<output>@Model.Metrics[index].Value</output>
|
||||||
|
</div>
|
||||||
|
<input asp-for="Metrics[index].Value" type="range" min="@Model.Metrics[index].MinValue" max="@Model.Metrics[index].MaxValue" class="form-range" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
|
||||||
|
<input asp-for="Metrics[index].Notes" class="form-control form-control-sm" placeholder="Metric note" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inspector-actions">
|
||||||
|
<button class="btn btn-primary" type="submit">Save scene</button>
|
||||||
|
@if (!Model.ReturnToTimeline)
|
||||||
|
{
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Chapters" asp-action="Details" asp-route-id="@Model.ChapterID">Back to chapter</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<section class="inspector-section warning-inspector-section" data-section-title="Continuity Warnings" data-section-id="inspector-warnings">
|
||||||
|
<div class="inspector-section-heading">
|
||||||
|
<h3>Continuity Warnings</h3>
|
||||||
|
@if (Model.SceneID > 0 && Model.ReturnProjectID.HasValue)
|
||||||
|
{
|
||||||
|
<form asp-controller="Warnings" asp-action="ValidateScene" method="post">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="returnToTimeline" value="@Model.ReturnToTimeline.ToString().ToLowerInvariant()" />
|
||||||
|
<button class="btn btn-outline-primary btn-sm" type="submit">Validate scene</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (!Model.Warnings.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No active warnings for this scene.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="warning-mini-list">
|
||||||
|
@foreach (var warning in Model.Warnings)
|
||||||
|
{
|
||||||
|
<div class="warning-mini-card">
|
||||||
|
<span class="severity-pill @warning.SeverityName.ToLowerInvariant()">@warning.SeverityName</span>
|
||||||
|
<strong>@warning.WarningTypeName</strong>
|
||||||
|
<p>@warning.Message</p>
|
||||||
|
<div class="button-row compact-buttons">
|
||||||
|
<form asp-controller="Warnings" asp-action="Dismiss" method="post">
|
||||||
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="returnToTimeline" value="@Model.ReturnToTimeline.ToString().ToLowerInvariant()" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Dismiss</button>
|
||||||
|
</form>
|
||||||
|
<form asp-controller="Warnings" asp-action="MarkIntentional" method="post">
|
||||||
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="returnToTimeline" value="@Model.ReturnToTimeline.ToString().ToLowerInvariant()" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Intentional</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section scene-dependency-section" data-section-title="Scene Dependencies" data-section-id="inspector-dependencies">
|
||||||
|
<h3>Scene Dependencies</h3>
|
||||||
|
<div class="dependency-columns">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">This scene depends on</p>
|
||||||
|
@if (!Model.DependenciesThisSceneNeeds.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No dependencies yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var dependency in Model.DependenciesThisSceneNeeds)
|
||||||
|
{
|
||||||
|
<div class="dependency-card">
|
||||||
|
<strong>Ch @dependency.SourceChapterNumber, Scene @dependency.SourceSceneNumber</strong>
|
||||||
|
<span>@dependency.SourceSceneTitle</span>
|
||||||
|
<span class="muted">@dependency.SceneDependencyTypeName @(dependency.IsHardDependency ? "/ hard" : "/ soft")</span>
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteSceneDependency" method="post">
|
||||||
|
<input type="hidden" name="id" value="@dependency.SceneDependencyID" />
|
||||||
|
<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">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Scenes depending on this</p>
|
||||||
|
@if (!Model.DependenciesNeedingThisScene.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No dependent scenes yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var dependency in Model.DependenciesNeedingThisScene)
|
||||||
|
{
|
||||||
|
<div class="dependency-card">
|
||||||
|
<strong>Ch @dependency.TargetChapterNumber, Scene @dependency.TargetSceneNumber</strong>
|
||||||
|
<span>@dependency.TargetSceneTitle</span>
|
||||||
|
<span class="muted">@dependency.SceneDependencyTypeName @(dependency.IsHardDependency ? "/ hard" : "/ soft")</span>
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteSceneDependency" method="post">
|
||||||
|
<input type="hidden" name="id" value="@dependency.SceneDependencyID" />
|
||||||
|
<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">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="AddSceneDependency" method="post" class="asset-create-form mt-2">
|
||||||
|
<input type="hidden" name="ProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="CurrentSceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="DependencyDirection">Plain English</label>
|
||||||
|
<select class="form-select form-select-sm" id="DependencyDirection" name="Direction">
|
||||||
|
<option value="CurrentDependsOnRelated">This scene depends on another scene</option>
|
||||||
|
<option value="RelatedDependsOnCurrent">Another scene depends on this scene</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="RelatedSceneID">Related scene</label>
|
||||||
|
<select class="form-select form-select-sm" id="RelatedSceneID" name="RelatedSceneID" asp-items="Model.SceneOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<label class="form-label" for="SceneDependencyTypeID">Dependency type</label>
|
||||||
|
<select class="form-select form-select-sm" id="SceneDependencyTypeID" name="SceneDependencyTypeID" asp-items="Model.SceneDependencyTypeOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 d-flex align-items-end">
|
||||||
|
<label class="form-check mb-2">
|
||||||
|
<input class="form-check-input" name="IsHardDependency" type="checkbox" value="true" />
|
||||||
|
<span class="form-check-label">Hard</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<textarea class="form-control form-control-sm" name="Description" rows="2" placeholder="Why this order matters"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add dependency</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section scene-move-section" data-section-title="Move / Reorder" data-section-id="inspector-move">
|
||||||
|
<h3>Move / Reorder</h3>
|
||||||
|
<form asp-controller="Scenes" asp-action="PreviewMove" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="TargetChapterID">Move to chapter</label>
|
||||||
|
<select class="form-select form-select-sm" id="TargetChapterID" name="TargetChapterID" asp-items="Model.ChapterOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="MovePosition">Position</label>
|
||||||
|
<select class="form-select form-select-sm" id="MovePosition" name="Position">
|
||||||
|
<option value="End">End of chapter</option>
|
||||||
|
<option value="Start">Start of chapter</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 d-flex align-items-end">
|
||||||
|
<label class="form-check mb-2">
|
||||||
|
<input class="form-check-input" name="RenumberScenes" type="checkbox" value="true" />
|
||||||
|
<span class="form-check-label">Renumber scenes</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Preview move</button>
|
||||||
|
</form>
|
||||||
|
<form asp-controller="Scenes" asp-action="RenumberChapter" method="post" class="mt-2">
|
||||||
|
<input type="hidden" name="chapterId" value="@Model.ChapterID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Renumber this chapter</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section thread-events-section" data-section-title="Plot Thread Events" data-section-id="inspector-thread-events">
|
||||||
|
<h3>Plot Thread Events</h3>
|
||||||
|
@if (!Model.ThreadEvents.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No plot thread events attached to this scene yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="thread-event-list">
|
||||||
|
@foreach (var threadEvent in Model.ThreadEvents)
|
||||||
|
{
|
||||||
|
<div class="thread-event-item" style="--plot-colour:@threadEvent.Colour">
|
||||||
|
<div>
|
||||||
|
<span class="thread-event-marker">@threadEvent.MarkerText</span>
|
||||||
|
<strong>@threadEvent.EventTitle</strong>
|
||||||
|
<span class="muted">@threadEvent.PlotLineName / @threadEvent.ThreadTitle</span>
|
||||||
|
</div>
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteThreadEvent" method="post">
|
||||||
|
<input type="hidden" name="id" value="@threadEvent.ThreadEventID" />
|
||||||
|
<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">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="AddThreadEvent" method="post" class="thread-event-create">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<p class="form-text mb-2">Choose an existing thread, or choose a plot line to create one from the event title.</p>
|
||||||
|
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="PlotLineID">Plot line</label>
|
||||||
|
<select class="form-select form-select-sm" id="PlotLineID" name="PlotLineID" asp-items="Model.PlotLineOptions">
|
||||||
|
<option value="">Choose plot line</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="PlotThreadID">Existing plot thread</label>
|
||||||
|
<select class="form-select form-select-sm" id="PlotThreadID" name="PlotThreadID" asp-items="Model.PlotThreadOptions">
|
||||||
|
<option value="">Create or choose a thread</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<label class="form-label" for="NewThreadTitle">New thread title</label>
|
||||||
|
<input class="form-control form-control-sm" id="NewThreadTitle" name="NewThreadTitle" placeholder="Optional" />
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<label class="form-label" for="NewThreadTypeID">Thread type</label>
|
||||||
|
<select class="form-select form-select-sm" id="NewThreadTypeID" name="NewThreadTypeID" asp-items="Model.ThreadTypeOptions">
|
||||||
|
<option value="">Default</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="EventTypeID">Event type</label>
|
||||||
|
<select class="form-select form-select-sm" id="EventTypeID" name="EventTypeID" asp-items="Model.ThreadEventTypeOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="EventTitle">Event title</label>
|
||||||
|
<input class="form-control form-control-sm" id="EventTitle" name="EventTitle" placeholder="Defaults to event type if blank" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="EventDescription">Description</label>
|
||||||
|
<textarea class="form-control form-control-sm" id="EventDescription" name="EventDescription" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add thread event</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section asset-events-section" data-section-title="Story Assets" data-section-id="inspector-asset-events">
|
||||||
|
<h3>Story Assets</h3>
|
||||||
|
@if (!Model.AssetEvents.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No asset events attached to this scene yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-event-list">
|
||||||
|
@foreach (var assetEvent in Model.AssetEvents)
|
||||||
|
{
|
||||||
|
<div class="asset-event-item">
|
||||||
|
<div>
|
||||||
|
<span class="asset-event-marker">@assetEvent.MarkerText</span>
|
||||||
|
<strong>@assetEvent.EventTitle</strong>
|
||||||
|
<span class="muted">@assetEvent.AssetName / @assetEvent.AssetEventTypeName</span>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(assetEvent.ToStateName))
|
||||||
|
{
|
||||||
|
<span class="muted">State: @(assetEvent.FromStateName ?? "Any") -> @assetEvent.ToStateName</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteAssetEvent" method="post">
|
||||||
|
<input type="hidden" name="id" value="@assetEvent.AssetEventID" />
|
||||||
|
<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">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="AddAssetEvent" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<p class="form-text mb-2">Choose an existing asset, or enter a new asset name to create it from this scene.</p>
|
||||||
|
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="StoryAssetID">Asset</label>
|
||||||
|
<select class="form-select form-select-sm" id="StoryAssetID" name="StoryAssetID" asp-items="Model.AssetOptions">
|
||||||
|
<option value="">Create or choose an asset</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<label class="form-label" for="NewAssetName">New asset name</label>
|
||||||
|
<input class="form-control form-control-sm" id="NewAssetName" name="NewAssetName" placeholder="Optional" />
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<label class="form-label" for="NewAssetKindID">Kind</label>
|
||||||
|
<select class="form-select form-select-sm" id="NewAssetKindID" name="NewAssetKindID" asp-items="Model.AssetKindOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="AssetEventTypeID">Event type</label>
|
||||||
|
<select class="form-select form-select-sm" id="AssetEventTypeID" name="AssetEventTypeID" asp-items="Model.AssetEventTypeOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="FromStateID">From state</label>
|
||||||
|
<select class="form-select form-select-sm" id="FromStateID" name="FromStateID" asp-items="Model.AssetStateOptions">
|
||||||
|
<option value="">Any</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="ToStateID">To state</label>
|
||||||
|
<select class="form-select form-select-sm" id="ToStateID" name="ToStateID" asp-items="Model.AssetStateOptions">
|
||||||
|
<option value="">No change</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="AssetEventTitle">Event title</label>
|
||||||
|
<input class="form-control form-control-sm" id="AssetEventTitle" name="EventTitle" placeholder="Defaults to event type if blank" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="AssetEventDescription">Description</label>
|
||||||
|
<textarea class="form-control form-control-sm" id="AssetEventDescription" name="EventDescription" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add asset event</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section asset-events-section" data-section-title="Asset Locations" data-section-id="inspector-asset-locations">
|
||||||
|
<h3>Asset Locations</h3>
|
||||||
|
@if (!Model.SceneAssetLocations.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No assets have been placed in a location for this scene yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-event-list">
|
||||||
|
@foreach (var assetLocation in Model.SceneAssetLocations)
|
||||||
|
{
|
||||||
|
<div class="asset-event-item">
|
||||||
|
<div>
|
||||||
|
<strong>@assetLocation.AssetName</strong>
|
||||||
|
<span class="muted" title="@assetLocation.LocationPath">@assetLocation.LocationName</span>
|
||||||
|
<span class="muted">@assetLocation.Description</span>
|
||||||
|
</div>
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteSceneAssetLocation" method="post">
|
||||||
|
<input type="hidden" name="id" value="@assetLocation.SceneAssetLocationID" />
|
||||||
|
<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">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="AddSceneAssetLocation" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="SceneAssetStoryAssetID">Asset</label>
|
||||||
|
<select class="form-select form-select-sm" id="SceneAssetStoryAssetID" name="StoryAssetID" asp-items="Model.AssetOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="SceneAssetLocationID">Location</label>
|
||||||
|
<select class="form-select form-select-sm" id="SceneAssetLocationID" name="LocationID" asp-items="Model.LocationOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<input class="form-control form-control-sm" name="Description" placeholder="e.g. desk drawer, Beth's handbag" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-check">
|
||||||
|
<input class="form-check-input" name="UpdateCurrentLocation" type="checkbox" value="true" checked />
|
||||||
|
<span class="form-check-label">Update asset's current location</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Place asset</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section asset-events-section" data-section-title="Asset Custody" data-section-id="inspector-asset-custody">
|
||||||
|
<h3>Asset Custody</h3>
|
||||||
|
@if (!Model.AssetCustodyEvents.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No custody entries attached to this scene yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-event-list">
|
||||||
|
@foreach (var custody in Model.AssetCustodyEvents)
|
||||||
|
{
|
||||||
|
<div class="asset-event-item custody">
|
||||||
|
<div>
|
||||||
|
<strong>@custody.CustodyEventTypeName</strong>
|
||||||
|
<span class="muted">@custody.AssetName</span>
|
||||||
|
<span class="muted">@custody.CustodianSummary</span>
|
||||||
|
</div>
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteAssetCustodyEvent" method="post">
|
||||||
|
<input type="hidden" name="id" value="@custody.AssetCustodyEventID" />
|
||||||
|
<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">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="AddAssetCustodyEvent" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="CustodyStoryAssetID">Asset</label>
|
||||||
|
<select class="form-select form-select-sm" id="CustodyStoryAssetID" name="StoryAssetID" asp-items="Model.AssetOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="AssetCustodyEventTypeID">Custody event</label>
|
||||||
|
<select class="form-select form-select-sm" id="AssetCustodyEventTypeID" name="AssetCustodyEventTypeID" asp-items="Model.AssetCustodyEventTypeOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="CustodyRoleID">Role</label>
|
||||||
|
<select class="form-select form-select-sm" id="CustodyRoleID" name="CustodyRoleID" asp-items="Model.CustodyRoleOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="CustodianCharacterIds">Existing custodians</label>
|
||||||
|
<select class="form-select form-select-sm" id="CustodianCharacterIds" name="CustodianCharacterIds" asp-items="Model.CharacterOptions" multiple></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="CustodianNames">Custodian names</label>
|
||||||
|
<input class="form-control form-control-sm" id="CustodianNames" name="CustodianNames" placeholder="Beth, Maggie" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="CustodyDescription">Description</label>
|
||||||
|
<textarea class="form-control form-control-sm" id="CustodyDescription" name="Description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add custody</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section character-section" data-section-title="Characters" data-section-id="inspector-characters">
|
||||||
|
<h3>Characters</h3>
|
||||||
|
@if (!Model.SceneCharacters.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No characters attached to this scene yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-event-list">
|
||||||
|
@foreach (var character in Model.SceneCharacters)
|
||||||
|
{
|
||||||
|
<div class="asset-event-item character-item">
|
||||||
|
<div>
|
||||||
|
<strong>@character.CharacterName</strong>
|
||||||
|
<span class="muted">@character.RoleInSceneTypeName / @character.PresenceTypeName</span>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(character.LocationName))
|
||||||
|
{
|
||||||
|
<span class="muted" title="@character.LocationPath">Location: @character.LocationName</span>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(character.EntryLocationName) || !string.IsNullOrWhiteSpace(character.ExitLocationName))
|
||||||
|
{
|
||||||
|
<span class="muted">In: @(character.EntryLocationName ?? "not set") / Out: @(character.ExitLocationName ?? "not set")</span>
|
||||||
|
}
|
||||||
|
<span class="muted">@character.OutfitDescription @character.PhysicalCondition @character.EmotionalState</span>
|
||||||
|
</div>
|
||||||
|
<form asp-controller="Scenes" asp-action="DeleteSceneCharacter" method="post">
|
||||||
|
<input type="hidden" name="id" value="@character.SceneCharacterID" />
|
||||||
|
<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">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-controller="Scenes" asp-action="AddSceneCharacter" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="CharacterID">Character</label>
|
||||||
|
<select class="form-select form-select-sm" id="CharacterID" name="CharacterID" asp-items="Model.CharacterOptions">
|
||||||
|
<option value="">Create or choose a character</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="NewCharacterName">New character name</label>
|
||||||
|
<input class="form-control form-control-sm" id="NewCharacterName" name="NewCharacterName" placeholder="Optional" />
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="RoleInSceneTypeID">Role</label>
|
||||||
|
<select class="form-select form-select-sm" id="RoleInSceneTypeID" name="RoleInSceneTypeID" asp-items="Model.CharacterRoleOptions">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="PresenceTypeID">Presence</label>
|
||||||
|
<select class="form-select form-select-sm" id="PresenceTypeID" name="PresenceTypeID" asp-items="Model.PresenceTypeOptions">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="CharacterLocationID">Current location</label>
|
||||||
|
<select class="form-select form-select-sm" id="CharacterLocationID" name="LocationID" asp-items="Model.LocationOptions">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="EntryLocationID">Enters from</label>
|
||||||
|
<select class="form-select form-select-sm" id="EntryLocationID" name="EntryLocationID" asp-items="Model.LocationOptions">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label" for="ExitLocationID">Exits to</label>
|
||||||
|
<select class="form-select form-select-sm" id="ExitLocationID" name="ExitLocationID" asp-items="Model.LocationOptions">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12"><input class="form-control form-control-sm" name="AppearanceNotes" placeholder="Appearance notes" /></div>
|
||||||
|
<div class="col-12"><input class="form-control form-control-sm" name="OutfitDescription" placeholder="Outfit description" /></div>
|
||||||
|
<div class="col-6"><input class="form-control form-control-sm" name="PhysicalCondition" placeholder="Physical condition" /></div>
|
||||||
|
<div class="col-6"><input class="form-control form-control-sm" name="EmotionalState" placeholder="Emotional state" /></div>
|
||||||
|
<div class="col-12"><textarea class="form-control form-control-sm" name="KnowledgeNotes" rows="2" placeholder="Knowledge notes"></textarea></div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add character</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section character-section" data-section-title="Character Attributes" data-section-id="inspector-character-attributes">
|
||||||
|
<h3>Character Attributes</h3>
|
||||||
|
@if (Model.CharacterAttributeEvents.Any())
|
||||||
|
{
|
||||||
|
<div class="asset-event-list">
|
||||||
|
@foreach (var item in Model.CharacterAttributeEvents)
|
||||||
|
{
|
||||||
|
<div class="asset-event-item character-item">
|
||||||
|
<div>
|
||||||
|
<strong>@item.CharacterName: @item.AttributeName</strong>
|
||||||
|
<span class="muted">@item.AttributeValue</span>
|
||||||
|
<span class="muted">@item.Description</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="muted">Character attribute history for this scene will appear here when available.</p>
|
||||||
|
}
|
||||||
|
<form asp-controller="Scenes" asp-action="AddCharacterAttributeEvent" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<select class="form-select form-select-sm" name="CharacterID" asp-items="Model.CharacterOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<select class="form-select form-select-sm" name="CharacterAttributeTypeID" asp-items="Model.CharacterAttributeTypeOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<input class="form-control form-control-sm" name="AttributeValue" placeholder="Value, e.g. Bruised ribs" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<textarea class="form-control form-control-sm" name="Description" rows="2" placeholder="Description"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add attribute</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section character-section" data-section-title="Character Knowledge" data-section-id="inspector-character-knowledge">
|
||||||
|
<h3>Character Knowledge</h3>
|
||||||
|
@if (Model.CharacterKnowledge.Any())
|
||||||
|
{
|
||||||
|
<div class="asset-event-list">
|
||||||
|
@foreach (var item in Model.CharacterKnowledge)
|
||||||
|
{
|
||||||
|
<div class="asset-event-item character-item">
|
||||||
|
<div>
|
||||||
|
<strong>@item.CharacterName @item.KnowledgeStateName</strong>
|
||||||
|
<span class="muted">@(item.AssetName ?? item.ThreadTitle)</span>
|
||||||
|
<span class="muted">@item.Description</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<form asp-controller="Scenes" asp-action="AddCharacterKnowledge" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12"><select class="form-select form-select-sm" name="CharacterID" asp-items="Model.CharacterOptions"></select></div>
|
||||||
|
<div class="col-12">
|
||||||
|
<select class="form-select form-select-sm" name="StoryAssetID" asp-items="Model.AssetOptions">
|
||||||
|
<option value="">No asset</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<select class="form-select form-select-sm" name="PlotThreadID" asp-items="Model.PlotThreadOptions">
|
||||||
|
<option value="">No plot thread</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12"><select class="form-select form-select-sm" name="KnowledgeStateID" asp-items="Model.KnowledgeStateOptions"></select></div>
|
||||||
|
<div class="col-12"><textarea class="form-control form-control-sm" name="Description" rows="2" placeholder="Description"></textarea></div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add knowledge</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inspector-section character-section" data-section-title="Relationship Events" data-section-id="inspector-relationships">
|
||||||
|
<h3>Relationship Events</h3>
|
||||||
|
@if (Model.RelationshipEvents.Any())
|
||||||
|
{
|
||||||
|
<div class="asset-event-list">
|
||||||
|
@foreach (var item in Model.RelationshipEvents)
|
||||||
|
{
|
||||||
|
<div class="asset-event-item character-item">
|
||||||
|
<div>
|
||||||
|
<strong>@item.CharacterAName -> @item.CharacterBName</strong>
|
||||||
|
<span class="muted">@item.RelationshipStateName / intensity @item.Intensity</span>
|
||||||
|
<span class="muted">@item.Description</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<form asp-controller="Scenes" asp-action="AddRelationshipEvent" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="SceneID" value="@Model.SceneID" />
|
||||||
|
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||||
|
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<select class="form-select form-select-sm" name="CharacterRelationshipID" asp-items="Model.RelationshipOptions">
|
||||||
|
<option value="">Create or choose relationship</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6"><select class="form-select form-select-sm" name="CharacterAID" asp-items="Model.CharacterOptions"><option value="">Character A</option></select></div>
|
||||||
|
<div class="col-6"><select class="form-select form-select-sm" name="CharacterBID" asp-items="Model.CharacterOptions"><option value="">Character B</option></select></div>
|
||||||
|
<div class="col-12"><select class="form-select form-select-sm" name="RelationshipTypeID" asp-items="Model.RelationshipTypeOptions"><option value="">Relationship type</option></select></div>
|
||||||
|
<div class="col-8"><select class="form-select form-select-sm" name="RelationshipStateID" asp-items="Model.RelationshipStateOptions"></select></div>
|
||||||
|
<div class="col-4"><input class="form-control form-control-sm" name="Intensity" type="number" min="1" max="10" placeholder="1-10" /></div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-check">
|
||||||
|
<input class="form-check-input" name="IsKnownToOtherCharacter" type="checkbox" value="true" />
|
||||||
|
<span class="form-check-label">Other character knows</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-12"><textarea class="form-control form-control-sm" name="Description" rows="2" placeholder="Description"></textarea></div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add relationship event</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
25
PlotLine/Views/Shared/Error.cshtml
Normal file
25
PlotLine/Views/Shared/Error.cshtml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@model ErrorViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
@if (Model.ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Development Mode</h3>
|
||||||
|
<p>
|
||||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
|
It can result in displaying sensitive information from exceptions to end users.
|
||||||
|
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||||
|
and restarting the app.
|
||||||
|
</p>
|
||||||
46
PlotLine/Views/Shared/_Layout.cshtml
Normal file
46
PlotLine/Views/Shared/_Layout.cshtml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@ViewData["Title"] - PlotLine</title>
|
||||||
|
<script type="importmap"></script>
|
||||||
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="~/PlotLine.styles.css" asp-append-version="true" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom plotline-nav mb-3">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand fw-semibold" asp-area="" asp-controller="Projects" asp-action="Index">PlotLine</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||||
|
aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||||
|
<ul class="navbar-nav flex-grow-1">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-dark" asp-area="" asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">About</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<div class="container-fluid plotline-shell @ViewData["ShellClass"]">
|
||||||
|
<main role="main" class="pb-3">
|
||||||
|
@RenderBody()
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||||
|
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
|
||||||
|
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||||
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
PlotLine/Views/Shared/_Layout.cshtml.css
Normal file
48
PlotLine/Views/Shared/_Layout.cshtml.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||||
|
for details on configuring this project to bundle and minify static web assets. */
|
||||||
|
|
||||||
|
a.navbar-brand {
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0077cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-top {
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow {
|
||||||
|
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.accept-policy {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
2
PlotLine/Views/Shared/_ValidationScriptsPartial.cshtml
Normal file
2
PlotLine/Views/Shared/_ValidationScriptsPartial.cshtml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||||
|
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
|
||||||
158
PlotLine/Views/StoryAssets/Details.cshtml
Normal file
158
PlotLine/Views/StoryAssets/Details.cshtml
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
@model StoryAssetDetailViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.Asset.AssetName;
|
||||||
|
var firstAssetEvent = Model.Events.FirstOrDefault();
|
||||||
|
var firstCustodyEvent = Model.CustodyEvents.FirstOrDefault();
|
||||||
|
var firstSceneId = firstAssetEvent?.SceneID ?? firstCustodyEvent?.SceneID;
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||||
|
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story assets</a>
|
||||||
|
<span>@Model.Asset.AssetName</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Project context</span>
|
||||||
|
<strong>@Model.Project.ProjectName</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@firstSceneId">Show on timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">@Model.Asset.KindName</p>
|
||||||
|
<h1>@Model.Asset.AssetName</h1>
|
||||||
|
<p class="lead-text">@Model.Asset.Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story assets</a>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@firstSceneId">Back to timeline</a>
|
||||||
|
<a class="btn btn-outline-primary" asp-action="Edit" asp-route-id="@Model.Asset.StoryAssetID">Edit asset</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="asset-detail-grid">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Current state</h2>
|
||||||
|
<p><strong>Importance:</strong> @Model.Asset.Importance / 10</p>
|
||||||
|
<p><strong>State:</strong> @(Model.Asset.CurrentStateName ?? "No state")</p>
|
||||||
|
<p><strong>Location:</strong> @(Model.Asset.CurrentLocationPath ?? "No location")</p>
|
||||||
|
<p><strong>Resolved:</strong> @(Model.Asset.IsResolved ? "Yes" : "No")</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Dependencies</h2>
|
||||||
|
@if (!Model.Dependencies.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No dependencies yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var dependency in Model.Dependencies)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<strong>@dependency.SourceAssetName @dependency.DependencyTypeName.ToLowerInvariant() @dependency.TargetAssetName</strong>
|
||||||
|
<span>@dependency.SourceRequiredStateName before @dependency.TargetBlockedStateName</span>
|
||||||
|
<form asp-action="DeleteDependency" method="post">
|
||||||
|
<input type="hidden" name="id" value="@dependency.AssetDependencyID" />
|
||||||
|
<input type="hidden" name="returnAssetId" value="@Model.Asset.StoryAssetID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Remove</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-action="AddDependency" method="post" class="asset-create-form">
|
||||||
|
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="returnAssetId" value="@Model.Asset.StoryAssetID" />
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Source asset</label>
|
||||||
|
<select class="form-select form-select-sm" name="SourceAssetID" asp-items="Model.AssetOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Target asset</label>
|
||||||
|
<select class="form-select form-select-sm" name="TargetAssetID" asp-items="Model.AssetOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Dependency</label>
|
||||||
|
<select class="form-select form-select-sm" name="AssetDependencyTypeID" asp-items="Model.AssetDependencyTypeOptions"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Source required state</label>
|
||||||
|
<select class="form-select form-select-sm" name="SourceRequiredStateID" asp-items="Model.AssetStateOptions">
|
||||||
|
<option value="">Any</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Target blocked state</label>
|
||||||
|
<select class="form-select form-select-sm" name="TargetBlockedStateID" asp-items="Model.AssetStateOptions">
|
||||||
|
<option value="">Any</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Description</label>
|
||||||
|
<textarea class="form-control form-control-sm" name="Description" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-2" type="submit">Add dependency</button>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section mt-3">
|
||||||
|
<h2>Events across scenes</h2>
|
||||||
|
@if (!Model.Events.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No scene events yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var assetEvent in Model.Events)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<span class="asset-event-marker">@assetEvent.MarkerText</span>
|
||||||
|
<div>
|
||||||
|
<strong>@assetEvent.EventTitle</strong>
|
||||||
|
<span>@assetEvent.BookTitle / Ch @assetEvent.ChapterNumber / Scene @assetEvent.SceneNumber: @assetEvent.SceneTitle</span>
|
||||||
|
<span>@assetEvent.FromStateName -> @assetEvent.ToStateName</span>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@assetEvent.SceneID">Open in timeline</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section mt-3">
|
||||||
|
<h2>Custody</h2>
|
||||||
|
@if (!Model.CustodyEvents.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No custody events yet.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-mini-list">
|
||||||
|
@foreach (var custody in Model.CustodyEvents)
|
||||||
|
{
|
||||||
|
<div class="asset-mini-item">
|
||||||
|
<strong>@custody.CustodyEventTypeName</strong>
|
||||||
|
<span>@custody.CustodianSummary</span>
|
||||||
|
<span>@custody.BookTitle / Ch @custody.ChapterNumber / Scene @custody.SceneNumber: @custody.SceneTitle</span>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@custody.SceneID">Open in timeline</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
64
PlotLine/Views/StoryAssets/Edit.cshtml
Normal file
64
PlotLine/Views/StoryAssets/Edit.cshtml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
@model StoryAssetEditViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.StoryAssetID == 0 ? "New Story Asset" : "Edit Story Asset";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Story asset</p>
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Save" method="post" class="edit-panel">
|
||||||
|
<input asp-for="StoryAssetID" type="hidden" />
|
||||||
|
<input asp-for="ProjectID" type="hidden" />
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="AssetName" class="form-label"></label>
|
||||||
|
<input asp-for="AssetName" class="form-control" />
|
||||||
|
<span asp-validation-for="AssetName" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="AssetKindID" class="form-label"></label>
|
||||||
|
<select asp-for="AssetKindID" asp-items="Model.AssetKindOptions" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Importance" class="form-label"></label>
|
||||||
|
<input asp-for="Importance" class="form-range" min="1" max="10" />
|
||||||
|
<span asp-validation-for="Importance" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="CurrentStateID" class="form-label"></label>
|
||||||
|
<select asp-for="CurrentStateID" asp-items="Model.AssetStateOptions" class="form-select">
|
||||||
|
<option value="">No state</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="CurrentLocationID" class="form-label"></label>
|
||||||
|
<select asp-for="CurrentLocationID" asp-items="Model.LocationOptions" class="form-select">
|
||||||
|
<option value="">No location</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 d-flex align-items-end">
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input asp-for="IsResolved" class="form-check-input" />
|
||||||
|
<label asp-for="IsResolved" class="form-check-label"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Description" class="form-label"></label>
|
||||||
|
<textarea asp-for="Description" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-row mt-3">
|
||||||
|
<button class="btn btn-primary" type="submit">Save asset</button>
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
59
PlotLine/Views/StoryAssets/Index.cshtml
Normal file
59
PlotLine/Views/StoryAssets/Index.cshtml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
@model StoryAssetListViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Story Assets";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title"><span class="eyebrow">Project context</span><strong>@Model.Project.ProjectName</strong></div>
|
||||||
|
<nav class="context-nav" aria-label="Project navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Story assets</p>
|
||||||
|
<h1>@Model.Project.ProjectName</h1>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a class="btn btn-primary" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">Add asset</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
@if (!Model.Assets.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No story assets yet. Add an object, clue, secret or other story item to track it across scenes.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="asset-card-grid">
|
||||||
|
@foreach (var asset in Model.Assets)
|
||||||
|
{
|
||||||
|
<article class="asset-card">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">@asset.KindName</p>
|
||||||
|
<h2><a asp-action="Details" asp-route-id="@asset.StoryAssetID">@asset.AssetName</a></h2>
|
||||||
|
<p class="muted">@asset.Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="asset-card-footer">
|
||||||
|
<span class="status-pill">Importance @asset.Importance</span>
|
||||||
|
<span class="purpose-chip">@(asset.CurrentStateName ?? "No state")</span>
|
||||||
|
@if (asset.IsResolved)
|
||||||
|
{
|
||||||
|
<span class="purpose-chip">Resolved</span>
|
||||||
|
}
|
||||||
|
<a class="btn btn-outline-secondary btn-sm ms-auto" asp-action="Edit" asp-route-id="@asset.StoryAssetID">Edit</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
370
PlotLine/Views/Timeline/Index.cshtml
Normal file
370
PlotLine/Views/Timeline/Index.cshtml
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
@model TimelineViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Timeline";
|
||||||
|
ViewData["ShellClass"] = "plotline-shell-wide";
|
||||||
|
var selectedBook = Model.Books.FirstOrDefault(x => x.Book.BookID == Model.SelectedBookID)?.Book;
|
||||||
|
var timelineMetricScenes = Model.Books
|
||||||
|
.SelectMany(book => book.Chapters.SelectMany(chapter => chapter.Scenes.Select(scene => new
|
||||||
|
{
|
||||||
|
sceneId = scene.SceneID,
|
||||||
|
axisLabel = $"{chapter.Chapter.ChapterNumber:g}.{scene.SceneNumber:g}",
|
||||||
|
chapterLabel = $"Chapter {chapter.Chapter.ChapterNumber:g}",
|
||||||
|
sceneNumber = scene.SceneNumber.ToString("g"),
|
||||||
|
sceneTitle = scene.SceneTitle,
|
||||||
|
bookTitle = book.Book.BookTitle,
|
||||||
|
timelineUrl = Url.Action("Index", "Timeline", new { projectId = Model.Project.ProjectID, bookId = book.Book.BookID, selectedSceneId = scene.SceneID })
|
||||||
|
})))
|
||||||
|
.ToList();
|
||||||
|
var timelineMetricShapePayload = new
|
||||||
|
{
|
||||||
|
scenes = timelineMetricScenes,
|
||||||
|
metrics = Model.MetricGraphs.Select(graph => new
|
||||||
|
{
|
||||||
|
metricName = graph.MetricName,
|
||||||
|
points = graph.Points.Select(point => new { sceneId = point.SceneID, value = point.Value })
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Workspace context</span>
|
||||||
|
<strong>@Model.Project.ProjectName@if (selectedBook is not null) { <span> / @selectedBook.BookTitle</span> }</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Workspace navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Story health</a>
|
||||||
|
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Warnings</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="workspace-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Story timeline</p>
|
||||||
|
<h1>@Model.Project.ProjectName</h1>
|
||||||
|
</div>
|
||||||
|
<form method="get" class="timeline-filter">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<select name="bookId" asp-items="Model.BookOptions" class="form-select">
|
||||||
|
<option value="">All books</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-outline-primary" type="submit">View</button>
|
||||||
|
</form>
|
||||||
|
<div class="button-row">
|
||||||
|
<form asp-controller="Warnings" asp-action="ValidateProject" method="post">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.SelectedBookID" />
|
||||||
|
<button class="btn btn-outline-primary" type="submit">Validate project</button>
|
||||||
|
</form>
|
||||||
|
@if (Model.SelectedBookID.HasValue)
|
||||||
|
{
|
||||||
|
<form asp-controller="Warnings" asp-action="ValidateBook" method="post">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.SelectedBookID" />
|
||||||
|
<button class="btn btn-outline-primary" type="submit">Validate book</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Warnings</a>
|
||||||
|
<a class="btn btn-outline-secondary" asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Story health</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!Model.Books.Any())
|
||||||
|
{
|
||||||
|
<section class="empty-panel">
|
||||||
|
<h2>No timeline yet</h2>
|
||||||
|
<p>Add books, chapters and scenes to see them arranged here in narrative order.</p>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="workspace-layout">
|
||||||
|
<main class="timeline-workspace">
|
||||||
|
<section class="timeline-shell workspace-timeline">
|
||||||
|
@foreach (var book in Model.Books)
|
||||||
|
{
|
||||||
|
<div class="timeline-book">
|
||||||
|
<div class="timeline-book-row">
|
||||||
|
<div class="timeline-book-title">Book @book.Book.BookNumber: @book.Book.BookTitle</div>
|
||||||
|
<div class="timeline-chapters">
|
||||||
|
@foreach (var chapter in book.Chapters)
|
||||||
|
{
|
||||||
|
<div class="timeline-chapter" style="--chapter-scene-count:@Math.Max(chapter.Scenes.Count, 1)">
|
||||||
|
<div class="timeline-chapter-title">
|
||||||
|
<span>Chapter @chapter.Chapter.ChapterNumber</span>
|
||||||
|
<strong>@chapter.Chapter.ChapterTitle</strong>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-scenes">
|
||||||
|
@if (!chapter.Scenes.Any())
|
||||||
|
{
|
||||||
|
<div class="timeline-empty-scene">No scenes</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var scene in chapter.Scenes)
|
||||||
|
{
|
||||||
|
var selectedClass = scene.SceneID == Model.SelectedSceneID ? "selected" : "";
|
||||||
|
<a class="timeline-scene-card @selectedClass"
|
||||||
|
asp-controller="Timeline"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-projectId="@Model.Project.ProjectID"
|
||||||
|
asp-route-bookId="@Model.SelectedBookID"
|
||||||
|
asp-route-selectedSceneId="@scene.SceneID">
|
||||||
|
<span class="scene-number">Scene @scene.SceneNumber</span>
|
||||||
|
<strong>@scene.SceneTitle</strong>
|
||||||
|
<div class="scene-badge-row">
|
||||||
|
<span class="status-pill">@scene.RevisionStatusName</span>
|
||||||
|
@if (scene.WarningCount > 0)
|
||||||
|
{
|
||||||
|
<span class="warning-placeholder active">@scene.WarningCount warning@(scene.WarningCount == 1 ? "" : "s")</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="warning-placeholder">Warnings 0</span>
|
||||||
|
}
|
||||||
|
@if (scene.DependencyCount > 0)
|
||||||
|
{
|
||||||
|
<span class="dependency-placeholder">@scene.DependencyCount dep@(scene.DependencyCount == 1 ? "" : "s")</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<span class="time-label">@scene.TimeLabel</span>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(scene.PrimaryLocationName))
|
||||||
|
{
|
||||||
|
<span class="location-pill" title="@scene.PrimaryLocationPath">@scene.PrimaryLocationName</span>
|
||||||
|
}
|
||||||
|
@if (scene.PurposeLabels.Any())
|
||||||
|
{
|
||||||
|
<div class="purpose-chip-row">
|
||||||
|
@foreach (var purpose in scene.PurposeLabels.Take(3))
|
||||||
|
{
|
||||||
|
<span class="purpose-chip">@purpose.PurposeName</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<span class="scene-summary">@scene.Summary</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (Model.PlotLanes.Any() && Model.OrderedScenes.Any())
|
||||||
|
{
|
||||||
|
<div class="plot-lane-board" style="--scene-count:@Model.OrderedScenes.Count">
|
||||||
|
<div class="plot-lane-board-heading">
|
||||||
|
<span>Plot line lanes</span>
|
||||||
|
<small>Thread event markers by scene</small>
|
||||||
|
</div>
|
||||||
|
@foreach (var lane in Model.PlotLanes)
|
||||||
|
{
|
||||||
|
<div class="plot-lane-row" style="--plot-colour:@lane.PlotLine.Colour">
|
||||||
|
<a class="plot-lane-label" asp-controller="PlotLines" asp-action="Edit" asp-route-id="@lane.PlotLine.PlotLineID">
|
||||||
|
<span class="colour-swatch" style="background:@lane.PlotLine.Colour"></span>
|
||||||
|
<strong>@lane.PlotLine.PlotLineName</strong>
|
||||||
|
</a>
|
||||||
|
<div class="plot-lane-slots">
|
||||||
|
@foreach (var slot in lane.SceneSlots)
|
||||||
|
{
|
||||||
|
<div class="plot-lane-slot" title="Scene @slot.Scene.SceneNumber: @slot.Scene.SceneTitle">
|
||||||
|
@foreach (var threadEvent in slot.Events)
|
||||||
|
{
|
||||||
|
var markerClass = threadEvent.EventTypeName is "Resolved" or "Payoff"
|
||||||
|
? "resolved"
|
||||||
|
: threadEvent.EventTypeName is "Contradicted"
|
||||||
|
? "contradicted"
|
||||||
|
: "";
|
||||||
|
<a class="thread-marker @markerClass"
|
||||||
|
asp-controller="Timeline"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-projectId="@Model.Project.ProjectID"
|
||||||
|
asp-route-bookId="@Model.SelectedBookID"
|
||||||
|
asp-route-selectedSceneId="@slot.Scene.SceneID"
|
||||||
|
title="@threadEvent.ThreadTitle: @threadEvent.EventTitle">
|
||||||
|
@threadEvent.MarkerText
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="future-lane-placeholder">
|
||||||
|
Add visible plot lines and thread events to see narrative lanes here.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (Model.AssetLanes.Any() && Model.OrderedScenes.Any())
|
||||||
|
{
|
||||||
|
<div class="asset-lane-board" style="--scene-count:@Model.OrderedScenes.Count">
|
||||||
|
<div class="plot-lane-board-heading">
|
||||||
|
<span>Story asset lanes</span>
|
||||||
|
<small>Asset event markers by scene</small>
|
||||||
|
</div>
|
||||||
|
@foreach (var lane in Model.AssetLanes)
|
||||||
|
{
|
||||||
|
<div class="asset-lane-row">
|
||||||
|
<a class="asset-lane-label" asp-controller="StoryAssets" asp-action="Details" asp-route-id="@lane.Asset.StoryAssetID">
|
||||||
|
<strong>@lane.Asset.AssetName</strong>
|
||||||
|
<span>@lane.Asset.KindName / @lane.Asset.Importance</span>
|
||||||
|
</a>
|
||||||
|
<div class="asset-lane-slots">
|
||||||
|
@foreach (var slot in lane.SceneSlots)
|
||||||
|
{
|
||||||
|
<div class="asset-lane-slot" title="Scene @slot.Scene.SceneNumber: @slot.Scene.SceneTitle">
|
||||||
|
@foreach (var assetEvent in slot.Events)
|
||||||
|
{
|
||||||
|
<a class="asset-marker"
|
||||||
|
asp-controller="StoryAssets"
|
||||||
|
asp-action="Details"
|
||||||
|
asp-route-id="@assetEvent.StoryAssetID"
|
||||||
|
title="@assetEvent.AssetName: @assetEvent.EventTitle">
|
||||||
|
@assetEvent.MarkerText
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="future-lane-placeholder">
|
||||||
|
Add story assets and asset events to see asset lanes here.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (Model.CharacterLanes.Any() && Model.OrderedScenes.Any())
|
||||||
|
{
|
||||||
|
<div class="character-lane-board" style="--scene-count:@Model.OrderedScenes.Count">
|
||||||
|
<div class="plot-lane-board-heading">
|
||||||
|
<span>Character appearances</span>
|
||||||
|
<small>First twelve project characters</small>
|
||||||
|
</div>
|
||||||
|
@foreach (var lane in Model.CharacterLanes)
|
||||||
|
{
|
||||||
|
<div class="character-lane-row">
|
||||||
|
<a class="character-lane-label" asp-controller="Characters" asp-action="Details" asp-route-id="@lane.Character.CharacterID">
|
||||||
|
<strong>@lane.Character.CharacterName</strong>
|
||||||
|
<span>@(lane.Character.ShortName ?? "Character")</span>
|
||||||
|
</a>
|
||||||
|
<div class="character-lane-slots">
|
||||||
|
@foreach (var slot in lane.SceneSlots)
|
||||||
|
{
|
||||||
|
<div class="character-lane-slot" title="Scene @slot.Scene.SceneNumber: @slot.Scene.SceneTitle">
|
||||||
|
@foreach (var appearance in slot.Appearances)
|
||||||
|
{
|
||||||
|
<a class="character-marker"
|
||||||
|
asp-controller="Timeline"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-projectId="@Model.Project.ProjectID"
|
||||||
|
asp-route-bookId="@Model.SelectedBookID"
|
||||||
|
asp-route-selectedSceneId="@slot.Scene.SceneID"
|
||||||
|
title="@appearance.CharacterName: @appearance.RoleInSceneTypeName / @appearance.PresenceTypeName">
|
||||||
|
@(!string.IsNullOrWhiteSpace(appearance.PresenceTypeName) ? appearance.PresenceTypeName.Substring(0, 1) : "C")
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="future-lane-placeholder">
|
||||||
|
Add characters to scenes to see character appearance lanes here.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="pacing-panel">
|
||||||
|
<div class="pacing-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Pacing</p>
|
||||||
|
<h2>Metric shape</h2>
|
||||||
|
</div>
|
||||||
|
<span class="muted">@Model.OrderedScenes.Count scene@(Model.OrderedScenes.Count == 1 ? "" : "s")</span>
|
||||||
|
</div>
|
||||||
|
@if (!Model.MetricGraphs.Any() || !Model.OrderedScenes.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">Add scenes to begin seeing emotional and pacing shape.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="metric-shape-chart" data-metric-shape-chart>
|
||||||
|
<p class="metric-shape-intro">Compare selected metrics across the same scene timeline. Click a point to focus that scene.</p>
|
||||||
|
<div class="metric-toggle-row" data-metric-toggles></div>
|
||||||
|
<div class="metric-chart-frame">
|
||||||
|
<canvas aria-label="Comparative scene metric line graph"></canvas>
|
||||||
|
<p class="muted d-none" data-chart-fallback>Metric chart could not load. The raw metric rows are still available below.</p>
|
||||||
|
</div>
|
||||||
|
<script type="application/json">@Html.Raw(System.Text.Json.JsonSerializer.Serialize(timelineMetricShapePayload))</script>
|
||||||
|
</div>
|
||||||
|
<details class="metric-raw-details">
|
||||||
|
<summary>Show raw metric rows</summary>
|
||||||
|
<div class="metric-graph-stack mt-2">
|
||||||
|
@foreach (var graph in Model.MetricGraphs)
|
||||||
|
{
|
||||||
|
<div class="metric-graph-row">
|
||||||
|
<div class="metric-graph-label">@graph.MetricName</div>
|
||||||
|
<div class="metric-graph-points" style="--scene-count:@Math.Max(graph.Points.Count, 1)">
|
||||||
|
@foreach (var point in graph.Points)
|
||||||
|
{
|
||||||
|
<a asp-controller="Timeline"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-projectId="@Model.Project.ProjectID"
|
||||||
|
asp-route-bookId="@Model.SelectedBookID"
|
||||||
|
asp-route-selectedSceneId="@point.SceneID"
|
||||||
|
class="metric-point"
|
||||||
|
style="--metric-height:@point.Percent%"
|
||||||
|
title="Scene @point.SceneNumber: @point.SceneTitle - @point.Value">
|
||||||
|
<span>@point.Value</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<aside class="scene-inspector-panel">
|
||||||
|
@if (Model.SelectedScene is null)
|
||||||
|
{
|
||||||
|
<div class="inspector-empty">
|
||||||
|
<p class="eyebrow">Scene inspector</p>
|
||||||
|
<h2>Select a scene</h2>
|
||||||
|
<p>Click a scene card to edit its summary, timing, purposes, outcomes and metric sliders without leaving the timeline.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="inspector-header">
|
||||||
|
<p class="eyebrow">Scene inspector</p>
|
||||||
|
<h2>@Model.SelectedScene.SceneTitle</h2>
|
||||||
|
</div>
|
||||||
|
<partial name="~/Views/Scenes/_SceneInspector.cshtml" model="Model.SelectedScene" />
|
||||||
|
}
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
196
PlotLine/Views/Warnings/Index.cshtml
Normal file
196
PlotLine/Views/Warnings/Index.cshtml
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
@model WarningDashboardViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Continuity Warnings";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="context-header">
|
||||||
|
<div class="context-title">
|
||||||
|
<span class="eyebrow">Workspace context</span>
|
||||||
|
<strong>@Model.Project.ProjectName</strong>
|
||||||
|
</div>
|
||||||
|
<nav class="context-nav" aria-label="Workspace navigation">
|
||||||
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Timeline</a>
|
||||||
|
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Story health</a>
|
||||||
|
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||||
|
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
|
||||||
|
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||||
|
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="page-heading compact">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Continuity</p>
|
||||||
|
<h1>@Model.Project.ProjectName</h1>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<form asp-action="ValidateProject" method="post">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
|
<button class="btn btn-primary" type="submit">Validate project</button>
|
||||||
|
</form>
|
||||||
|
@if (Model.BookID.HasValue)
|
||||||
|
{
|
||||||
|
<form asp-action="ValidateBook" method="post">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
|
<button class="btn btn-outline-primary" type="submit">Validate book</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.LastValidationSummary is not null)
|
||||||
|
{
|
||||||
|
<section class="validation-summary">
|
||||||
|
<strong>Validation complete.</strong>
|
||||||
|
<span>@Model.LastValidationSummary.ErrorCount errors</span>
|
||||||
|
<span>@Model.LastValidationSummary.WarningCount warnings</span>
|
||||||
|
<span>@Model.LastValidationSummary.InfoCount info</span>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="warning-filter-panel">
|
||||||
|
<form method="get" class="row g-2 align-items-end">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Book</label>
|
||||||
|
<select name="bookId" asp-items="Model.BookOptions" class="form-select form-select-sm">
|
||||||
|
<option value="">All books</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Type</label>
|
||||||
|
<select name="warningTypeId" asp-items="Model.WarningTypeOptions" class="form-select form-select-sm">
|
||||||
|
<option value="">All types</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Severity</label>
|
||||||
|
<select name="warningSeverityId" asp-items="Model.SeverityOptions" class="form-select form-select-sm">
|
||||||
|
<option value="">All severities</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Entity</label>
|
||||||
|
<select name="entityType" asp-items="Model.EntityTypeOptions" class="form-select form-select-sm">
|
||||||
|
<option value="">All entities</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-check">
|
||||||
|
<input class="form-check-input" name="includeDismissed" type="checkbox" value="true" checked="@Model.IncludeDismissed" />
|
||||||
|
<span class="form-check-label">Dismissed</span>
|
||||||
|
</label>
|
||||||
|
<label class="form-check">
|
||||||
|
<input class="form-check-input" name="includeIntentional" type="checkbox" value="true" checked="@Model.IncludeIntentional" />
|
||||||
|
<span class="form-check-label">Intentional</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<button class="btn btn-outline-primary btn-sm" type="submit">Filter warnings</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="warning-dashboard-grid">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>By severity</h2>
|
||||||
|
@foreach (var count in Model.CountsBySeverity)
|
||||||
|
{
|
||||||
|
<div class="warning-count-row">
|
||||||
|
<span class="severity-pill @count.Key.ToLowerInvariant()">@count.Key</span>
|
||||||
|
<strong>@count.Value</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>By type</h2>
|
||||||
|
@foreach (var count in Model.CountsByType.Take(8))
|
||||||
|
{
|
||||||
|
<div class="warning-count-row">
|
||||||
|
<span>@count.Key</span>
|
||||||
|
<strong>@count.Value</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
<h2>Warnings</h2>
|
||||||
|
@if (!Model.Warnings.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No warnings match this view.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="warning-list">
|
||||||
|
@foreach (var warning in Model.Warnings)
|
||||||
|
{
|
||||||
|
<article class="warning-card">
|
||||||
|
<div>
|
||||||
|
<div class="warning-card-title">
|
||||||
|
<span class="severity-pill @warning.SeverityName.ToLowerInvariant()">@warning.SeverityName</span>
|
||||||
|
<strong>@warning.WarningTypeName</strong>
|
||||||
|
@if (warning.IsDismissed) { <span class="location-badge">dismissed</span> }
|
||||||
|
@if (warning.IsIntentional) { <span class="location-badge">intentional</span> }
|
||||||
|
</div>
|
||||||
|
<p>@warning.Message</p>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(warning.Details))
|
||||||
|
{
|
||||||
|
<p class="muted">@warning.Details</p>
|
||||||
|
}
|
||||||
|
<p class="muted">
|
||||||
|
@(warning.BookTitle ?? "Project")
|
||||||
|
@if (warning.SceneID.HasValue)
|
||||||
|
{
|
||||||
|
<span>/ Scene @warning.SceneNumber: @warning.SceneTitle</span>
|
||||||
|
}
|
||||||
|
<span>/ @warning.EntityType</span>
|
||||||
|
<span>/ Last seen @warning.LastDetectedDate.ToString("dd MMM yyyy HH:mm")</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="warning-actions">
|
||||||
|
@if (warning.SceneID.HasValue)
|
||||||
|
{
|
||||||
|
<a class="btn btn-outline-primary btn-sm"
|
||||||
|
asp-controller="Timeline"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-projectId="@Model.Project.ProjectID"
|
||||||
|
asp-route-bookId="@warning.BookID"
|
||||||
|
asp-route-selectedSceneId="@warning.SceneID">Open scene</a>
|
||||||
|
}
|
||||||
|
@if (warning.IsDismissed || warning.IsIntentional)
|
||||||
|
{
|
||||||
|
<form asp-action="Restore" method="post">
|
||||||
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Restore</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<form asp-action="Dismiss" method="post">
|
||||||
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Dismiss</button>
|
||||||
|
</form>
|
||||||
|
<form asp-action="MarkIntentional" method="post">
|
||||||
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="submit">Intentional</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
4
PlotLine/Views/_ViewImports.cshtml
Normal file
4
PlotLine/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
@using PlotLine
|
||||||
|
@using PlotLine.Models
|
||||||
|
@using PlotLine.ViewModels
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
3
PlotLine/Views/_ViewStart.cshtml
Normal file
3
PlotLine/Views/_ViewStart.cshtml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@{
|
||||||
|
Layout = "_Layout";
|
||||||
|
}
|
||||||
8
PlotLine/appsettings.Development.json
Normal file
8
PlotLine/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
PlotLine/appsettings.json
Normal file
12
PlotLine/appsettings.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Server=localhost;Database=PlotLine;User Id=PlotLineApp;Password=t6eb1cvd9bNB27czde#h9r;MultipleActiveResultSets=true;Encrypt=false;"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
1684
PlotLine/wwwroot/css/site.css
Normal file
1684
PlotLine/wwwroot/css/site.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
PlotLine/wwwroot/favicon.ico
Normal file
BIN
PlotLine/wwwroot/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
282
PlotLine/wwwroot/js/site.js
Normal file
282
PlotLine/wwwroot/js/site.js
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
(() => {
|
||||||
|
const roots = document.querySelectorAll(".scene-inspector-root");
|
||||||
|
if (!roots.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slugify = (value) => value
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/&/g, "and")
|
||||||
|
.replace(/[^a-z0-9]+/g, "-")
|
||||||
|
.replace(/^-|-$/g, "");
|
||||||
|
|
||||||
|
roots.forEach((root) => {
|
||||||
|
const sceneId = root.querySelector("[name='SceneID']")?.value || "new";
|
||||||
|
const storagePrefix = `plotline.inspector.${sceneId}.`;
|
||||||
|
const defaultOpen = new Set(["overview", "timing", "characters"]);
|
||||||
|
const sections = [...root.querySelectorAll(".inspector-section")];
|
||||||
|
|
||||||
|
sections.forEach((section) => {
|
||||||
|
if (section.dataset.enhanced === "true") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const heading = section.querySelector("h3, h2");
|
||||||
|
const title = section.dataset.sectionTitle || heading?.textContent?.trim() || "Overview";
|
||||||
|
const slug = slugify(title);
|
||||||
|
const sectionId = section.dataset.sectionId || `inspector-${slug}`;
|
||||||
|
const storageKey = sectionId.replace(/^inspector-/, "");
|
||||||
|
section.id = sectionId;
|
||||||
|
section.dataset.searchText = `${title} ${section.textContent}`.toLowerCase();
|
||||||
|
section.dataset.enhanced = "true";
|
||||||
|
|
||||||
|
const body = document.createElement("div");
|
||||||
|
body.className = "inspector-accordion-body";
|
||||||
|
while (section.firstChild) {
|
||||||
|
body.appendChild(section.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = "inspector-accordion-toggle";
|
||||||
|
button.setAttribute("aria-expanded", "false");
|
||||||
|
const count = section.querySelectorAll(".asset-event-item, .thread-event-item, .warning-mini-card, .dependency-card, .metric-slider").length;
|
||||||
|
button.innerHTML = `<span>${title}</span><span class="accordion-count">${count || ""}</span>`;
|
||||||
|
|
||||||
|
section.append(button, body);
|
||||||
|
|
||||||
|
const stored = localStorage.getItem(`${storagePrefix}${storageKey}`);
|
||||||
|
const isOpen = stored === null ? defaultOpen.has(storageKey) : stored === "open";
|
||||||
|
section.classList.toggle("is-open", isOpen);
|
||||||
|
button.setAttribute("aria-expanded", String(isOpen));
|
||||||
|
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const nextOpen = !section.classList.contains("is-open");
|
||||||
|
section.classList.toggle("is-open", nextOpen);
|
||||||
|
button.setAttribute("aria-expanded", String(nextOpen));
|
||||||
|
localStorage.setItem(`${storagePrefix}${storageKey}`, nextOpen ? "open" : "closed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
root.querySelectorAll(".inspector-nav-links a").forEach((link) => {
|
||||||
|
link.addEventListener("click", (event) => {
|
||||||
|
const target = root.querySelector(link.getAttribute("href"));
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
target.classList.add("is-open");
|
||||||
|
target.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded", "true");
|
||||||
|
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const search = root.querySelector("[data-inspector-search]");
|
||||||
|
search?.addEventListener("input", () => {
|
||||||
|
const query = search.value.trim().toLowerCase();
|
||||||
|
sections.forEach((section) => {
|
||||||
|
const matches = !query || section.dataset.searchText.includes(query);
|
||||||
|
section.classList.toggle("is-filtered-out", !matches);
|
||||||
|
if (matches && query) {
|
||||||
|
section.classList.add("is-open");
|
||||||
|
section.querySelector(".inspector-accordion-toggle")?.setAttribute("aria-expanded", "true");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("IntersectionObserver" in window) {
|
||||||
|
const navLinks = [...root.querySelectorAll(".inspector-nav-links a")];
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (!entry.isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navLinks.forEach((link) => {
|
||||||
|
link.classList.toggle("active", link.getAttribute("href") === `#${entry.target.id}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, { root: root.closest(".scene-inspector-panel") || null, rootMargin: "-10% 0px -70% 0px" });
|
||||||
|
|
||||||
|
sections.forEach((section) => observer.observe(section));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const chartRoots = document.querySelectorAll("[data-metric-shape-chart]");
|
||||||
|
if (!chartRoots.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colours = [
|
||||||
|
"#2f6f63",
|
||||||
|
"#b35f3b",
|
||||||
|
"#6750a4",
|
||||||
|
"#c58b2b",
|
||||||
|
"#7d3f52",
|
||||||
|
"#33759b",
|
||||||
|
"#9c4d8f",
|
||||||
|
"#a33d3d",
|
||||||
|
"#6f8f3d",
|
||||||
|
"#d08a2f",
|
||||||
|
"#444b57"
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultMetrics = new Set(["Overall Intensity", "Tension", "Emotional Weight", "Action"]);
|
||||||
|
|
||||||
|
const chapterGuidePlugin = {
|
||||||
|
id: "plotlineChapterGuides",
|
||||||
|
afterDraw(chart) {
|
||||||
|
const scenes = chart.config.options.plugins.plotlineScenes || [];
|
||||||
|
const { ctx, chartArea, scales } = chart;
|
||||||
|
if (!scenes.length || !scales.x) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.strokeStyle = "rgba(47, 111, 99, 0.16)";
|
||||||
|
ctx.fillStyle = "rgba(82, 75, 65, 0.58)";
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.font = "700 10px system-ui, -apple-system, Segoe UI, sans-serif";
|
||||||
|
|
||||||
|
let previousChapter = null;
|
||||||
|
scenes.forEach((scene, index) => {
|
||||||
|
if (scene.chapterLabel === previousChapter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = scales.x.getPixelForValue(index);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, chartArea.top);
|
||||||
|
ctx.lineTo(x, chartArea.bottom);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillText(scene.chapterLabel, Math.min(x + 4, chartArea.right - 70), chartArea.bottom + 22);
|
||||||
|
previousChapter = scene.chapterLabel;
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window.Chart) {
|
||||||
|
Chart.register(chapterGuidePlugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
chartRoots.forEach((root) => {
|
||||||
|
const payloadScript = root.querySelector("script[type='application/json']");
|
||||||
|
const canvas = root.querySelector("canvas");
|
||||||
|
const toggleHost = root.querySelector("[data-metric-toggles]");
|
||||||
|
const fallback = root.querySelector("[data-chart-fallback]");
|
||||||
|
if (!payloadScript || !canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload;
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(payloadScript.textContent || "{}");
|
||||||
|
} catch {
|
||||||
|
fallback?.classList.remove("d-none");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.Chart || !payload.scenes?.length || !payload.metrics?.length) {
|
||||||
|
fallback?.classList.remove("d-none");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = payload.scenes.map((scene) => scene.axisLabel);
|
||||||
|
const sceneIndexById = new Map(payload.scenes.map((scene, index) => [scene.sceneId, index]));
|
||||||
|
const datasets = payload.metrics.map((metric, metricIndex) => {
|
||||||
|
const pointMap = new Map(metric.points.map((point) => [point.sceneId, point.value]));
|
||||||
|
const colour = colours[metricIndex % colours.length];
|
||||||
|
return {
|
||||||
|
label: metric.metricName,
|
||||||
|
data: payload.scenes.map((scene) => pointMap.get(scene.sceneId) ?? null),
|
||||||
|
borderColor: colour,
|
||||||
|
backgroundColor: colour,
|
||||||
|
pointBackgroundColor: colour,
|
||||||
|
pointBorderColor: "#fff",
|
||||||
|
pointBorderWidth: 1.5,
|
||||||
|
pointRadius: 3,
|
||||||
|
pointHoverRadius: 5,
|
||||||
|
borderWidth: 2,
|
||||||
|
tension: 0.35,
|
||||||
|
spanGaps: true,
|
||||||
|
hidden: !defaultMetrics.has(metric.metricName)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleHost.innerHTML = "";
|
||||||
|
datasets.forEach((dataset, index) => {
|
||||||
|
const id = `metric-toggle-${Math.random().toString(36).slice(2)}-${index}`;
|
||||||
|
const label = document.createElement("label");
|
||||||
|
label.className = "metric-toggle-pill";
|
||||||
|
label.style.setProperty("--metric-colour", dataset.borderColor);
|
||||||
|
label.innerHTML = `<input type="checkbox" id="${id}" ${dataset.hidden ? "" : "checked"}><span>${dataset.label}</span>`;
|
||||||
|
toggleHost.appendChild(label);
|
||||||
|
label.querySelector("input").addEventListener("change", (event) => {
|
||||||
|
chart.setDatasetVisibility(index, event.target.checked);
|
||||||
|
chart.update();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const chart = new Chart(canvas, {
|
||||||
|
type: "line",
|
||||||
|
data: { labels, datasets },
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
interaction: { mode: "nearest", intersect: false },
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
ticks: { stepSize: 1 },
|
||||||
|
grid: { color: "rgba(82, 75, 65, 0.10)" }
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
maxRotation: 0,
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 16
|
||||||
|
},
|
||||||
|
grid: { display: false }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
plotlineScenes: payload.scenes,
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
title(items) {
|
||||||
|
const scene = payload.scenes[items[0].dataIndex];
|
||||||
|
return `${scene.chapterLabel}, Scene ${scene.sceneNumber}: ${scene.sceneTitle}`;
|
||||||
|
},
|
||||||
|
label(item) {
|
||||||
|
return `${item.dataset.label}: ${item.formattedValue}`;
|
||||||
|
},
|
||||||
|
afterBody(items) {
|
||||||
|
const scene = payload.scenes[items[0].dataIndex];
|
||||||
|
return scene.bookTitle ? scene.bookTitle : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener("click", (event) => {
|
||||||
|
const points = chart.getElementsAtEventForMode(event, "nearest", { intersect: true }, true);
|
||||||
|
if (!points.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene = payload.scenes[points[0].index];
|
||||||
|
if (scene?.timelineUrl) {
|
||||||
|
window.location.href = scene.timelineUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
22
PlotLine/wwwroot/lib/bootstrap/LICENSE
Normal file
22
PlotLine/wwwroot/lib/bootstrap/LICENSE
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2011-2021 Twitter, Inc.
|
||||||
|
Copyright (c) 2011-2021 The Bootstrap Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
4085
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css
vendored
Normal file
4085
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map
vendored
Normal file
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css
vendored
Normal file
6
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map
vendored
Normal file
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4084
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css
vendored
Normal file
4084
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map
vendored
Normal file
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css
vendored
Normal file
6
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map
vendored
Normal file
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
597
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css
vendored
Normal file
597
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css
vendored
Normal file
@ -0,0 +1,597 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2024 The Bootstrap Authors
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/
|
||||||
|
:root,
|
||||||
|
[data-bs-theme=light] {
|
||||||
|
--bs-blue: #0d6efd;
|
||||||
|
--bs-indigo: #6610f2;
|
||||||
|
--bs-purple: #6f42c1;
|
||||||
|
--bs-pink: #d63384;
|
||||||
|
--bs-red: #dc3545;
|
||||||
|
--bs-orange: #fd7e14;
|
||||||
|
--bs-yellow: #ffc107;
|
||||||
|
--bs-green: #198754;
|
||||||
|
--bs-teal: #20c997;
|
||||||
|
--bs-cyan: #0dcaf0;
|
||||||
|
--bs-black: #000;
|
||||||
|
--bs-white: #fff;
|
||||||
|
--bs-gray: #6c757d;
|
||||||
|
--bs-gray-dark: #343a40;
|
||||||
|
--bs-gray-100: #f8f9fa;
|
||||||
|
--bs-gray-200: #e9ecef;
|
||||||
|
--bs-gray-300: #dee2e6;
|
||||||
|
--bs-gray-400: #ced4da;
|
||||||
|
--bs-gray-500: #adb5bd;
|
||||||
|
--bs-gray-600: #6c757d;
|
||||||
|
--bs-gray-700: #495057;
|
||||||
|
--bs-gray-800: #343a40;
|
||||||
|
--bs-gray-900: #212529;
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-secondary: #6c757d;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-info: #0dcaf0;
|
||||||
|
--bs-warning: #ffc107;
|
||||||
|
--bs-danger: #dc3545;
|
||||||
|
--bs-light: #f8f9fa;
|
||||||
|
--bs-dark: #212529;
|
||||||
|
--bs-primary-rgb: 13, 110, 253;
|
||||||
|
--bs-secondary-rgb: 108, 117, 125;
|
||||||
|
--bs-success-rgb: 25, 135, 84;
|
||||||
|
--bs-info-rgb: 13, 202, 240;
|
||||||
|
--bs-warning-rgb: 255, 193, 7;
|
||||||
|
--bs-danger-rgb: 220, 53, 69;
|
||||||
|
--bs-light-rgb: 248, 249, 250;
|
||||||
|
--bs-dark-rgb: 33, 37, 41;
|
||||||
|
--bs-primary-text-emphasis: #052c65;
|
||||||
|
--bs-secondary-text-emphasis: #2b2f32;
|
||||||
|
--bs-success-text-emphasis: #0a3622;
|
||||||
|
--bs-info-text-emphasis: #055160;
|
||||||
|
--bs-warning-text-emphasis: #664d03;
|
||||||
|
--bs-danger-text-emphasis: #58151c;
|
||||||
|
--bs-light-text-emphasis: #495057;
|
||||||
|
--bs-dark-text-emphasis: #495057;
|
||||||
|
--bs-primary-bg-subtle: #cfe2ff;
|
||||||
|
--bs-secondary-bg-subtle: #e2e3e5;
|
||||||
|
--bs-success-bg-subtle: #d1e7dd;
|
||||||
|
--bs-info-bg-subtle: #cff4fc;
|
||||||
|
--bs-warning-bg-subtle: #fff3cd;
|
||||||
|
--bs-danger-bg-subtle: #f8d7da;
|
||||||
|
--bs-light-bg-subtle: #fcfcfd;
|
||||||
|
--bs-dark-bg-subtle: #ced4da;
|
||||||
|
--bs-primary-border-subtle: #9ec5fe;
|
||||||
|
--bs-secondary-border-subtle: #c4c8cb;
|
||||||
|
--bs-success-border-subtle: #a3cfbb;
|
||||||
|
--bs-info-border-subtle: #9eeaf9;
|
||||||
|
--bs-warning-border-subtle: #ffe69c;
|
||||||
|
--bs-danger-border-subtle: #f1aeb5;
|
||||||
|
--bs-light-border-subtle: #e9ecef;
|
||||||
|
--bs-dark-border-subtle: #adb5bd;
|
||||||
|
--bs-white-rgb: 255, 255, 255;
|
||||||
|
--bs-black-rgb: 0, 0, 0;
|
||||||
|
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
|
--bs-body-font-size: 1rem;
|
||||||
|
--bs-body-font-weight: 400;
|
||||||
|
--bs-body-line-height: 1.5;
|
||||||
|
--bs-body-color: #212529;
|
||||||
|
--bs-body-color-rgb: 33, 37, 41;
|
||||||
|
--bs-body-bg: #fff;
|
||||||
|
--bs-body-bg-rgb: 255, 255, 255;
|
||||||
|
--bs-emphasis-color: #000;
|
||||||
|
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||||
|
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||||
|
--bs-secondary-color-rgb: 33, 37, 41;
|
||||||
|
--bs-secondary-bg: #e9ecef;
|
||||||
|
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||||
|
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||||
|
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||||
|
--bs-tertiary-bg: #f8f9fa;
|
||||||
|
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||||
|
--bs-heading-color: inherit;
|
||||||
|
--bs-link-color: #0d6efd;
|
||||||
|
--bs-link-color-rgb: 13, 110, 253;
|
||||||
|
--bs-link-decoration: underline;
|
||||||
|
--bs-link-hover-color: #0a58ca;
|
||||||
|
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||||
|
--bs-code-color: #d63384;
|
||||||
|
--bs-highlight-color: #212529;
|
||||||
|
--bs-highlight-bg: #fff3cd;
|
||||||
|
--bs-border-width: 1px;
|
||||||
|
--bs-border-style: solid;
|
||||||
|
--bs-border-color: #dee2e6;
|
||||||
|
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||||
|
--bs-border-radius: 0.375rem;
|
||||||
|
--bs-border-radius-sm: 0.25rem;
|
||||||
|
--bs-border-radius-lg: 0.5rem;
|
||||||
|
--bs-border-radius-xl: 1rem;
|
||||||
|
--bs-border-radius-xxl: 2rem;
|
||||||
|
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||||
|
--bs-border-radius-pill: 50rem;
|
||||||
|
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||||
|
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||||
|
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||||
|
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||||
|
--bs-focus-ring-width: 0.25rem;
|
||||||
|
--bs-focus-ring-opacity: 0.25;
|
||||||
|
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||||
|
--bs-form-valid-color: #198754;
|
||||||
|
--bs-form-valid-border-color: #198754;
|
||||||
|
--bs-form-invalid-color: #dc3545;
|
||||||
|
--bs-form-invalid-border-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme=dark] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bs-body-color: #dee2e6;
|
||||||
|
--bs-body-color-rgb: 222, 226, 230;
|
||||||
|
--bs-body-bg: #212529;
|
||||||
|
--bs-body-bg-rgb: 33, 37, 41;
|
||||||
|
--bs-emphasis-color: #fff;
|
||||||
|
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||||
|
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||||
|
--bs-secondary-color-rgb: 222, 226, 230;
|
||||||
|
--bs-secondary-bg: #343a40;
|
||||||
|
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||||
|
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||||
|
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||||
|
--bs-tertiary-bg: #2b3035;
|
||||||
|
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||||
|
--bs-primary-text-emphasis: #6ea8fe;
|
||||||
|
--bs-secondary-text-emphasis: #a7acb1;
|
||||||
|
--bs-success-text-emphasis: #75b798;
|
||||||
|
--bs-info-text-emphasis: #6edff6;
|
||||||
|
--bs-warning-text-emphasis: #ffda6a;
|
||||||
|
--bs-danger-text-emphasis: #ea868f;
|
||||||
|
--bs-light-text-emphasis: #f8f9fa;
|
||||||
|
--bs-dark-text-emphasis: #dee2e6;
|
||||||
|
--bs-primary-bg-subtle: #031633;
|
||||||
|
--bs-secondary-bg-subtle: #161719;
|
||||||
|
--bs-success-bg-subtle: #051b11;
|
||||||
|
--bs-info-bg-subtle: #032830;
|
||||||
|
--bs-warning-bg-subtle: #332701;
|
||||||
|
--bs-danger-bg-subtle: #2c0b0e;
|
||||||
|
--bs-light-bg-subtle: #343a40;
|
||||||
|
--bs-dark-bg-subtle: #1a1d20;
|
||||||
|
--bs-primary-border-subtle: #084298;
|
||||||
|
--bs-secondary-border-subtle: #41464b;
|
||||||
|
--bs-success-border-subtle: #0f5132;
|
||||||
|
--bs-info-border-subtle: #087990;
|
||||||
|
--bs-warning-border-subtle: #997404;
|
||||||
|
--bs-danger-border-subtle: #842029;
|
||||||
|
--bs-light-border-subtle: #495057;
|
||||||
|
--bs-dark-border-subtle: #343a40;
|
||||||
|
--bs-heading-color: inherit;
|
||||||
|
--bs-link-color: #6ea8fe;
|
||||||
|
--bs-link-hover-color: #8bb9fe;
|
||||||
|
--bs-link-color-rgb: 110, 168, 254;
|
||||||
|
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||||
|
--bs-code-color: #e685b5;
|
||||||
|
--bs-highlight-color: #dee2e6;
|
||||||
|
--bs-highlight-bg: #664d03;
|
||||||
|
--bs-border-color: #495057;
|
||||||
|
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||||
|
--bs-form-valid-color: #75b798;
|
||||||
|
--bs-form-valid-border-color: #75b798;
|
||||||
|
--bs-form-invalid-color: #ea868f;
|
||||||
|
--bs-form-invalid-border-color: #ea868f;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--bs-body-font-family);
|
||||||
|
font-size: var(--bs-body-font-size);
|
||||||
|
font-weight: var(--bs-body-font-weight);
|
||||||
|
line-height: var(--bs-body-line-height);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
text-align: var(--bs-body-text-align);
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
border: 0;
|
||||||
|
border-top: var(--bs-border-width) solid;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.1875em;
|
||||||
|
color: var(--bs-highlight-color);
|
||||||
|
background-color: var(--bs-highlight-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-code-color);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.1875rem 0.375rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-body-bg);
|
||||||
|
background-color: var(--bs-body-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rtl:raw:
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||||
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map
vendored
Normal file
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css
vendored
Normal file
6
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map
vendored
Normal file
1
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
594
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css
vendored
Normal file
594
PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css
vendored
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2024 The Bootstrap Authors
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/
|
||||||
|
:root,
|
||||||
|
[data-bs-theme=light] {
|
||||||
|
--bs-blue: #0d6efd;
|
||||||
|
--bs-indigo: #6610f2;
|
||||||
|
--bs-purple: #6f42c1;
|
||||||
|
--bs-pink: #d63384;
|
||||||
|
--bs-red: #dc3545;
|
||||||
|
--bs-orange: #fd7e14;
|
||||||
|
--bs-yellow: #ffc107;
|
||||||
|
--bs-green: #198754;
|
||||||
|
--bs-teal: #20c997;
|
||||||
|
--bs-cyan: #0dcaf0;
|
||||||
|
--bs-black: #000;
|
||||||
|
--bs-white: #fff;
|
||||||
|
--bs-gray: #6c757d;
|
||||||
|
--bs-gray-dark: #343a40;
|
||||||
|
--bs-gray-100: #f8f9fa;
|
||||||
|
--bs-gray-200: #e9ecef;
|
||||||
|
--bs-gray-300: #dee2e6;
|
||||||
|
--bs-gray-400: #ced4da;
|
||||||
|
--bs-gray-500: #adb5bd;
|
||||||
|
--bs-gray-600: #6c757d;
|
||||||
|
--bs-gray-700: #495057;
|
||||||
|
--bs-gray-800: #343a40;
|
||||||
|
--bs-gray-900: #212529;
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-secondary: #6c757d;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-info: #0dcaf0;
|
||||||
|
--bs-warning: #ffc107;
|
||||||
|
--bs-danger: #dc3545;
|
||||||
|
--bs-light: #f8f9fa;
|
||||||
|
--bs-dark: #212529;
|
||||||
|
--bs-primary-rgb: 13, 110, 253;
|
||||||
|
--bs-secondary-rgb: 108, 117, 125;
|
||||||
|
--bs-success-rgb: 25, 135, 84;
|
||||||
|
--bs-info-rgb: 13, 202, 240;
|
||||||
|
--bs-warning-rgb: 255, 193, 7;
|
||||||
|
--bs-danger-rgb: 220, 53, 69;
|
||||||
|
--bs-light-rgb: 248, 249, 250;
|
||||||
|
--bs-dark-rgb: 33, 37, 41;
|
||||||
|
--bs-primary-text-emphasis: #052c65;
|
||||||
|
--bs-secondary-text-emphasis: #2b2f32;
|
||||||
|
--bs-success-text-emphasis: #0a3622;
|
||||||
|
--bs-info-text-emphasis: #055160;
|
||||||
|
--bs-warning-text-emphasis: #664d03;
|
||||||
|
--bs-danger-text-emphasis: #58151c;
|
||||||
|
--bs-light-text-emphasis: #495057;
|
||||||
|
--bs-dark-text-emphasis: #495057;
|
||||||
|
--bs-primary-bg-subtle: #cfe2ff;
|
||||||
|
--bs-secondary-bg-subtle: #e2e3e5;
|
||||||
|
--bs-success-bg-subtle: #d1e7dd;
|
||||||
|
--bs-info-bg-subtle: #cff4fc;
|
||||||
|
--bs-warning-bg-subtle: #fff3cd;
|
||||||
|
--bs-danger-bg-subtle: #f8d7da;
|
||||||
|
--bs-light-bg-subtle: #fcfcfd;
|
||||||
|
--bs-dark-bg-subtle: #ced4da;
|
||||||
|
--bs-primary-border-subtle: #9ec5fe;
|
||||||
|
--bs-secondary-border-subtle: #c4c8cb;
|
||||||
|
--bs-success-border-subtle: #a3cfbb;
|
||||||
|
--bs-info-border-subtle: #9eeaf9;
|
||||||
|
--bs-warning-border-subtle: #ffe69c;
|
||||||
|
--bs-danger-border-subtle: #f1aeb5;
|
||||||
|
--bs-light-border-subtle: #e9ecef;
|
||||||
|
--bs-dark-border-subtle: #adb5bd;
|
||||||
|
--bs-white-rgb: 255, 255, 255;
|
||||||
|
--bs-black-rgb: 0, 0, 0;
|
||||||
|
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
|
--bs-body-font-size: 1rem;
|
||||||
|
--bs-body-font-weight: 400;
|
||||||
|
--bs-body-line-height: 1.5;
|
||||||
|
--bs-body-color: #212529;
|
||||||
|
--bs-body-color-rgb: 33, 37, 41;
|
||||||
|
--bs-body-bg: #fff;
|
||||||
|
--bs-body-bg-rgb: 255, 255, 255;
|
||||||
|
--bs-emphasis-color: #000;
|
||||||
|
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||||
|
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||||
|
--bs-secondary-color-rgb: 33, 37, 41;
|
||||||
|
--bs-secondary-bg: #e9ecef;
|
||||||
|
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||||
|
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||||
|
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||||
|
--bs-tertiary-bg: #f8f9fa;
|
||||||
|
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||||
|
--bs-heading-color: inherit;
|
||||||
|
--bs-link-color: #0d6efd;
|
||||||
|
--bs-link-color-rgb: 13, 110, 253;
|
||||||
|
--bs-link-decoration: underline;
|
||||||
|
--bs-link-hover-color: #0a58ca;
|
||||||
|
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||||
|
--bs-code-color: #d63384;
|
||||||
|
--bs-highlight-color: #212529;
|
||||||
|
--bs-highlight-bg: #fff3cd;
|
||||||
|
--bs-border-width: 1px;
|
||||||
|
--bs-border-style: solid;
|
||||||
|
--bs-border-color: #dee2e6;
|
||||||
|
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||||
|
--bs-border-radius: 0.375rem;
|
||||||
|
--bs-border-radius-sm: 0.25rem;
|
||||||
|
--bs-border-radius-lg: 0.5rem;
|
||||||
|
--bs-border-radius-xl: 1rem;
|
||||||
|
--bs-border-radius-xxl: 2rem;
|
||||||
|
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||||
|
--bs-border-radius-pill: 50rem;
|
||||||
|
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||||
|
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||||
|
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||||
|
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||||
|
--bs-focus-ring-width: 0.25rem;
|
||||||
|
--bs-focus-ring-opacity: 0.25;
|
||||||
|
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||||
|
--bs-form-valid-color: #198754;
|
||||||
|
--bs-form-valid-border-color: #198754;
|
||||||
|
--bs-form-invalid-color: #dc3545;
|
||||||
|
--bs-form-invalid-border-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme=dark] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bs-body-color: #dee2e6;
|
||||||
|
--bs-body-color-rgb: 222, 226, 230;
|
||||||
|
--bs-body-bg: #212529;
|
||||||
|
--bs-body-bg-rgb: 33, 37, 41;
|
||||||
|
--bs-emphasis-color: #fff;
|
||||||
|
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||||
|
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||||
|
--bs-secondary-color-rgb: 222, 226, 230;
|
||||||
|
--bs-secondary-bg: #343a40;
|
||||||
|
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||||
|
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||||
|
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||||
|
--bs-tertiary-bg: #2b3035;
|
||||||
|
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||||
|
--bs-primary-text-emphasis: #6ea8fe;
|
||||||
|
--bs-secondary-text-emphasis: #a7acb1;
|
||||||
|
--bs-success-text-emphasis: #75b798;
|
||||||
|
--bs-info-text-emphasis: #6edff6;
|
||||||
|
--bs-warning-text-emphasis: #ffda6a;
|
||||||
|
--bs-danger-text-emphasis: #ea868f;
|
||||||
|
--bs-light-text-emphasis: #f8f9fa;
|
||||||
|
--bs-dark-text-emphasis: #dee2e6;
|
||||||
|
--bs-primary-bg-subtle: #031633;
|
||||||
|
--bs-secondary-bg-subtle: #161719;
|
||||||
|
--bs-success-bg-subtle: #051b11;
|
||||||
|
--bs-info-bg-subtle: #032830;
|
||||||
|
--bs-warning-bg-subtle: #332701;
|
||||||
|
--bs-danger-bg-subtle: #2c0b0e;
|
||||||
|
--bs-light-bg-subtle: #343a40;
|
||||||
|
--bs-dark-bg-subtle: #1a1d20;
|
||||||
|
--bs-primary-border-subtle: #084298;
|
||||||
|
--bs-secondary-border-subtle: #41464b;
|
||||||
|
--bs-success-border-subtle: #0f5132;
|
||||||
|
--bs-info-border-subtle: #087990;
|
||||||
|
--bs-warning-border-subtle: #997404;
|
||||||
|
--bs-danger-border-subtle: #842029;
|
||||||
|
--bs-light-border-subtle: #495057;
|
||||||
|
--bs-dark-border-subtle: #343a40;
|
||||||
|
--bs-heading-color: inherit;
|
||||||
|
--bs-link-color: #6ea8fe;
|
||||||
|
--bs-link-hover-color: #8bb9fe;
|
||||||
|
--bs-link-color-rgb: 110, 168, 254;
|
||||||
|
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||||
|
--bs-code-color: #e685b5;
|
||||||
|
--bs-highlight-color: #dee2e6;
|
||||||
|
--bs-highlight-bg: #664d03;
|
||||||
|
--bs-border-color: #495057;
|
||||||
|
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||||
|
--bs-form-valid-color: #75b798;
|
||||||
|
--bs-form-valid-border-color: #75b798;
|
||||||
|
--bs-form-invalid-color: #ea868f;
|
||||||
|
--bs-form-invalid-border-color: #ea868f;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--bs-body-font-family);
|
||||||
|
font-size: var(--bs-body-font-size);
|
||||||
|
font-weight: var(--bs-body-font-weight);
|
||||||
|
line-height: var(--bs-body-line-height);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
text-align: var(--bs-body-text-align);
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
border: 0;
|
||||||
|
border-top: var(--bs-border-width) solid;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.1875em;
|
||||||
|
color: var(--bs-highlight-color);
|
||||||
|
background-color: var(--bs-highlight-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-code-color);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.1875rem 0.375rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-body-bg);
|
||||||
|
background-color: var(--bs-body-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: right;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user