mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
ux v1.30.2: editor tabs redesign
- two tabs only: 'Plan editor' / 'Device editor'; View is the implicit default state, no tab - Device editor gets its own bottom toolbar (add/show-all/reset/rules moved from the header), mirroring the Plan toolbar - X button on both toolbars and inside the active tab returns to View; re-click on active tab is a no-op; Plan<->Devices switches directly - smoke_editor_tabs.mjs; TESTING/CHANGELOG/UX-MODES same-commit
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_DIR = "houseplan/files"
|
||||
CONF_ADMIN_ONLY = "admin_only"
|
||||
VERSION = "1.30.1"
|
||||
VERSION = "1.30.2"
|
||||
|
||||
DEFAULT_CONFIG: dict = {
|
||||
"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",
|
||||
"requirements": [],
|
||||
"single_config_entry": true,
|
||||
"version": "1.30.1"
|
||||
"version": "1.30.2"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
const tabs = () => [...sr().querySelectorAll('.modetab')];
|
||||
// 1) две вкладки, Просмотра нет, крестиков в неактивных нет
|
||||
out.twoTabs = tabs().length === 2;
|
||||
out.labels = tabs().map((t) => t.textContent.trim());
|
||||
out.noCrossIdle = sr().querySelectorAll('.modetab .closex').length === 0;
|
||||
out.startView = c._mode === 'view';
|
||||
// 2) клик по «Редактор плана» → активна, панель с крестиком
|
||||
tabs()[0].click(); await c.updateComplete;
|
||||
out.planActive = c._mode === 'plan' && tabs()[0].classList.contains('active');
|
||||
out.planBar = !!sr().querySelector('.editbar');
|
||||
out.planBarClose = !!sr().querySelector('.editbar .barclose');
|
||||
out.tabCross = !!tabs()[0].querySelector('.closex');
|
||||
// 3) повторный клик по активной вкладке — ничего
|
||||
tabs()[0].click(); await c.updateComplete;
|
||||
out.reclickNoop = c._mode === 'plan';
|
||||
// 4) прямое переключение План → Устройства
|
||||
tabs()[1].click(); await c.updateComplete;
|
||||
out.directSwitch = c._mode === 'devices';
|
||||
out.devBar = !!sr().querySelector('.editbar.devbar');
|
||||
out.devBarBtns = sr().querySelectorAll('.editbar.devbar .btn:not(.barclose)').length;
|
||||
// 5) инструменты устройств из шапки исчезли (в .bar их больше нет)
|
||||
out.headerCleanInDev = !sr().querySelector('.bar > .btn[title*="' + (c._t('title.add_device')) + '"]');
|
||||
// 6) крестик на панели → Просмотр
|
||||
sr().querySelector('.editbar .barclose').click(); await c.updateComplete;
|
||||
out.barCloseWorks = c._mode === 'view' && !sr().querySelector('.editbar');
|
||||
// 7) крестик в самой вкладке → Просмотр (и не переключает режим)
|
||||
tabs()[1].click(); await c.updateComplete;
|
||||
tabs()[1].querySelector('.closex').click(); await c.updateComplete;
|
||||
out.tabCrossWorks = c._mode === '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
+45
-19
File diff suppressed because one or more lines are too long
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## v1.30.2 — 2026-07-22 (editor tabs redesign)
|
||||
- Mode tabs renamed and reduced to two: **"Plan editor"** and **"Device
|
||||
editor"**. View is no longer a tab — it is the implicit default state.
|
||||
- The Device editor now has its own bottom toolbar (add / show all / reset
|
||||
layout / icon rules moved out of the header), mirroring the Plan toolbar.
|
||||
- Both toolbars and the active tab itself got an **X** button that closes the
|
||||
editor and returns to View. Re-clicking the active tab does nothing;
|
||||
switching Plan↔Devices is direct.
|
||||
|
||||
## v1.30.1 — 2026-07-22 (space gear polish)
|
||||
- The gear icon next to the space name is now visible in **every mode** (not
|
||||
just Plan) for users who can edit, so space settings are always one click
|
||||
|
||||
@@ -140,6 +140,11 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
|
||||
(explicit ripple color still wins); off/white lights unchanged [auto]
|
||||
- [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any
|
||||
display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static
|
||||
- [ ] Editor tabs (v1.30.2): only two tabs — "Plan editor" / "Device editor"
|
||||
(no View button; View is the default state); clicking a tab opens its
|
||||
bottom toolbar (Devices got its own bar with add/show-all/reset/rules);
|
||||
the bar and the active tab both show an X that returns to View; re-click
|
||||
on the active tab does nothing; Plan↔Devices switches directly [auto]
|
||||
- [ ] Space gear (v1.30.1): the cog next to the space name is visible in every
|
||||
mode (admins only), vertically centered with the tab text; clicking it
|
||||
opens space settings without switching the tab; "+" tab stays Plan-only [auto]
|
||||
|
||||
+9
-4
@@ -12,11 +12,16 @@ A segmented control in the card header with three tabs; the active one is visual
|
||||
highlighted, and edit modes add a colored frame around the stage so the mode is
|
||||
obvious at a glance:
|
||||
|
||||
**[ 👁 View ] [ 📐 Plan ] [ 🔧 Devices ]**
|
||||
**[ 📐 Plan editor ] [ 🔧 Device editor ]** — View has NO tab (since v1.30.2).
|
||||
|
||||
- **View** is the default and the only mode after every load (edit modes are never
|
||||
restored across reloads).
|
||||
- **Plan** and **Devices** are shown only to admins when `admin_only` is on.
|
||||
- **View** is the implicit default state: no editor tab is active. It is the only
|
||||
state after every load (edit modes are never restored across reloads).
|
||||
- Activating an editor tab highlights it and opens that editor's bottom toolbar
|
||||
(both editors have one since v1.30.2). The toolbar and the active tab each
|
||||
carry an **X** that closes the editor back to View; re-clicking the active tab
|
||||
does nothing; Plan↔Devices switches directly.
|
||||
- **Plan editor** and **Device editor** are shown only to admins when
|
||||
`admin_only` is on.
|
||||
|
||||
## View — display and device interaction only
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "houseplan-card",
|
||||
"version": "1.30.1",
|
||||
"version": "1.30.2",
|
||||
"description": "Interactive house plan Lovelace card for Home Assistant",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
+36
-20
@@ -32,7 +32,7 @@ import './space-card';
|
||||
import { cardStyles } from './styles';
|
||||
import { langOf, t, type I18nKey } from './i18n';
|
||||
|
||||
const CARD_VERSION = '1.30.1';
|
||||
const CARD_VERSION = '1.30.2';
|
||||
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_ZOOM = 'houseplan_card_zoom_v1';
|
||||
@@ -2427,11 +2427,15 @@ class HouseplanCard extends LitElement {
|
||||
</div>
|
||||
${this._canEdit
|
||||
? html`<div class="modes">
|
||||
${([['view', 'mdi:eye-outline'], ['plan', 'mdi:floor-plan'], ['devices', 'mdi:tune-variant']] as const).map(
|
||||
${([['plan', 'mdi:floor-plan'], ['devices', 'mdi:tune-variant']] as const).map(
|
||||
([m, ic]) => html`<button class="modetab ${this._mode === m ? 'active' : ''}"
|
||||
title=${this._t(('mode.' + m) as any)}
|
||||
@click=${() => this._setMode(m)}>
|
||||
@click=${() => { if (this._mode !== m) this._setMode(m); }}>
|
||||
<ha-icon icon=${ic}></ha-icon><span class="ml">${this._t(('mode.' + m) as any)}</span>
|
||||
${this._mode === m
|
||||
? html`<ha-icon class="closex" icon="mdi:close" title=${this._t('title.close_editor')}
|
||||
@click=${(e: Event) => { e.stopPropagation(); this._setMode('view'); }}></ha-icon>`
|
||||
: nothing}
|
||||
</button>`,
|
||||
)}
|
||||
</div>`
|
||||
@@ -2444,29 +2448,13 @@ class HouseplanCard extends LitElement {
|
||||
title=${this._t('title.zoom_reset')}><ha-icon icon="mdi:fit-to-page-outline"></ha-icon></button>
|
||||
<button class="btn zb" @click=${() => this._stepZoom(1)} title=${this._t('title.zoom_in')}><ha-icon icon="mdi:plus"></ha-icon></button>
|
||||
</div>
|
||||
${this._norm && this._mode === 'devices'
|
||||
? html`<button class="btn" @click=${() => this._openMarkerDialog()}
|
||||
title=${this._t('title.add_device')}>
|
||||
<ha-icon icon="mdi:plus-box-outline"></ha-icon>
|
||||
</button>
|
||||
<button class="btn ${this._showAll ? 'on' : ''}" @click=${this._toggleShowAll}
|
||||
title=${this._t('title.show_all')}>
|
||||
<ha-icon icon="${this._showAll ? 'mdi:eye' : 'mdi:eye-off-outline'}"></ha-icon>
|
||||
</button>
|
||||
<button class="btn" @click=${this._resetLayout} title=${this._t('title.reset_layout')}>
|
||||
<ha-icon icon="mdi:backup-restore"></ha-icon>
|
||||
</button>
|
||||
<button class="btn" @click=${this._openRulesDialog} title=${this._t('title.icon_rules')}>
|
||||
<ha-icon icon="mdi:shape-plus-outline"></ha-icon>
|
||||
</button>`
|
||||
: nothing}
|
||||
${this._norm && this._mode === 'plan'
|
||||
? html`<button class="btn" @click=${this._openSettingsDialog} title=${this._t('title.general_settings')}>
|
||||
<ha-icon icon="mdi:cog-outline"></ha-icon>
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._markup ? this._renderMarkupBar() : nothing}
|
||||
${this._markup ? this._renderMarkupBar() : this._mode === 'devices' ? this._renderDevicesBar() : nothing}
|
||||
</div>
|
||||
|
||||
<div class="stage ${this._markup ? 'markup' : ''} ${space.bg ? '' : 'noplan'} mode-${this._mode}"
|
||||
@@ -3020,6 +3008,34 @@ class HouseplanCard extends LitElement {
|
||||
: this._t('markup.hint_start')}</span>
|
||||
${this._path.length ? html`<button class="btn ghost" @click=${this._cancelPath}>${this._t('btn.reset')}</button>` : nothing}`
|
||||
: nothing}
|
||||
<button class="btn barclose" title=${this._t('title.close_editor')}
|
||||
@click=${() => this._setMode('view')}>
|
||||
<ha-icon icon="mdi:close"></ha-icon>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderDevicesBar(): TemplateResult {
|
||||
return html`<div class="editbar devbar">
|
||||
<ha-icon icon="mdi:tune-variant" class="warn"></ha-icon>
|
||||
<button class="btn" @click=${() => this._openMarkerDialog()} title=${this._t('title.add_device')}>
|
||||
<ha-icon icon="mdi:plus-box-outline"></ha-icon>${this._t('devbar.add')}
|
||||
</button>
|
||||
<button class="btn ${this._showAll ? 'on' : ''}" @click=${this._toggleShowAll}
|
||||
title=${this._t('title.show_all')}>
|
||||
<ha-icon icon="${this._showAll ? 'mdi:eye' : 'mdi:eye-off-outline'}"></ha-icon>${this._t('devbar.show_all')}
|
||||
</button>
|
||||
<button class="btn" @click=${this._resetLayout} title=${this._t('title.reset_layout')}>
|
||||
<ha-icon icon="mdi:backup-restore"></ha-icon>${this._t('devbar.reset')}
|
||||
</button>
|
||||
<button class="btn" @click=${this._openRulesDialog} title=${this._t('title.icon_rules')}>
|
||||
<ha-icon icon="mdi:shape-plus-outline"></ha-icon>${this._t('devbar.rules')}
|
||||
</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn barclose" title=${this._t('title.close_editor')}
|
||||
@click=${() => this._setMode('view')}>
|
||||
<ha-icon icon="mdi:close"></ha-icon>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -240,13 +240,17 @@
|
||||
"gs.saved": "General settings saved",
|
||||
"space.show_lqi": "Show zigbee signal (LQI) next to devices",
|
||||
"gs.light_none": "No light sources",
|
||||
"mode.view": "View",
|
||||
"mode.plan": "Plan",
|
||||
"mode.devices": "Devices",
|
||||
"mode.plan": "Plan editor",
|
||||
"mode.devices": "Device editor",
|
||||
"display.value": "Value instead of an icon",
|
||||
"marker.subarea": "no area, manual",
|
||||
"device.new": "New device — open its editor to dismiss",
|
||||
"opening.unlock_action": "Unlock",
|
||||
"opening.lock_action": "Lock",
|
||||
"opening.lock_pending": "Working…"
|
||||
"opening.lock_pending": "Working…",
|
||||
"title.close_editor": "Close editor (back to view)",
|
||||
"devbar.add": "Add",
|
||||
"devbar.show_all": "Show all",
|
||||
"devbar.reset": "Reset",
|
||||
"devbar.rules": "Icon rules"
|
||||
}
|
||||
|
||||
+8
-4
@@ -240,13 +240,17 @@
|
||||
"gs.saved": "Общие настройки сохранены",
|
||||
"space.show_lqi": "Показывать зигби-сигнал (LQI) у устройств",
|
||||
"gs.light_none": "Нет источников света",
|
||||
"mode.view": "Просмотр",
|
||||
"mode.plan": "План",
|
||||
"mode.devices": "Устройства",
|
||||
"mode.plan": "Редактор плана",
|
||||
"mode.devices": "Редактор устройств",
|
||||
"display.value": "Значение вместо иконки",
|
||||
"marker.subarea": "без зоны, вручную",
|
||||
"device.new": "Новое устройство — откройте его редактор, чтобы снять отметку",
|
||||
"opening.unlock_action": "Открыть замок",
|
||||
"opening.lock_action": "Закрыть замок",
|
||||
"opening.lock_pending": "Выполняется…"
|
||||
"opening.lock_pending": "Выполняется…",
|
||||
"title.close_editor": "Закрыть редактор (вернуться к просмотру)",
|
||||
"devbar.add": "Добавить",
|
||||
"devbar.show_all": "Показать все",
|
||||
"devbar.reset": "Сброс",
|
||||
"devbar.rules": "Правила иконок"
|
||||
}
|
||||
|
||||
@@ -443,6 +443,20 @@ export const cardStyles = css`
|
||||
font-family: inherit;
|
||||
}
|
||||
.modetab ha-icon { --mdc-icon-size: 15px; }
|
||||
.modetab .closex {
|
||||
--mdc-icon-size: 13px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 2px;
|
||||
opacity: 0.75;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.modetab .closex:hover { opacity: 1; }
|
||||
.editbar .barclose {
|
||||
padding: 4px 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.modetab.active {
|
||||
background: var(--hp-accent);
|
||||
color: var(--text-primary-color, #fff);
|
||||
|
||||
Reference in New Issue
Block a user