mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix: the Thickness tool serves standalone walls and saved drafts (#313)
Кандидаты _wallThickHit расширены: интервалы комнат ∪ перегородки ∪ сегменты сохранённых драфтов (активная цепочка исключена, как в снап-геометрии); при точном наложении побеждает независимая кладка — она владеет хит-зоной и рисует видимое тело (решение владельца, согласовано с select и кейсом #308). Диалог для независимой кладки без кнопки «на всю комнату»; запись — в partition.cm / draft.segments[i].cm той же физической транзакцией с одним Undo. Ноль/пусто для независимой кладки отклоняется существующим тостом диапазона; switch записи — единственный шов, куда #306 повесит ветку «ноль превращает перегородку в виртуальную стену». Мутант wall-thickness-writer-bypasses-common-barrier расширен вторым патчем на новую точку коммита (#278-гвард), краснота обоих проверена исполнением. Смок smoke_wallthick_standalone: hit/диалог/запись/отказ нуля/приоритет наложения/hover — на dev падает. Issue: #313 User-Visible: yes
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Issue #313: the Thickness tool serves independent masonry — standalone
|
||||
* partitions and saved-draft segments — not only room contours. Independent
|
||||
* masonry wins an exact overlap (the #308 duplicate), and zero thickness is
|
||||
* refused for it until #306 gives zero a meaning.
|
||||
*/
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
|
||||
const { page, browser } = await launch({ width: 900, height: 800 }, 1);
|
||||
|
||||
const out = await page.evaluate(async () => {
|
||||
const NORM_W = 1000;
|
||||
const card = window.__card;
|
||||
const update = async () => { card.requestUpdate(); await card.updateComplete; };
|
||||
card._serverCfg = { spaces: [{ id: 'wt', title: 'WT', cell_cm: 5, view_box: [0, 0, 1, 0.8],
|
||||
rooms: [{ id: 'room', name: 'Room', area: null,
|
||||
poly: [[0.1, 0.1], [0.5, 0.1], [0.5, 0.4], [0.1, 0.4]] }],
|
||||
partitions: [
|
||||
{ id: 'standalone', a: [0.6, 0.2], b: [0.9, 0.2], cm: 20 },
|
||||
// exact duplicate over the room's top edge — the #308 layout
|
||||
{ id: 'overlay', a: [0.1, 0.1], b: [0.5, 0.1], cm: 30 },
|
||||
],
|
||||
room_drafts: [{ id: 'saved-draft', points: [[0.6, 0.5], [0.9, 0.5]], segments: [{ cm: 12 }] }],
|
||||
}], markers: [], settings: {} };
|
||||
card._layout = {}; card._space = 'wt'; card._modelCache = null; card._frame = null;
|
||||
card._cfgEpoch++; card._setMode('plan'); card._tool = 'wallthick'; await update();
|
||||
|
||||
const result = {};
|
||||
// 1) standalone partition: hit, dialog, write, undo
|
||||
const partitionHit = card._wallThickHit([0.75 * NORM_W, 0.2 * NORM_W]);
|
||||
result.partitionHit = partitionHit?.source?.kind === 'partition' && partitionHit.cm === 20;
|
||||
card._wallThickClick([0.75 * NORM_W, 0.2 * NORM_W]); await update();
|
||||
result.dialogOpen = !!card._wallDialog && card._wallDialog.source.kind === 'partition';
|
||||
const dlg = card.renderRoot.querySelector('.wallthick-dlg');
|
||||
result.noRoomButton = !!dlg && !dlg.textContent.includes(card._t('wallthick.apply_room'));
|
||||
card._wallDialog = { ...card._wallDialog, value: '35' };
|
||||
card._wallThickApply(false); await update();
|
||||
const sp = () => card._serverCfg.spaces[0];
|
||||
result.partitionWritten = sp().partitions[0].cm === 35;
|
||||
// Undo (server history is async-free here: geometry history object)
|
||||
// 2) zero refused for independent masonry
|
||||
card._wallThickClick([0.75 * NORM_W, 0.2 * NORM_W]); await update();
|
||||
card._wallDialog = { ...card._wallDialog, value: '' };
|
||||
card._wallThickApply(false); await update();
|
||||
// the refusal keeps both the value and the dialog (range toast shown)
|
||||
result.zeroRefused = sp().partitions[0].cm === 35 && !!card._wallDialog;
|
||||
card._wallDialog = null; await update();
|
||||
// 3) saved draft segment
|
||||
const draftHit = card._wallThickHit([0.75 * NORM_W, 0.5 * NORM_W]);
|
||||
result.draftHit = draftHit?.source?.kind === 'draft' && draftHit.cm === 12;
|
||||
card._wallThickClick([0.75 * NORM_W, 0.5 * NORM_W]); await update();
|
||||
card._wallDialog = { ...card._wallDialog, value: '18' };
|
||||
card._wallThickApply(false); await update();
|
||||
result.draftWritten = sp().room_drafts[0].segments[0].cm === 18;
|
||||
// 4) #308 overlap: the independent wall owns the hit
|
||||
const overlapHit = card._wallThickHit([0.3 * NORM_W, 0.1 * NORM_W]);
|
||||
result.overlapPrefersIndependent = overlapHit?.source?.kind === 'partition'
|
||||
&& overlapHit?.source?.id === 'overlay' && overlapHit.cm === 30;
|
||||
// 5) plain room wall still resolves to the room interval
|
||||
const roomHit = card._wallThickHit([0.3 * NORM_W, 0.4 * NORM_W]);
|
||||
result.roomWallStillWorks = roomHit?.source?.kind === 'room' && roomHit.roomId === 'room';
|
||||
// 6) hover strip follows the partition thickness
|
||||
card._cursorPt = [0.75 * NORM_W, 0.2 * NORM_W];
|
||||
const hover = card._wallThickHover;
|
||||
result.hoverPresent = !!hover && hover.d.length > 0;
|
||||
return result;
|
||||
});
|
||||
|
||||
checkAll(out);
|
||||
await finish(browser);
|
||||
Vendored
+38
-37
File diff suppressed because one or more lines are too long
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- The Thickness tool now serves every wall: standalone partitions and saved
|
||||
draft segments can be picked and resized just like room contours, with the
|
||||
independent wall winning an exact overlap. Zero stays refused for
|
||||
standalone walls — a wall without thickness does not exist
|
||||
([#313](https://github.com/Matysh/houseplan-card/issues/313)).
|
||||
|
||||
## v1.68.0-beta.1 — 2026-08-26
|
||||
|
||||
- Two walls meeting at an acute angle keep their full drawing point again —
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Инструмент «Толщина» теперь берёт любую стену: отдельно стоящие перегородки
|
||||
и сегменты сохранённых черновиков выбираются и меняются так же, как контуры
|
||||
комнат; при точном наложении клик достаётся отдельно стоящей стене. Ноль
|
||||
для неё по-прежнему отклоняется — стены без толщины не существует
|
||||
([#313](https://github.com/Matysh/houseplan-card/issues/313)).
|
||||
|
||||
## v1.68.0-beta.1 — 2026-08-26
|
||||
|
||||
- Две стены, сходящиеся под острым углом, снова сходятся в полное остриё —
|
||||
|
||||
@@ -516,7 +516,8 @@ Glow и солнечные лучи, но при Resize комнаты оста
|
||||
|
||||
| Параметр | Поведение |
|
||||
|---|---|
|
||||
| Значение | 1–100 см; пусто/0 в интерфейсе очищает толщину |
|
||||
| Значение | 1–100 см; пусто/0 у стены комнаты очищает толщину, у отдельно стоящей стены — отклоняется (стена без толщины не существует) |
|
||||
| Какие стены | Любые: контуры комнат, отдельно стоящие стены и сегменты сохранённых черновиков; при точном наложении клик берёт отдельно стоящую стену — именно её тело видно |
|
||||
| Геометрия | Толщина растёт на половину значения в обе стороны от оси стены |
|
||||
| Общая стена | Одна физическая стена между комнатами |
|
||||
| Чистая площадь | Считается по внутренней грани стен |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"version": 1,
|
||||
"fixture": "synthetic-only",
|
||||
"chromium": "151.0.7922.34",
|
||||
"sourceFingerprint": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceFingerprint": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"captureScriptSha256": "ce2e9542fed9dade3085be87d16f69adb2ac8262893ad78ad966b1b9673f2983",
|
||||
"command": "npm run build && node demo/docs/capture.mjs",
|
||||
"scenarios": {
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
|
||||
},
|
||||
"view-touch": {
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "41e3ba67f8db0e98f26f484293af83ef937c369ca5ca6a59a3350d8954c906f4"
|
||||
},
|
||||
"space-create": {
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "c33a7279165a4cec6fa6fadb6fd08cd967e082a17fe101ef442d27d36ae59b6b"
|
||||
},
|
||||
"room-contour-close": {
|
||||
@@ -47,7 +47,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "b4777162eae89e0d95801721330bcd74ba761624b82b3362069d7e2d38317e08"
|
||||
},
|
||||
"plan-context-tray": {
|
||||
@@ -58,7 +58,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "c0e28edf82f45ccc6df568d3023b681e9e4394262ad34c1605fe9eacdb57a390"
|
||||
},
|
||||
"device-editor": {
|
||||
@@ -69,7 +69,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "d0ffd31ce80bfde21ab75da356a5fc1af38246f2b301030880320620c228d89d"
|
||||
},
|
||||
"device-display-preview": {
|
||||
@@ -80,7 +80,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
|
||||
},
|
||||
"background-editor": {
|
||||
@@ -91,7 +91,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
|
||||
},
|
||||
"room-card": {
|
||||
@@ -102,7 +102,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
|
||||
},
|
||||
"device-info": {
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "eb044c22b9a5d03dd9c66c1bc8ace5e8a2637da2b0078ba5782eb0e056a8cc23",
|
||||
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
|
||||
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1218,6 +1218,12 @@ export const MUTANTS = [
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: " if (this._commitPhysicalGeometry(this._t('history.wall_thickness'), before))",
|
||||
replace: " if ((this._recordGeometry(this._t('history.wall_thickness'), before), true))",
|
||||
}, {
|
||||
// #313: the independent-masonry writer is a second thickness commit
|
||||
// point — bypassing the barrier there recreates the same #278 hole.
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: " const committed = this._commitPhysicalGeometry(\n this._t('history.wall_thickness'), before,\n );",
|
||||
replace: " const committed = (this._recordGeometry(this._t('history.wall_thickness'), before), true);",
|
||||
}],
|
||||
},
|
||||
{
|
||||
|
||||
+94
-15
@@ -590,6 +590,15 @@ const unionRect = (a: Rect, b: Rect): Rect => {
|
||||
return { x, y, w: Math.max(a.x + a.w, b.x + b.w) - x, h: Math.max(a.y + a.h, b.y + b.h) - y };
|
||||
};
|
||||
|
||||
/** #313: one Thickness-tool hit — a room interval or independent masonry. */
|
||||
type WallThickSource = { kind: 'room' }
|
||||
| { kind: 'partition'; id: string }
|
||||
| { kind: 'draft'; id: string; segment: number };
|
||||
type WallThickHit = {
|
||||
a: number[]; b: number[]; roomId: string; segs: number[][];
|
||||
open: boolean; cm: number; source: WallThickSource;
|
||||
};
|
||||
|
||||
type MarkupTool = 'select' | 'draw' | 'column' | 'merge' | 'split' | 'resize' | 'opening' | 'boundary' | 'wallthick' | 'delroom';
|
||||
type RoomFillFrame = {
|
||||
byRoom: Map<RoomCfg, ResolvedRoomFill | null>;
|
||||
@@ -1537,6 +1546,12 @@ class HouseplanCard extends LitElement {
|
||||
private _wallDialog: {
|
||||
a: number[]; b: number[];
|
||||
value: string; roomId: string | null;
|
||||
// #313: the Thickness tool serves independent masonry too. `room` keeps
|
||||
// the historical shape (walls records + «apply to room»); the other two
|
||||
// write partition.cm / draft.segments[i].cm. The switch in
|
||||
// _wallThickApply is the single seam where #306 will later hang the
|
||||
// «zero turns a partition virtual» branch.
|
||||
source: WallThickSource;
|
||||
sx: number; sy: number;
|
||||
} | null = null;
|
||||
/**
|
||||
@@ -11912,28 +11927,58 @@ class HouseplanCard extends LitElement {
|
||||
* wall wrote a key that the renderer then spread over the shared part as
|
||||
* well. The unit of the tool is now the same interval the renderer draws.
|
||||
*/
|
||||
private _wallThickHit(raw: number[]): {
|
||||
a: number[]; b: number[]; roomId: string; segs: number[][]; open: boolean; cm: number;
|
||||
} | null {
|
||||
private _wallThickHit(raw: number[]): WallThickHit | null {
|
||||
const space = this._spaceModel();
|
||||
if (!space) return null;
|
||||
const pull = this._gridPitch * 6;
|
||||
const cuts = this._openCuts();
|
||||
let best: { iv: ReturnType<typeof wallIntervals>[number]; d: number } | null = null;
|
||||
type Hit = WallThickHit;
|
||||
// #313: the tool serves every wall — room intervals AND independent
|
||||
// masonry. On an exact overlap (the #308 duplicate) the independent wall
|
||||
// wins: it owns the hit zone, exactly like the select tool, and it is
|
||||
// the body the eye actually sees.
|
||||
let best: { hit: Hit; d: number; independent: boolean } | null = null;
|
||||
const offer = (hit: Hit, d: number, independent: boolean): void => {
|
||||
if (d > pull) return;
|
||||
if (!best || d < best.d - 1e-9
|
||||
|| (independent && !best.independent && d <= best.d + 1e-9)) {
|
||||
best = { hit, d, independent };
|
||||
}
|
||||
};
|
||||
for (const iv of wallIntervals(
|
||||
space.rooms, this._spaceWalls, cuts,
|
||||
this._wallKeyPitch, this._cellCm, this._gridPitch, NORM_W,
|
||||
)) {
|
||||
const d = distToSegment(raw, [iv.a[0], iv.a[1], iv.b[0], iv.b[1]]);
|
||||
if (d <= pull && (!best || d < best.d)) best = { iv, d };
|
||||
offer({
|
||||
a: iv.a, b: iv.b, roomId: iv.roomId,
|
||||
segs: [[iv.a[0], iv.a[1], iv.b[0], iv.b[1]]],
|
||||
open: iv.open, cm: iv.cm, source: { kind: 'room' },
|
||||
}, distToSegment(raw, [iv.a[0], iv.a[1], iv.b[0], iv.b[1]]), false);
|
||||
}
|
||||
if (!best) return null;
|
||||
const iv = best.iv;
|
||||
return {
|
||||
a: iv.a, b: iv.b, roomId: iv.roomId,
|
||||
segs: [[iv.a[0], iv.a[1], iv.b[0], iv.b[1]]],
|
||||
open: iv.open, cm: iv.cm,
|
||||
};
|
||||
for (const partition of space.partitions || []) {
|
||||
const a = [partition.a[0], partition.a[1]];
|
||||
const b = [partition.b[0], partition.b[1]];
|
||||
offer({
|
||||
a, b, roomId: '', segs: [[a[0], a[1], b[0], b[1]]],
|
||||
open: false, cm: Number(partition.cm) || 0,
|
||||
source: { kind: 'partition', id: partition.id },
|
||||
}, distToSegment(raw, [a[0], a[1], b[0], b[1]]), true);
|
||||
}
|
||||
for (const draft of space.room_drafts || []) {
|
||||
// The active chain belongs to the draw tool; like the snap geometry,
|
||||
// Thickness only sees saved drafts.
|
||||
if (draft.id === this._activeDraftId) continue;
|
||||
for (let i = 0; i + 1 < draft.points.length; i++) {
|
||||
const a = [draft.points[i][0], draft.points[i][1]];
|
||||
const b = [draft.points[i + 1][0], draft.points[i + 1][1]];
|
||||
offer({
|
||||
a, b, roomId: '', segs: [[a[0], a[1], b[0], b[1]]],
|
||||
open: false, cm: Number(draft.segments[i]?.cm) || 15,
|
||||
source: { kind: 'draft', id: draft.id, segment: i },
|
||||
}, distToSegment(raw, [a[0], a[1], b[0], b[1]]), true);
|
||||
}
|
||||
}
|
||||
return best ? (best as { hit: Hit }).hit : null;
|
||||
}
|
||||
|
||||
private get _wallThickHover(): { segs: number[][]; open: boolean; d: string } | null {
|
||||
@@ -11970,6 +12015,7 @@ class HouseplanCard extends LitElement {
|
||||
a: hit.a, b: hit.b,
|
||||
value: cmToField(cm, this._imperial),
|
||||
roomId: hit.roomId,
|
||||
source: hit.source,
|
||||
sx: ((mx - view.x) / view.w) * 100,
|
||||
sy: ((my - view.y) / view.h) * 100,
|
||||
};
|
||||
@@ -11990,6 +12036,38 @@ class HouseplanCard extends LitElement {
|
||||
this._showPhysicalRange(100);
|
||||
return;
|
||||
}
|
||||
// #313: independent masonry writes its own record. This switch is the
|
||||
// single seam for #306: the future «zero turns the wall virtual» branch
|
||||
// belongs here, next to the range refusal below.
|
||||
if (d.source.kind !== 'room') {
|
||||
// A partition or draft segment without thickness does not exist in the
|
||||
// model (owner decision 2026-08-26): zero and empty are refused with
|
||||
// the ordinary range toast until #306 gives zero a meaning.
|
||||
if (!text || !(cmRaw > 0)) {
|
||||
this._showPhysicalRange(100);
|
||||
return;
|
||||
}
|
||||
const before = this._geometrySnapshot();
|
||||
if (d.source.kind === 'partition') {
|
||||
const partition = (sp.partitions || [])
|
||||
.find((item: PartitionCfg) => item.id === (d.source as { id: string }).id);
|
||||
if (!partition) return;
|
||||
partition.cm = cmRaw;
|
||||
} else {
|
||||
const draft = (sp.room_drafts || [])
|
||||
.find((item: any) => item.id === (d.source as { id: string }).id);
|
||||
const segment = draft?.segments?.[(d.source as { segment: number }).segment];
|
||||
if (!segment) return;
|
||||
segment.cm = cmRaw;
|
||||
}
|
||||
this._wallDialog = null;
|
||||
const committed = this._commitPhysicalGeometry(
|
||||
this._t('history.wall_thickness'), before,
|
||||
);
|
||||
if (committed) this._showToast(this._t('toast.wallthick_set'));
|
||||
this.requestUpdate();
|
||||
return;
|
||||
}
|
||||
const before = this._geometrySnapshot();
|
||||
const cm = text && cmRaw > 0 ? cmRaw : null;
|
||||
const openCuts = this._openCuts();
|
||||
@@ -12555,9 +12633,10 @@ class HouseplanCard extends LitElement {
|
||||
<span class="opl">${this._t(this._imperial ? 'wallthick.unit_in' : 'wallthick.unit_cm')}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn ghost" @click=${() => this._wallThickApply(true)}>
|
||||
${d.source.kind === 'room' ? html`<button class="btn ghost"
|
||||
@click=${() => this._wallThickApply(true)}>
|
||||
${this._t('wallthick.apply_room')}
|
||||
</button>
|
||||
</button>` : nothing}
|
||||
<span class="spacer"></span>
|
||||
<button class="btn on" @click=${() => this._wallThickApply(false)}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
|
||||
Reference in New Issue
Block a user