PlotDirector/PlotLine/Views/Scenes/_SceneOccupancy.cshtml
Nick Beckley cea82a922f Implemented Phase 1 Scene Occupancy.
What Changed
Added scene-owned occupancy data:Scenes.FloorPlanID
Scenes.InitialFloorPlanFloorID
new SceneFloorPlanOccupancy table

Added Occupancy section to the Scene Inspector with AJAX save/refresh.
Added Floor Plan editor Structure / Occupancy mode.
Occupancy mode shows linked scenes, previous/next scene controls, and character/asset tokens inside assigned locations.
Character tokens use image thumbnail, then initials fallback. Asset tokens do the same, smaller.
No Features, no new floor-plan architecture, no transition changes.
Files Changed
[CoreModels.cs](C:/Source/PlotLine/PlotLine/Models/CoreModels.cs)
[CoreViewModels.cs](C:/Source/PlotLine/PlotLine/ViewModels/CoreViewModels.cs)
[Repositories.cs](C:/Source/PlotLine/PlotLine/Data/Repositories.cs)
[CoreServices.cs](C:/Source/PlotLine/PlotLine/Services/CoreServices.cs)
[ScenesController.cs](C:/Source/PlotLine/PlotLine/Controllers/ScenesController.cs)
[Program.cs](C:/Source/PlotLine/PlotLine/Program.cs)
[Edit.cshtml](C:/Source/PlotLine/PlotLine/Views/FloorPlans/Edit.cshtml)
[_SceneInspector.cshtml](C:/Source/PlotLine/PlotLine/Views/Scenes/_SceneInspector.cshtml)
[_SceneOccupancy.cshtml](C:/Source/PlotLine/PlotLine/Views/Scenes/_SceneOccupancy.cshtml)
[site.js](C:/Source/PlotLine/PlotLine/wwwroot/js/site.js)
[site.css](C:/Source/PlotLine/PlotLine/wwwroot/css/site.css)
minified CSS/JS updated.
Database
Added new migration only: [080_SceneFloorPlanOccupancy.sql](C:/Source/PlotLine/PlotLine/Sql/080_SceneFloorPlanOccupancy.sql)
Applied locally.
Added constraints/FKs and filtered unique indexes for one character/asset occupancy row per scene.
Stored Procedures Added/Updated
Scene_Save
Scene_Get
Scene_FloorPlanLink_Save
Scene_ListByFloorPlan
FloorPlanFloor_ListByPlan
FloorPlanBlock_ListByPlan
SceneFloorPlanOccupancy_ListByScene
SceneFloorPlanOccupancy_ListByFloorPlan
SceneFloorPlanOccupancy_Save
Test Data
Permanent Calendar House seed data added:
Book: The Calendar House
Chapters 1-3
14 scenes
Characters: Ellie, Grant, Stephen, Housekeeper, Gardener
Assets: Brass Key, Journal, Candle, Map, Letter
45 occupancy assignments
Verification
dotnet build PlotLine.slnx passes with 0 warnings/errors.
Migration applied locally.
Local app boots and returns HTTP 200.
DB checks confirm 14 linked scenes and 45 occupancy rows.
Manual browser click-through of the authenticated UI still needs a human pass for the AJAX controls and visual token placement.
2026-06-22 10:45:30 +01:00

99 lines
4.7 KiB
Plaintext

@model SceneOccupancyEditorViewModel
@{
var selectedPlanId = Model.FloorPlanID;
var locationOptions = Model.Locations
.Where(x => !selectedPlanId.HasValue || x.FloorPlanID == selectedPlanId.Value)
.OrderBy(x => x.FloorSortOrder)
.ThenBy(x => x.FloorName)
.ThenBy(x => x.LocationPath)
.ToList();
}
<form asp-controller="Scenes" asp-action="SaveOccupancy" method="post" class="scene-occupancy-form" data-scene-occupancy-form data-editor-url="@Url.Action("OccupancyEditor", "Scenes")">
<input type="hidden" name="SceneID" value="@Model.SceneID" />
<div class="scene-occupancy-status" data-scene-occupancy-status>Saved</div>
<div class="row g-2">
<div class="col-sm-6">
<label class="form-label" for="OccupancyFloorPlanID">Floor Plan</label>
<select class="form-select form-select-sm" id="OccupancyFloorPlanID" name="FloorPlanID" data-occupancy-plan-select>
<option value="">No floor plan</option>
@foreach (var option in Model.FloorPlanOptions)
{
<option value="@option.Value" selected="@(option.Value == Model.FloorPlanID?.ToString())">@option.Text</option>
}
</select>
</div>
<div class="col-sm-6">
<label class="form-label" for="OccupancyInitialFloorID">Initial Floor</label>
<select class="form-select form-select-sm" id="OccupancyInitialFloorID" name="InitialFloorPlanFloorID" data-occupancy-floor-select>
<option value="">Use first floor</option>
@foreach (var floor in Model.Floors.Where(x => !selectedPlanId.HasValue || x.FloorPlanID == selectedPlanId.Value))
{
<option value="@floor.FloorPlanFloorID" selected="@(floor.FloorPlanFloorID == Model.InitialFloorPlanFloorID)">@floor.Name</option>
}
</select>
</div>
</div>
@if (!Model.FloorPlanOptions.Any())
{
<p class="muted mt-2 mb-0">No floor plans exist for this project yet.</p>
}
else
{
<div class="scene-occupancy-grid mt-3">
<section>
<h4>Characters</h4>
@if (!Model.Characters.Any())
{
<p class="muted">No characters appear in this scene yet.</p>
}
else
{
@for (var i = 0; i < Model.Characters.Count; i++)
{
<div class="scene-occupancy-row">
<input type="hidden" name="Characters[@i].CharacterID" value="@Model.Characters.ElementAt(i).CharacterID" />
<span>@Model.Characters.ElementAt(i).CharacterName</span>
<select class="form-select form-select-sm" name="Characters[@i].LocationID">
<option value="">Not placed</option>
@foreach (var location in locationOptions)
{
<option value="@location.LocationID" selected="@(location.LocationID == Model.Characters.ElementAt(i).LocationID)">@location.FloorName &gt; @location.LocationPath</option>
}
</select>
</div>
}
}
</section>
<section>
<h4>Assets</h4>
@if (!Model.Assets.Any())
{
<p class="muted">No story assets appear in this scene yet.</p>
}
else
{
@for (var i = 0; i < Model.Assets.Count; i++)
{
<div class="scene-occupancy-row">
<input type="hidden" name="Assets[@i].StoryAssetID" value="@Model.Assets.ElementAt(i).StoryAssetID" />
<span>@Model.Assets.ElementAt(i).AssetName</span>
<select class="form-select form-select-sm" name="Assets[@i].LocationID">
<option value="">Not placed</option>
@foreach (var location in locationOptions)
{
<option value="@location.LocationID" selected="@(location.LocationID == Model.Assets.ElementAt(i).LocationID)">@location.FloorName &gt; @location.LocationPath</option>
}
</select>
</div>
}
}
</section>
</div>
<button class="btn btn-primary btn-sm mt-3" type="submit">Save occupancy</button>
}
</form>