mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
Merge inert-openings: View-mode cursors and inert openings v1.28.1
This commit is contained in:
@@ -11,7 +11,7 @@ PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory
|
|||||||
FILES_URL = "/houseplan_files/files"
|
FILES_URL = "/houseplan_files/files"
|
||||||
FILES_DIR = "houseplan/files"
|
FILES_DIR = "houseplan/files"
|
||||||
CONF_ADMIN_ONLY = "admin_only"
|
CONF_ADMIN_ONLY = "admin_only"
|
||||||
VERSION = "1.28.0"
|
VERSION = "1.28.1"
|
||||||
|
|
||||||
DEFAULT_CONFIG: dict = {
|
DEFAULT_CONFIG: dict = {
|
||||||
"spaces": [],
|
"spaces": [],
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -16,5 +16,5 @@
|
|||||||
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"single_config_entry": true,
|
"single_config_entry": true,
|
||||||
"version": "1.28.0"
|
"version": "1.28.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { launch } from './serve.mjs';
|
||||||
|
const { page, browser } = await launch();
|
||||||
|
const res = await page.evaluate(async () => {
|
||||||
|
const out = {};
|
||||||
|
const c = window.__card;
|
||||||
|
const sr = () => c.shadowRoot || c.renderRoot;
|
||||||
|
// добавить открывание на f1
|
||||||
|
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== 'f1' ? s : ({
|
||||||
|
...s, openings: [{ id: 'op1', type: 'door', rx: 550, ry: 200, angle: 90, len_cm: 90 }] })) };
|
||||||
|
c.requestUpdate(); await c.updateComplete;
|
||||||
|
const hit = sr().querySelector('.op-hit');
|
||||||
|
out.hasOpening = !!hit;
|
||||||
|
const cs = hit ? getComputedStyle(hit) : null;
|
||||||
|
out.viewInert = cs ? cs.pointerEvents === 'none' && cs.cursor === 'default' : null;
|
||||||
|
out.lockBadgeInert = (() => { const l = sr().querySelector('.oplock'); return l ? getComputedStyle(l).pointerEvents === 'none' : 'no-badge'; })();
|
||||||
|
// View: клик по открыванию ничего не открывает
|
||||||
|
c._opClick({ stopPropagation(){} }, { id: 'op1', rx: 550, ry: 200, rlen: 90 });
|
||||||
|
out.viewClickNoop = !c._openingInfo && !c._openingDialog;
|
||||||
|
// курсор устройств в Просмотре — pointer, в Устройствах — grab
|
||||||
|
const dev = sr().querySelector('.dev');
|
||||||
|
out.viewDevCursor = getComputedStyle(dev).cursor;
|
||||||
|
// бейдж замка: замок привязан → бейдж кликабелен и открывает инфо
|
||||||
|
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s2) => s2.id !== 'f1' ? s2 : ({
|
||||||
|
...s2, openings: [{ id: 'op1', type: 'door', rx: 550, ry: 200, angle: 90, len_cm: 90, lock: 'lock.front_door' }] })) };
|
||||||
|
c.requestUpdate(); await c.updateComplete;
|
||||||
|
const badge = sr().querySelector('.oplock');
|
||||||
|
out.badgeShown = !!badge;
|
||||||
|
const bcs = badge ? getComputedStyle(badge) : null;
|
||||||
|
out.badgeClickableInView = bcs ? bcs.pointerEvents === 'auto' && bcs.cursor === 'pointer' : null;
|
||||||
|
badge?.click(); await c.updateComplete;
|
||||||
|
out.badgeOpensInfo = !!c._openingInfo;
|
||||||
|
c._openingInfo = null;
|
||||||
|
// Plan: op-hit интерактивен, клик редактирует
|
||||||
|
c._setMode('devices'); await c.updateComplete;
|
||||||
|
out.devModeCursor = getComputedStyle(sr().querySelector('.dev')).cursor;
|
||||||
|
c._setMode('plan'); await c.updateComplete;
|
||||||
|
out.badgeInertInPlan = (() => { const b = sr().querySelector('.oplock'); return b ? getComputedStyle(b).pointerEvents === 'none' : 'no-badge'; })();
|
||||||
|
const cs2 = getComputedStyle(sr().querySelector('.op-hit'));
|
||||||
|
out.planInteractive = cs2.pointerEvents === 'auto' && cs2.cursor === 'grab';
|
||||||
|
const op = c._spaceModel().openings?.[0] || { id: 'op1', rx: 550, ry: 200, rlen: 90 };
|
||||||
|
c._opClick({ stopPropagation(){} }, op);
|
||||||
|
out.planClickEdits = !!c._openingDialog;
|
||||||
|
c._openingDialog = null; c._setMode('view');
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify(res, null, 1));
|
||||||
|
await browser.close();
|
||||||
File diff suppressed because one or more lines are too long
Vendored
+20
-15
File diff suppressed because one or more lines are too long
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.28.1 — 2026-07-22 (View-mode polish: cursors and inert openings)
|
||||||
|
- **Device icons in View no longer show the grab cursor** (drag lives in the
|
||||||
|
Devices mode); they show a pointer — clicking still works exactly as before.
|
||||||
|
- **Doors and windows in View are pure drawings**: no grab cursor, no hover
|
||||||
|
outline, no hit target, no click — regardless of what is bound to them.
|
||||||
|
- **The lock badge is the one exception**: when a lock is bound, the badge is
|
||||||
|
shown and clickable in View (pointer cursor, click opens the door/lock info
|
||||||
|
card). It stays inert in Plan so it does not fight editing.
|
||||||
|
- In Plan an opening remains fully interactive: grab cursor + hover outline,
|
||||||
|
dragging along walls, and a click with ANY tool opens its properties dialog.
|
||||||
|
|
||||||
## v1.28.0 — 2026-07-21 (sub-area rooms: manual placement without an HA area; issue #3)
|
## v1.28.0 — 2026-07-21 (sub-area rooms: manual placement without an HA area; issue #3)
|
||||||
- **Devices can now be placed into rooms that have no Home Assistant area.**
|
- **Devices can now be placed into rooms that have no Home Assistant area.**
|
||||||
The marker dialog's room list includes area-less rooms (marked "no area,
|
The marker dialog's room list includes area-less rooms (marked "no area,
|
||||||
|
|||||||
+10
-2
@@ -40,8 +40,16 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
|
|||||||
- [ ] Devices: icon drag works, click opens the marker editor directly; +/👁/↺/⬡
|
- [ ] Devices: icon drag works, click opens the marker editor directly; +/👁/↺/⬡
|
||||||
buttons; accent stage frame [auto]
|
buttons; accent stage frame [auto]
|
||||||
- [ ] Mode tabs hidden for non-admin users; segmented control highlights the active mode
|
- [ ] Mode tabs hidden for non-admin users; segmented control highlights the active mode
|
||||||
- [ ] Opening: single click in View shows the info card; in Plan a click edits it;
|
- [ ] Openings in View (v1.28.1): the door/window itself is a pure drawing — no
|
||||||
double-click no longer does anything special [auto]
|
cursor change, no hover outline, no hit target, no click, regardless of
|
||||||
|
bindings [auto]
|
||||||
|
- [ ] The LOCK BADGE is the one exception: when a lock is bound it is shown and
|
||||||
|
clickable in View (pointer cursor, click → door/lock info card); inert in
|
||||||
|
Plan so it does not fight editing [auto]
|
||||||
|
- [ ] Device icons in View show a pointer cursor (no grab); grab only in the
|
||||||
|
Devices mode [auto]
|
||||||
|
- [ ] In Plan an opening is interactive: grab cursor, hover outline, drag along
|
||||||
|
walls, click (any tool) opens its properties [auto]
|
||||||
|
|
||||||
## Onboarding ★
|
## Onboarding ★
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "houseplan-card",
|
"name": "houseplan-card",
|
||||||
"version": "1.28.0",
|
"version": "1.28.1",
|
||||||
"description": "Interactive house plan Lovelace card for Home Assistant",
|
"description": "Interactive house plan Lovelace card for Home Assistant",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+4
-10
@@ -32,7 +32,7 @@ import './space-card';
|
|||||||
import { cardStyles } from './styles';
|
import { cardStyles } from './styles';
|
||||||
import { langOf, t, type I18nKey } from './i18n';
|
import { langOf, t, type I18nKey } from './i18n';
|
||||||
|
|
||||||
const CARD_VERSION = '1.28.0';
|
const CARD_VERSION = '1.28.1';
|
||||||
const LS_KEY = 'houseplan_card_layout_v1';
|
const LS_KEY = 'houseplan_card_layout_v1';
|
||||||
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
|
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
|
||||||
const LS_ZOOM = 'houseplan_card_zoom_v1';
|
const LS_ZOOM = 'houseplan_card_zoom_v1';
|
||||||
@@ -1261,15 +1261,9 @@ class HouseplanCard extends LitElement {
|
|||||||
private _opClick(ev: MouseEvent, o: OpeningCfg & { rx: number; ry: number; rlen: number }): void {
|
private _opClick(ev: MouseEvent, o: OpeningCfg & { rx: number; ry: number; rlen: number }): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (this._opDrag?.moved) return; // that click was the tail of a drag
|
if (this._opDrag?.moved) return; // that click was the tail of a drag
|
||||||
if (this._mode === 'view') {
|
// openings are inert outside Plan mode (owner's decision: View must not
|
||||||
// view: an opening is a status object — show the door/lock info card
|
// interact with them at all); in Plan any click on an opening edits it
|
||||||
this._openingInfo = o;
|
if (this._mode === 'plan') this._editOpening(o);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._mode === 'plan' && this._tool !== 'opening') {
|
|
||||||
// plan: any click on an opening edits it (the Opening tool also does)
|
|
||||||
this._editOpening(o);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _saveOpening(): void {
|
private _saveOpening(): void {
|
||||||
|
|||||||
+18
-13
@@ -242,22 +242,26 @@ export const cardStyles = css`
|
|||||||
transition: opacity 0.15s;
|
transition: opacity 0.15s;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
g.opening:hover .op-outline {
|
.stage.markup g.opening:hover .op-outline {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
/* openings are pure status graphics outside Plan mode: no cursor, no hover,
|
||||||
|
no hit target — View must not interact with them at all */
|
||||||
.op-hit {
|
.op-hit {
|
||||||
fill: transparent;
|
fill: transparent;
|
||||||
cursor: grab;
|
pointer-events: none;
|
||||||
pointer-events: auto;
|
cursor: default;
|
||||||
touch-action: none; /* drags, not scrolls, on touch */
|
|
||||||
}
|
|
||||||
.op-hit:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
}
|
||||||
.stage.markup .op-hit {
|
.stage.markup .op-hit {
|
||||||
pointer-events: none; /* markup clicks go to the stage tools */
|
pointer-events: auto;
|
||||||
|
cursor: grab;
|
||||||
|
touch-action: none; /* drags, not scrolls, on touch */
|
||||||
|
}
|
||||||
|
.stage.markup .op-hit:active {
|
||||||
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
.oplock {
|
.oplock {
|
||||||
|
pointer-events: none; /* inert while editing; clickable in View (rule below) */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: calc(var(--icon-size, 2.5cqw) * 0.62);
|
width: calc(var(--icon-size, 2.5cqw) * 0.62);
|
||||||
@@ -268,9 +272,11 @@ export const cardStyles = css`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: var(--hp-bg);
|
background: var(--hp-bg);
|
||||||
border: 1px solid var(--hp-line);
|
border: 1px solid var(--hp-line);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.stage.mode-view .oplock {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
.oplock ha-icon {
|
.oplock ha-icon {
|
||||||
--mdc-icon-size: calc(var(--icon-size, 2.5cqw) * 0.4);
|
--mdc-icon-size: calc(var(--icon-size, 2.5cqw) * 0.4);
|
||||||
@@ -540,7 +546,7 @@ export const cardStyles = css`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--hp-txt);
|
color: var(--hp-txt);
|
||||||
cursor: grab;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transition: background 0.15s, border-color 0.15s, opacity 0.2s;
|
transition: background 0.15s, border-color 0.15s, opacity 0.2s;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
|
||||||
@@ -553,9 +559,8 @@ export const cardStyles = css`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
.dev:active {
|
.stage.mode-devices .dev { cursor: grab; }
|
||||||
cursor: grabbing;
|
.stage.mode-devices .dev:active { cursor: grabbing; }
|
||||||
}
|
|
||||||
.dev:hover {
|
.dev:hover {
|
||||||
background: var(--hp-accent);
|
background: var(--hp-accent);
|
||||||
color: var(--text-primary-color, #fff);
|
color: var(--text-primary-color, #fff);
|
||||||
|
|||||||
Reference in New Issue
Block a user