Implemented the floor-level ordering pass using the existing integer SortOrder as the user-facing Level.
This commit is contained in:
parent
11b29b2504
commit
9fc4374df3
@ -7563,6 +7563,7 @@ public sealed class FloorPlanService(
|
||||
}
|
||||
|
||||
var projectLocations = await locations.ListByProjectAsync(project.ProjectID);
|
||||
var orderedFloors = data.Floors.OrderBy(x => x.SortOrder).ThenBy(x => x.Name).ToList();
|
||||
return new FloorPlanEditorViewModel
|
||||
{
|
||||
Project = project,
|
||||
@ -7576,27 +7577,27 @@ public sealed class FloorPlanService(
|
||||
Name = data.FloorPlan.Name,
|
||||
Description = data.FloorPlan.Description
|
||||
},
|
||||
Floors = data.Floors.Select(ToFloorEdit).ToList(),
|
||||
Floors = orderedFloors.Select(ToFloorEdit).ToList(),
|
||||
Blocks = data.Blocks.Select(ToBlockEdit).ToList(),
|
||||
Transitions = data.Transitions.Select(ToTransitionEdit).ToList(),
|
||||
NewFloor = new FloorPlanFloorEditViewModel
|
||||
{
|
||||
FloorPlanID = data.FloorPlan.FloorPlanID,
|
||||
Name = "New floor",
|
||||
SortOrder = data.Floors.Any() ? data.Floors.Max(x => x.SortOrder) + 10 : 10,
|
||||
SortOrder = orderedFloors.Any() ? orderedFloors.Max(x => x.SortOrder) + 1 : 0,
|
||||
GridWidth = 24,
|
||||
GridHeight = 16
|
||||
},
|
||||
NewBlock = new FloorPlanBlockEditViewModel
|
||||
{
|
||||
FloorPlanFloorID = data.Floors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
||||
FloorPlanFloorID = orderedFloors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
||||
BlockType = FloorPlanBlockTypes.Room,
|
||||
WidthCells = 2,
|
||||
HeightCells = 1
|
||||
},
|
||||
NewTransition = new FloorPlanTransitionEditViewModel
|
||||
{
|
||||
FloorPlanFloorID = data.Floors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
||||
FloorPlanFloorID = orderedFloors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
||||
TransitionType = FloorPlanTransitionTypes.Door
|
||||
},
|
||||
LocationOptions = ToOptionalLocationOptions(projectLocations, "Create/link automatically"),
|
||||
@ -7627,7 +7628,7 @@ public sealed class FloorPlanService(
|
||||
FloorPlanID = floorPlanId,
|
||||
LocationID = floorLocationId,
|
||||
Name = "Ground Floor",
|
||||
SortOrder = 10,
|
||||
SortOrder = 0,
|
||||
GridWidth = 24,
|
||||
GridHeight = 16
|
||||
});
|
||||
@ -8005,6 +8006,11 @@ public sealed class FloorPlanService(
|
||||
|
||||
private static void ValidateFloor(FloorPlanFloorEditViewModel model)
|
||||
{
|
||||
if (model.SortOrder is < -10 or > 50)
|
||||
{
|
||||
throw new InvalidOperationException("Floor level must be between -10 and 50.");
|
||||
}
|
||||
|
||||
if (model.GridWidth is < 8 or > 80 || model.GridHeight is < 8 or > 80)
|
||||
{
|
||||
throw new InvalidOperationException("Grid width and height must be between 8 and 80.");
|
||||
|
||||
31
PlotLine/Sql/076_FloorPlanFloorLevels.sql
Normal file
31
PlotLine/Sql/076_FloorPlanFloorLevels.sql
Normal file
@ -0,0 +1,31 @@
|
||||
-- Reinterpret the existing FloorPlanFloors.SortOrder integer as user-facing floor Level.
|
||||
-- This preserves current relative order for legacy 10-step SortOrder data without adding schema.
|
||||
|
||||
IF OBJECT_ID(N'dbo.FloorPlanFloors', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
;WITH LegacyPlans AS
|
||||
(
|
||||
SELECT FloorPlanID
|
||||
FROM dbo.FloorPlanFloors
|
||||
GROUP BY FloorPlanID
|
||||
HAVING COUNT(*) > 0
|
||||
AND MIN(SortOrder) >= 10
|
||||
AND SUM(CASE WHEN SortOrder % 10 = 0 THEN 0 ELSE 1 END) = 0
|
||||
),
|
||||
OrderedFloors AS
|
||||
(
|
||||
SELECT
|
||||
fpf.FloorPlanFloorID,
|
||||
ROW_NUMBER() OVER (PARTITION BY fpf.FloorPlanID ORDER BY fpf.SortOrder, fpf.Name, fpf.FloorPlanFloorID) - 1 AS FloorLevel
|
||||
FROM dbo.FloorPlanFloors fpf
|
||||
INNER JOIN LegacyPlans lp ON lp.FloorPlanID = fpf.FloorPlanID
|
||||
)
|
||||
UPDATE fpf
|
||||
SET
|
||||
SortOrder = ordered.FloorLevel,
|
||||
ModifiedDate = SYSUTCDATETIME()
|
||||
FROM dbo.FloorPlanFloors fpf
|
||||
INNER JOIN OrderedFloors ordered ON ordered.FloorPlanFloorID = fpf.FloorPlanFloorID
|
||||
WHERE fpf.SortOrder <> ordered.FloorLevel;
|
||||
END;
|
||||
GO
|
||||
@ -991,6 +991,8 @@ public sealed class TimelineMetricSettingOptionViewModel
|
||||
public int MetricTypeID { get; set; }
|
||||
public string MetricName { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
[Display(Name = "Level")]
|
||||
[Range(-10, 50)]
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsSelected { get; set; }
|
||||
}
|
||||
|
||||
@ -113,12 +113,15 @@
|
||||
<div class="floor-plan-tool-card mt-3">
|
||||
<h3>Add floor</h3>
|
||||
<input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
|
||||
<input asp-for="NewFloor.SortOrder" type="hidden" name="SortOrder" form="floor-plan-add-floor-form" />
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">New floor</label>
|
||||
<input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Level</label>
|
||||
<input asp-for="NewFloor.SortOrder" class="form-control form-control-sm" name="SortOrder" min="-10" max="50" step="1" form="floor-plan-add-floor-form" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Width</label>
|
||||
<input asp-for="NewFloor.GridWidth" class="form-control form-control-sm" name="GridWidth" min="8" max="80" form="floor-plan-add-floor-form" />
|
||||
@ -131,6 +134,7 @@
|
||||
<button class="btn btn-outline-primary btn-sm w-100" type="submit" form="floor-plan-add-floor-form">Add floor</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="form-text mb-0">Used to order floors and later to connect stairs up/down.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -564,11 +568,15 @@
|
||||
<div class="floor-plan-tool-subsection">
|
||||
<h3>Add floor</h3>
|
||||
<input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
|
||||
<input asp-for="NewFloor.SortOrder" type="hidden" name="SortOrder" form="floor-plan-add-floor-form" />
|
||||
<div class="mb-2">
|
||||
<label class="form-label">New floor</label>
|
||||
<input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Level</label>
|
||||
<input asp-for="NewFloor.SortOrder" class="form-control form-control-sm" name="SortOrder" min="-10" max="50" step="1" form="floor-plan-add-floor-form" />
|
||||
<div class="form-text">Used to order floors and later to connect stairs up/down.</div>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<label class="form-label">Width</label>
|
||||
@ -590,12 +598,16 @@
|
||||
<div class="floor-plan-floor-editor @(isActive ? string.Empty : "d-none")" data-floor-editor="@floor.FloorPlanFloorID">
|
||||
<input asp-for="Floors[i].FloorPlanFloorID" type="hidden" />
|
||||
<input asp-for="Floors[i].FloorPlanID" type="hidden" />
|
||||
<input asp-for="Floors[i].SortOrder" type="hidden" />
|
||||
<input asp-for="Floors[i].LocationID" type="hidden" />
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Floor name</label>
|
||||
<input asp-for="Floors[i].Name" class="form-control form-control-sm" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Level</label>
|
||||
<input asp-for="Floors[i].SortOrder" class="form-control form-control-sm" min="-10" max="50" step="1" data-floor-level="@floor.FloorPlanFloorID" />
|
||||
<div class="form-text">Used to order floors and later to connect stairs up/down.</div>
|
||||
</div>
|
||||
<p class="muted">Location: @(string.IsNullOrWhiteSpace(floor.LocationPath) ? "A child Location will be created or linked on save." : floor.LocationPath)</p>
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
@ -907,6 +919,7 @@
|
||||
let saveTimer = null;
|
||||
let saveInFlight = false;
|
||||
let saveAgain = false;
|
||||
let refreshAfterSave = false;
|
||||
const backgroundTimers = new Map();
|
||||
|
||||
function setToolboxTab(tabName) {
|
||||
@ -1068,6 +1081,10 @@
|
||||
}
|
||||
|
||||
setSaveStatus("saved");
|
||||
if (refreshAfterSave) {
|
||||
refreshAfterSave = false;
|
||||
await refreshWorkspaceAsync(workspaceState());
|
||||
}
|
||||
} catch (error) {
|
||||
setSaveStatus("failed", error.message || "Save failed");
|
||||
} finally {
|
||||
@ -1831,11 +1848,25 @@
|
||||
});
|
||||
});
|
||||
|
||||
editor.querySelectorAll("[data-floor-level]").forEach(input => {
|
||||
const updateFloorLevel = (clampValue = false) => {
|
||||
const parsed = Number(input.value);
|
||||
if (!Number.isFinite(parsed)) return;
|
||||
if (clampValue && Number.isFinite(parsed)) {
|
||||
input.value = Math.max(-10, Math.min(parsed, 50));
|
||||
}
|
||||
refreshAfterSave = true;
|
||||
markDirty();
|
||||
};
|
||||
input.addEventListener("input", () => updateFloorLevel());
|
||||
input.addEventListener("change", () => updateFloorLevel(true));
|
||||
});
|
||||
|
||||
editor.querySelectorAll("input, select, textarea").forEach(input => {
|
||||
if (input.matches("[form='floor-plan-add-block-form'], [form='floor-plan-add-floor-form']")) return;
|
||||
if (input.matches("[form^='floor-plan-background-'], [form^='floor-plan-transition-add-']")) return;
|
||||
if (input.matches("[data-background-field]")) return;
|
||||
if (input.matches("[data-floor-width],[data-floor-height]")) return;
|
||||
if (input.matches("[data-floor-width],[data-floor-height],[data-floor-level]")) return;
|
||||
if (input.matches("[data-block-field='x'],[data-block-field='y'],[data-block-field='w'],[data-block-field='h'],[data-block-field='type'],[data-block-field='location']")) return;
|
||||
const eventName = input.tagName === "TEXTAREA" || input.type === "text" ? "input" : "change";
|
||||
input.addEventListener(eventName, markDirty);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user