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 projectLocations = await locations.ListByProjectAsync(project.ProjectID);
|
||||||
|
var orderedFloors = data.Floors.OrderBy(x => x.SortOrder).ThenBy(x => x.Name).ToList();
|
||||||
return new FloorPlanEditorViewModel
|
return new FloorPlanEditorViewModel
|
||||||
{
|
{
|
||||||
Project = project,
|
Project = project,
|
||||||
@ -7576,27 +7577,27 @@ public sealed class FloorPlanService(
|
|||||||
Name = data.FloorPlan.Name,
|
Name = data.FloorPlan.Name,
|
||||||
Description = data.FloorPlan.Description
|
Description = data.FloorPlan.Description
|
||||||
},
|
},
|
||||||
Floors = data.Floors.Select(ToFloorEdit).ToList(),
|
Floors = orderedFloors.Select(ToFloorEdit).ToList(),
|
||||||
Blocks = data.Blocks.Select(ToBlockEdit).ToList(),
|
Blocks = data.Blocks.Select(ToBlockEdit).ToList(),
|
||||||
Transitions = data.Transitions.Select(ToTransitionEdit).ToList(),
|
Transitions = data.Transitions.Select(ToTransitionEdit).ToList(),
|
||||||
NewFloor = new FloorPlanFloorEditViewModel
|
NewFloor = new FloorPlanFloorEditViewModel
|
||||||
{
|
{
|
||||||
FloorPlanID = data.FloorPlan.FloorPlanID,
|
FloorPlanID = data.FloorPlan.FloorPlanID,
|
||||||
Name = "New floor",
|
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,
|
GridWidth = 24,
|
||||||
GridHeight = 16
|
GridHeight = 16
|
||||||
},
|
},
|
||||||
NewBlock = new FloorPlanBlockEditViewModel
|
NewBlock = new FloorPlanBlockEditViewModel
|
||||||
{
|
{
|
||||||
FloorPlanFloorID = data.Floors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
FloorPlanFloorID = orderedFloors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
||||||
BlockType = FloorPlanBlockTypes.Room,
|
BlockType = FloorPlanBlockTypes.Room,
|
||||||
WidthCells = 2,
|
WidthCells = 2,
|
||||||
HeightCells = 1
|
HeightCells = 1
|
||||||
},
|
},
|
||||||
NewTransition = new FloorPlanTransitionEditViewModel
|
NewTransition = new FloorPlanTransitionEditViewModel
|
||||||
{
|
{
|
||||||
FloorPlanFloorID = data.Floors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
FloorPlanFloorID = orderedFloors.FirstOrDefault()?.FloorPlanFloorID ?? 0,
|
||||||
TransitionType = FloorPlanTransitionTypes.Door
|
TransitionType = FloorPlanTransitionTypes.Door
|
||||||
},
|
},
|
||||||
LocationOptions = ToOptionalLocationOptions(projectLocations, "Create/link automatically"),
|
LocationOptions = ToOptionalLocationOptions(projectLocations, "Create/link automatically"),
|
||||||
@ -7627,7 +7628,7 @@ public sealed class FloorPlanService(
|
|||||||
FloorPlanID = floorPlanId,
|
FloorPlanID = floorPlanId,
|
||||||
LocationID = floorLocationId,
|
LocationID = floorLocationId,
|
||||||
Name = "Ground Floor",
|
Name = "Ground Floor",
|
||||||
SortOrder = 10,
|
SortOrder = 0,
|
||||||
GridWidth = 24,
|
GridWidth = 24,
|
||||||
GridHeight = 16
|
GridHeight = 16
|
||||||
});
|
});
|
||||||
@ -8005,6 +8006,11 @@ public sealed class FloorPlanService(
|
|||||||
|
|
||||||
private static void ValidateFloor(FloorPlanFloorEditViewModel model)
|
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)
|
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.");
|
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 int MetricTypeID { get; set; }
|
||||||
public string MetricName { get; set; } = string.Empty;
|
public string MetricName { get; set; } = string.Empty;
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
[Display(Name = "Level")]
|
||||||
|
[Range(-10, 50)]
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
public bool IsSelected { get; set; }
|
public bool IsSelected { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,12 +113,15 @@
|
|||||||
<div class="floor-plan-tool-card mt-3">
|
<div class="floor-plan-tool-card mt-3">
|
||||||
<h3>Add floor</h3>
|
<h3>Add floor</h3>
|
||||||
<input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
|
<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="row g-2 align-items-end">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label">New floor</label>
|
<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" />
|
<input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
|
||||||
</div>
|
</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">
|
<div class="col-md-2">
|
||||||
<label class="form-label">Width</label>
|
<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" />
|
<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>
|
<button class="btn btn-outline-primary btn-sm w-100" type="submit" form="floor-plan-add-floor-form">Add floor</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="form-text mb-0">Used to order floors and later to connect stairs up/down.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -564,11 +568,15 @@
|
|||||||
<div class="floor-plan-tool-subsection">
|
<div class="floor-plan-tool-subsection">
|
||||||
<h3>Add floor</h3>
|
<h3>Add floor</h3>
|
||||||
<input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
|
<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">
|
<div class="mb-2">
|
||||||
<label class="form-label">New floor</label>
|
<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" />
|
<input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
|
||||||
</div>
|
</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="row g-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">Width</label>
|
<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">
|
<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].FloorPlanFloorID" type="hidden" />
|
||||||
<input asp-for="Floors[i].FloorPlanID" 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" />
|
<input asp-for="Floors[i].LocationID" type="hidden" />
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Floor name</label>
|
<label class="form-label">Floor name</label>
|
||||||
<input asp-for="Floors[i].Name" class="form-control form-control-sm" />
|
<input asp-for="Floors[i].Name" class="form-control form-control-sm" />
|
||||||
</div>
|
</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>
|
<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="row g-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
@ -907,6 +919,7 @@
|
|||||||
let saveTimer = null;
|
let saveTimer = null;
|
||||||
let saveInFlight = false;
|
let saveInFlight = false;
|
||||||
let saveAgain = false;
|
let saveAgain = false;
|
||||||
|
let refreshAfterSave = false;
|
||||||
const backgroundTimers = new Map();
|
const backgroundTimers = new Map();
|
||||||
|
|
||||||
function setToolboxTab(tabName) {
|
function setToolboxTab(tabName) {
|
||||||
@ -1068,6 +1081,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSaveStatus("saved");
|
setSaveStatus("saved");
|
||||||
|
if (refreshAfterSave) {
|
||||||
|
refreshAfterSave = false;
|
||||||
|
await refreshWorkspaceAsync(workspaceState());
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSaveStatus("failed", error.message || "Save failed");
|
setSaveStatus("failed", error.message || "Save failed");
|
||||||
} finally {
|
} 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 => {
|
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-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("[form^='floor-plan-background-'], [form^='floor-plan-transition-add-']")) return;
|
||||||
if (input.matches("[data-background-field]")) 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;
|
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";
|
const eventName = input.tagName === "TEXTAREA" || input.type === "text" ? "input" : "change";
|
||||||
input.addEventListener(eventName, markDirty);
|
input.addEventListener(eventName, markDirty);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user