mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
committed by
claude[bot]
parent
578eb5ebd3
commit
953f6756bc
File diff suppressed because one or more lines are too long
@@ -9,6 +9,23 @@ const result = await page.evaluate(async () => {
|
||||
const root = () => card.shadowRoot || card.renderRoot;
|
||||
const update = async () => { card.requestUpdate(); await card.updateComplete; };
|
||||
const space = () => card._serverCfg.spaces[0];
|
||||
const keyDown = async (key, init = {}) => {
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
key, bubbles: true, cancelable: true, ...init,
|
||||
}));
|
||||
await update();
|
||||
};
|
||||
const clickStage = async (x, y) => {
|
||||
const stage = root().querySelector('.stage');
|
||||
const rect = stage.getBoundingClientRect();
|
||||
const view = card._viewOr(card._baseVb());
|
||||
stage.dispatchEvent(new MouseEvent('click', {
|
||||
clientX: rect.left + ((x - view.x) / view.w) * rect.width,
|
||||
clientY: rect.top + ((y - view.y) / view.h) * rect.height,
|
||||
bubbles: true, cancelable: true,
|
||||
}));
|
||||
await update();
|
||||
};
|
||||
const resetGeometry = async (rooms = []) => {
|
||||
const current = space();
|
||||
current.rooms = JSON.parse(JSON.stringify(rooms));
|
||||
@@ -24,9 +41,14 @@ const result = await page.evaluate(async () => {
|
||||
card._wallFaceBatch = null;
|
||||
card._roomDialog = false;
|
||||
card._tool = 'draw';
|
||||
card._cursorPt = null;
|
||||
card._suppressClick = false;
|
||||
card._geometryHistory.clear();
|
||||
delete card._resumeDraftBySpace[card._space];
|
||||
card._clearPlanSnapHover();
|
||||
await update();
|
||||
};
|
||||
const draw = async (points) => {
|
||||
const draw = async (points, cms = []) => {
|
||||
card._tool = 'draw';
|
||||
card._path = [[...points[0]]];
|
||||
card._activeDraftId = null;
|
||||
@@ -34,7 +56,7 @@ const result = await page.evaluate(async () => {
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const before = card._path.map((point) => [...point]);
|
||||
card._path = [...card._path, [...points[i]]];
|
||||
card._draftSegmentCms = [...card._draftSegmentCms, card._drawWallCm];
|
||||
card._draftSegmentCms = [...card._draftSegmentCms, cms[i - 1] ?? card._drawWallCm];
|
||||
card._persistActiveDraftSegment();
|
||||
card._offerWallFaces(before);
|
||||
await update();
|
||||
@@ -74,6 +96,64 @@ const result = await page.evaluate(async () => {
|
||||
&& space().partitions.every((partition, index) => partition.cm === cms[index]);
|
||||
out.finishedChainDoesNotResume = !card._activeDraftId && card._path.length === 0;
|
||||
|
||||
// #294: Escape owns the registered window keyboard path, finishes exactly
|
||||
// like a tool change and leaves Walls armed for an independent next chain.
|
||||
await resetGeometry();
|
||||
await draw([[100, 100], [300, 100], [300, 250]], [12, 23]);
|
||||
await keyDown('Escape');
|
||||
const escapedPartitions = space().partitions || [];
|
||||
out.escapeFinishesWithoutDeletingSegments = card._tool === 'draw'
|
||||
&& card._path.length === 0 && !card._activeDraftId && !space().room_drafts
|
||||
&& !card._resumeDraftBySpace[card._space]
|
||||
&& escapedPartitions.length === 2
|
||||
&& escapedPartitions.some((part) => part.cm === 12)
|
||||
&& escapedPartitions.some((part) => part.cm === 23)
|
||||
&& card._geometryHistory.undoName === card._t('history.wall_chain_finish')
|
||||
&& !root().querySelector('.active-axis') && !root().querySelector('.active-vertex');
|
||||
const escapedGeometry = JSON.stringify(space());
|
||||
const escapedHistory = card._geometryHistory.undoName;
|
||||
await keyDown('Escape');
|
||||
out.repeatedEscapeAfterFinishIsNoop = JSON.stringify(space()) === escapedGeometry
|
||||
&& card._geometryHistory.undoName === escapedHistory
|
||||
&& card._tool === 'draw' && card._path.length === 0;
|
||||
await clickStage(500, 500);
|
||||
out.nextClickStartsIndependentChain = card._path.length === 1
|
||||
&& card._path[0][0] === 500 && card._path[0][1] === 500
|
||||
&& !space().room_drafts && (space().partitions || []).length === 2;
|
||||
await clickStage(700, 500);
|
||||
const independentDraft = space().room_drafts?.[0];
|
||||
out.secondClickCreatesOnlyIndependentSegment = card._path.length === 2
|
||||
&& independentDraft?.points?.length === 2
|
||||
&& independentDraft.points[0][0] === 0.5 && independentDraft.points[0][1] === 0.5
|
||||
&& independentDraft.points[1][0] === 0.7 && independentDraft.points[1][1] === 0.5
|
||||
&& (space().partitions || []).length === 2;
|
||||
|
||||
await resetGeometry();
|
||||
const singlePointGeometry = JSON.stringify(space());
|
||||
const singlePointHistory = card._geometryHistory.undoName;
|
||||
await clickStage(200, 200);
|
||||
const onePointWasArmed = card._path.length === 1 && !space().room_drafts;
|
||||
await keyDown('Escape');
|
||||
out.escapeClearsOnlyTransientFirstPoint = onePointWasArmed
|
||||
&& card._tool === 'draw' && card._path.length === 0 && !card._activeDraftId
|
||||
&& JSON.stringify(space()) === singlePointGeometry
|
||||
&& card._geometryHistory.undoName === singlePointHistory;
|
||||
|
||||
await resetGeometry();
|
||||
await draw([[100, 100], [300, 100], [300, 250]], [12, 23]);
|
||||
const rejectedPath = JSON.stringify(card._path);
|
||||
const rejectedDraft = JSON.stringify(space().room_drafts);
|
||||
const rejectedHistory = card._geometryHistory.undoName;
|
||||
const finishWallChain = card._finishWallChain;
|
||||
card._finishWallChain = () => false;
|
||||
await keyDown('Escape');
|
||||
card._finishWallChain = finishWallChain;
|
||||
out.rejectedFinishKeepsActiveDraft = JSON.stringify(card._path) === rejectedPath
|
||||
&& JSON.stringify(space().room_drafts) === rejectedDraft
|
||||
&& card._geometryHistory.undoName === rejectedHistory
|
||||
&& card._activeDraftId === space().room_drafts?.[0]?.id
|
||||
&& !(space().partitions?.length);
|
||||
|
||||
await resetGeometry();
|
||||
await draw([[100, 100], [300, 100], [300, 300], [100, 300], [100, 100]]);
|
||||
out.closedFaceOpensDialog = card._roomDialog && card._wallFaceBatch?.candidates.length === 1;
|
||||
@@ -93,19 +173,25 @@ const result = await page.evaluate(async () => {
|
||||
out.cancelKeepsWholeTerminalDraft = !card._roomDialog && !card._wallFaceBatch
|
||||
&& JSON.stringify(space().room_drafts) === draftBeforeCancel
|
||||
&& card._path.length === 5;
|
||||
card._onKey(new KeyboardEvent('keydown', {
|
||||
key: 'z', code: 'KeyZ', ctrlKey: true, bubbles: true,
|
||||
}));
|
||||
await update();
|
||||
out.escapeUndoContractStillRemovesLastSegment = space().room_drafts?.[0]?.segments.length === 3
|
||||
await keyDown('z', { code: 'KeyZ', ctrlKey: true });
|
||||
out.ctrlZContractStillRemovesLastSegment = space().room_drafts?.[0]?.segments.length === 3
|
||||
&& card._path.length === 4;
|
||||
|
||||
await resetGeometry();
|
||||
await draw([[500, 100], [700, 100], [700, 300], [500, 300], [500, 100]]);
|
||||
const dialogDraft = JSON.stringify(space().room_drafts);
|
||||
await keyDown('Escape');
|
||||
out.firstEscapeOnlyCancelsRoomDialog = !card._roomDialog && !card._wallFaceBatch
|
||||
&& JSON.stringify(space().room_drafts) === dialogDraft
|
||||
&& card._path.length === 5 && !(space().partitions?.length);
|
||||
await keyDown('Escape');
|
||||
out.secondEscapeFinishesRestoredDraft = !space().room_drafts
|
||||
&& card._path.length === 0 && !card._activeDraftId
|
||||
&& space().partitions?.length === 4 && card._tool === 'draw';
|
||||
|
||||
await resetGeometry();
|
||||
await draw([[100, 450], [300, 450], [300, 650], [100, 650], [100, 450]]);
|
||||
card._onKey(new KeyboardEvent('keydown', {
|
||||
key: 'z', code: 'KeyZ', ctrlKey: true, bubbles: true,
|
||||
}));
|
||||
await update();
|
||||
await keyDown('z', { code: 'KeyZ', ctrlKey: true });
|
||||
out.ctrlZInQueueRemovesTerminalPoint = !card._roomDialog && !card._wallFaceBatch
|
||||
&& card._path.length === 4 && space().room_drafts?.[0]?.segments.length === 3;
|
||||
|
||||
|
||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- While drawing Walls, `Esc` now finishes all accepted segments as independent
|
||||
walls and releases the last point without deleting geometry or leaving the
|
||||
tool. The next click starts a new chain; `Ctrl/Cmd+Z` remains the shortcut
|
||||
that removes the last point and segment
|
||||
([#294](https://github.com/Matysh/houseplan-card/issues/294)).
|
||||
|
||||
## v1.67.0-rc.1 — 2026-08-24
|
||||
|
||||
- Republished the reviewed beta.10 candidate under an RC identifier so HACS
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- При рисовании стен `Esc` теперь завершает все принятые отрезки как
|
||||
независимые стены и отцепляется от последней точки, не удаляя геометрию и не
|
||||
покидая инструмент. Следующий клик начинает новую цепочку, а `Ctrl/Cmd+Z`
|
||||
по-прежнему удаляет последнюю точку и отрезок
|
||||
([#294](https://github.com/Matysh/houseplan-card/issues/294)).
|
||||
|
||||
## v1.67.0-rc.1 — 2026-08-24
|
||||
|
||||
- Проверенный кандидат beta.10 переопубликован с RC-идентификатором, чтобы HACS
|
||||
|
||||
+10
-3
@@ -204,7 +204,7 @@ objects; each space keeps its local View viewport.
|
||||
| Zoom and pan | Wheel; drag empty space; `−`/`+` | Pinch; drag; double-tap resets kiosk | Available but precision is not guaranteed | — |
|
||||
| Change space | Click a tab | Tap; kiosk swipe at 1:1 | Tap a tab | — |
|
||||
| Device | Click/double-click per mode | Tap; safe actions equal desktop | Drag/properties are best effort | `Esc` closes the top surface |
|
||||
| Draw or precise drag | Full contract | Not applicable | Best effort; use desktop for Resize and exact nodes | `Shift` changes magnet/angle; `Esc` cancels the operation |
|
||||
| Walls drawing or precise drag | Full contract | Not applicable | Best effort; use desktop for Resize and exact nodes | `Shift` changes magnet/angle; `Esc` finishes a Walls chain or cancels the current precise drag |
|
||||
| Editor history | Undo/Redo controls | Not applicable | Controls may work; no gesture guarantee | `Ctrl/Cmd+Z`, `Ctrl/Cmd+Shift+Z`, `Ctrl+Y` |
|
||||
| Kiosk sizing | — | Hold empty space for 3 seconds | — | — |
|
||||
|
||||
@@ -213,8 +213,10 @@ Any manual kiosk operation pauses auto-cycle for 60 seconds.
|
||||
|
||||
### Cancel and undo
|
||||
|
||||
- `Esc` cancels an unfinished path, current drag/resize/rotation, or the top
|
||||
dialog without undoing an already committed action.
|
||||
- `Esc` finishes an active Walls chain without deleting its accepted segments.
|
||||
In Split and other tools it cancels the unfinished path, current
|
||||
drag/resize/rotation, or the top dialog without undoing an already committed
|
||||
action.
|
||||
- `Ctrl/Cmd+Z` undoes an editor command; redo is `Ctrl/Cmd+Shift+Z` or `Ctrl+Y`.
|
||||
- Undo/Redo stores up to 50 named commands for Plan and Background.
|
||||
|
||||
@@ -298,6 +300,11 @@ House Plan offers them from smallest to largest. Save creates that room, Keep
|
||||
as walls rejects only that candidate, and Cancel restores the whole draft with
|
||||
no partial rooms.
|
||||
|
||||
While drawing an open chain, `Esc` finishes all accepted segments as ordinary
|
||||
independent walls and keeps **Walls** selected; the next click starts a new
|
||||
chain. `Ctrl/Cmd+Z` instead removes the last accepted point and segment. Pan,
|
||||
pinch and `pointercancel` neither finish the chain nor add geometry.
|
||||
|
||||
Existing segment endpoints and lines appear above walls while drawing. An
|
||||
endpoint grows when the next click will join it. A point on a line shows where
|
||||
the click will create a valid junction.
|
||||
|
||||
+10
-6
@@ -220,7 +220,7 @@ desktop: для точного рисования, Resize, модификато
|
||||
| Масштаб и панорама | Колесо; drag пустого места; кнопки `−`/`+` | Щипок; drag; двойной тап сбрасывает киоск | Работает, но точность не гарантируется | — |
|
||||
| Переключение пространства | Клик по вкладке | Клик; в киоске свайп при масштабе 1:1 | Клик по вкладке | — |
|
||||
| Устройство | Клик/двойной клик по правилам режима | Tap; безопасные действия как на desktop | Drag и свойства — best effort | `Esc` закрывает верхнюю поверхность |
|
||||
| Рисование и точный drag | Полный контракт | Не применяется | Best effort; для Resize и точных точек используйте мышь | `Shift` меняет магнит/угол; `Esc` отменяет операцию |
|
||||
| Рисование стен и точный drag | Полный контракт | Не применяется | Best effort; для Resize и точных точек используйте мышь | `Shift` меняет магнит/угол; `Esc` завершает цепочку стен или отменяет текущий точный drag |
|
||||
| История редактора | Кнопки Undo/Redo | Не применяется | Кнопки могут работать, жест не гарантирован | `Ctrl/Cmd+Z`, `Ctrl/Cmd+Shift+Z`, `Ctrl+Y` |
|
||||
| Киоск-размеры | — | Удерживать пустое место 3 секунды | — | — |
|
||||
|
||||
@@ -238,8 +238,9 @@ desktop: для точного рисования, Resize, модификато
|
||||
|
||||
| Контекст | `Esc` | `Ctrl+Z` / `Cmd+Z` | `Ctrl+Shift+Z` / `Ctrl+Y` |
|
||||
|---|---|---|---|
|
||||
| Незавершённый Draw/Split | Убирает последнюю точку или выходит из инструмента | Сначала убирает незавершённую точку | — |
|
||||
| Любой инструмент редактора плана | Отменяет текущий жест/выбор | Отменяет последнюю именованную геометрическую операцию | Повторяет отменённую операцию |
|
||||
| Активная цепочка «Стены» | Завершает принятые отрезки как независимые стены, оставляя инструмент активным | Убирает последнюю принятую точку и отрезок | — |
|
||||
| Незавершённый Split | Убирает последнюю точку или выходит из инструмента | Сначала убирает незавершённую точку | — |
|
||||
| Любой другой инструмент редактора плана | Отменяет текущий жест/выбор | Отменяет последнюю именованную геометрическую операцию | Повторяет отменённую операцию |
|
||||
| Подложка | Отменяет незавершённое рисование или возвращает состояние до начала активного move/resize/rotate | Отменяет последнюю именованную операцию декора или картинки | Повторяет отменённую операцию |
|
||||
| Текстовое поле | Закрывает верхний диалог | Обычная отмена текста браузером | Обычный повтор текста браузером |
|
||||
|
||||
@@ -409,9 +410,12 @@ T-соединение входит в проходящую стену без в
|
||||
Отмена ничего не записывает, подтверждённое исправление можно отменить обычным
|
||||
Undo оптимизации.
|
||||
|
||||
Каждый законченный отрезок цепочки сохраняется сразу. Esc/Ctrl+Z удаляет
|
||||
последнюю точку. Pan, pinch и `pointercancel` ничего не завершают и не добавляют.
|
||||
После перезагрузки сохранённый draft можно продолжить кликом по его концу.
|
||||
Каждый законченный отрезок цепочки сохраняется сразу. `Esc` завершает принятые
|
||||
отрезки как независимые стены, оставляет инструмент **Стены** активным и
|
||||
отцепляется от последней точки; следующий клик начинает новую цепочку.
|
||||
`Ctrl/Cmd+Z` вместо этого удаляет последнюю точку и отрезок. Pan, pinch и
|
||||
`pointercancel` ничего не завершают и не добавляют. После перезагрузки
|
||||
сохранённый draft можно продолжить кликом по его концу.
|
||||
|
||||
### Связь с HA-зоной
|
||||
|
||||
|
||||
@@ -2627,7 +2627,10 @@ class HouseplanCard extends LitElement {
|
||||
}
|
||||
if (this._tool === 'draw' && this._path.length) {
|
||||
e.preventDefault();
|
||||
this._undoPoint();
|
||||
// Escape releases the active wall chain without making it destructive:
|
||||
// Ctrl/Cmd+Z remains the one-point undo path (#294). Reuse the exact
|
||||
// finish transaction used by a tool change, but keep `draw` selected.
|
||||
this._finishWallChain();
|
||||
return;
|
||||
}
|
||||
if (this._physicalSel) {
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@
|
||||
"confirm.delete_partition_openings_body": "This wall contains {count} opening(s). They will be deleted together.",
|
||||
"confirm.delete_partition_openings_item": "• {type}, {length}",
|
||||
"markup.delete": "Delete",
|
||||
"markup.hint_points": "points: {n} · Shift — 45° steps · Esc/Ctrl+Z — undo a dot · closing an area offers a room",
|
||||
"markup.hint_points": "points: {n} · Shift — 45° steps · Esc — finish chain · Ctrl+Z — undo a point · closing an area offers a room",
|
||||
"markup.hint_start": "click a grid dot to start a wall chain",
|
||||
"tip.lqi": "average zigbee signal:",
|
||||
"tip.area": "area: {value}",
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@
|
||||
"confirm.delete_partition_openings_body": "В стене есть проёмы: {count}. Они будут удалены вместе со стеной.",
|
||||
"confirm.delete_partition_openings_item": "• {type}, {length}",
|
||||
"markup.delete": "Удалить",
|
||||
"markup.hint_points": "точек: {n} · Shift — шаг 45° · Esc/Ctrl+Z — убрать точку · при замыкании будет предложена комната",
|
||||
"markup.hint_points": "точек: {n} · Shift — шаг 45° · Esc — завершить цепочку · Ctrl+Z — убрать точку · при замыкании будет предложена комната",
|
||||
"markup.hint_start": "кликните точку сетки, чтобы начать цепочку стен",
|
||||
"tip.lqi": "средний сигнал zigbee:",
|
||||
"tip.area": "площадь: {value}",
|
||||
|
||||
Reference in New Issue
Block a user