diff --git a/data/furniture.json b/data/furniture.json
index 0938506..c65662a 100644
--- a/data/furniture.json
+++ b/data/furniture.json
@@ -166,6 +166,52 @@
"depth_in": 9.5
}
]
+ },
+ {
+ "name": "Library Left Shelving",
+ "description": "To the right of the Kallax",
+ "openings": [
+ {
+ "label": "A1",
+ "zone": "",
+ "width_in": 23.25,
+ "height_in": 10.25,
+ "depth_in": 9.5,
+ "id": "library-right-shelving-2-a1"
+ },
+ {
+ "label": "B1",
+ "zone": "",
+ "width_in": 23.25,
+ "height_in": 10.25,
+ "depth_in": 9.5,
+ "id": "library-right-shelving-2-b1"
+ },
+ {
+ "label": "C1",
+ "zone": "",
+ "width_in": 23.25,
+ "height_in": 10.25,
+ "depth_in": 9.5,
+ "id": "library-right-shelving-2-c1"
+ },
+ {
+ "label": "D1",
+ "zone": "",
+ "width_in": 23.25,
+ "height_in": 10.25,
+ "depth_in": 9.5,
+ "id": "library-right-shelving-2-d1"
+ },
+ {
+ "label": "E1",
+ "zone": "",
+ "width_in": 23.25,
+ "height_in": 10.25,
+ "depth_in": 9.5,
+ "id": "library-right-shelving-2-e1"
+ }
+ ]
}
]
}
diff --git a/src/bggpipe/static/app.css b/src/bggpipe/static/app.css
index 4840225..db88321 100644
--- a/src/bggpipe/static/app.css
+++ b/src/bggpipe/static/app.css
@@ -552,6 +552,9 @@ select {
margin-bottom: .8rem; }
.orow { display: flex; gap: .5rem; }
.formlabel { margin: 1.2rem 0 .35rem; }
+.editform label.attop { flex-direction: row; align-items: center; gap: .35rem;
+ text-transform: none; letter-spacing: normal; align-self: end;
+ padding-bottom: .45rem; }
.renamerow:not([hidden]) { display: flex; gap: .4rem; margin: .3rem 0 .6rem; }
.renamerow input { font: inherit; font-size: .9rem;
border: 2px solid var(--board-edge); border-radius: var(--radius);
diff --git a/src/bggpipe/templates/pages/shelves.html b/src/bggpipe/templates/pages/shelves.html
index 48b7e70..bf14bb9 100644
--- a/src/bggpipe/templates/pages/shelves.html
+++ b/src/bggpipe/templates/pages/shelves.html
@@ -148,6 +148,7 @@ function unitBlock(u) {
+
@@ -325,7 +326,7 @@ function wire() {
name: b.dataset.unit,
new_name: b.closest(".renamerow").querySelector("input").value,
}));
- document.querySelectorAll(".editunitdesc").forEach(b =
+ document.querySelectorAll(".editunitdesc").forEach(b =>
b.onclick = () => {
const form = b.closest(".unitdesc").querySelector(".descform");
form.hidden = !form.hidden;
@@ -359,6 +360,7 @@ function wire() {
try {
await post("/api/furniture/add-openings", {
unit: form.dataset.unit, zone: f.zone.value,
+ at_top: f.at_top.checked,
...(grid ? {rows, cols} : {label: f.label.value}),
width_in: parseFloat(f.width_in.value) || null,
height_in: parseFloat(f.height_in.value) || null,
diff --git a/src/bggpipe/webreview.py b/src/bggpipe/webreview.py
index d0e0504..6d11979 100644
--- a/src/bggpipe/webreview.py
+++ b/src/bggpipe/webreview.py
@@ -237,6 +237,7 @@ class OpeningsBody(BaseModel):
unit: str
zone: str = ""
+ at_top: bool = False # new openings land ABOVE the existing rows
label: str = "" # single mode
rows: int = 0 # grid mode when rows*cols > 0
cols: int = 0
@@ -1307,6 +1308,14 @@ def create_app(
# a half-sized opening would silently become limitless:
# fit checks need all three axes, or none (virtual)
raise HTTPException(400, "give all three interior dimensions, or none")
+ added: list[dict] = []
+
+ def place(new_openings):
+ if body.at_top:
+ unit["openings"][:0] = new_openings
+ else:
+ unit["openings"].extend(new_openings)
+
if body.rows and body.cols:
# a second grid on the same unit continues the row letters
# (A1 already exists -> next section starts at C, say):
@@ -1329,7 +1338,7 @@ def create_app(
break
for c in range(body.cols):
label = f"{letters}{c + 1}"
- unit["openings"].append(
+ added.append(
{
"id": new_opening_id(units, unit["name"], label),
"label": label,
@@ -1341,7 +1350,7 @@ def create_app(
label = body.label.strip()
if not label:
raise HTTPException(400, "the opening needs a label")
- unit["openings"].append(
+ added.append(
{
"id": new_opening_id(units, unit["name"], label),
"label": label,
@@ -1349,6 +1358,7 @@ def create_app(
**dims,
}
)
+ place(added)
return _mutate_furniture(add)
diff --git a/tests/test_shelves.py b/tests/test_shelves.py
index a1c44ff..92a7d41 100644
--- a/tests/test_shelves.py
+++ b/tests/test_shelves.py
@@ -978,3 +978,34 @@ def test_rename_unit_keeps_openings_locations_and_description(tmp_path):
).status_code
== 409
)
+
+
+def test_add_openings_at_top(tmp_path):
+ """Things live ON the furniture too: an opening added at_top lands
+ above the existing rows and renders first in the wall diagram."""
+ web, cfg = _web(tmp_path, {})
+ web.post("/api/furniture/add-unit", json={"name": "Kallax"})
+ web.post(
+ "/api/furniture/add-openings",
+ json={
+ "unit": "Kallax",
+ "rows": 2,
+ "cols": 2,
+ "width_in": 13.25,
+ "height_in": 13.25,
+ "depth_in": 15.4,
+ },
+ )
+ web.post(
+ "/api/furniture/add-openings",
+ json={
+ "unit": "Kallax",
+ "label": "on top",
+ "zone": "display",
+ "at_top": True,
+ },
+ )
+ labels = [
+ o["label"] for o in web.get("/api/shelves").json()["units"][0]["openings"]
+ ]
+ assert labels == ["on top", "A1", "A2", "B1", "B2"]