mirror of
https://github.com/home-assistant/frontend.git
synced 2026-09-15 21:40:58 +00:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f3b919da0 | ||
|
|
c937c7af6e | ||
|
|
e29156d754 | ||
|
|
4d2a4b2c51 | ||
|
|
bbc9c03714 | ||
|
|
dbd4a1714d | ||
|
|
b570205c1c | ||
|
|
3405d8078f | ||
|
|
c16daeb619 | ||
|
|
b5de8071e3 | ||
|
|
7234ffaafa | ||
|
|
e023f2c691 | ||
|
|
c7cdafba29 | ||
|
|
e20485de7b | ||
|
|
a890cfec6e | ||
|
|
742ad1ba22 | ||
|
|
407e5fa1b4 | ||
|
|
03295dac1f | ||
|
|
ddada54553 | ||
|
|
4f57c943ff | ||
|
|
2b5ab81fa7 | ||
|
|
ce18d3745b | ||
|
|
956222c6df | ||
|
|
1d6419310b |
+1
-1
@@ -106,7 +106,7 @@
|
||||
"hls.js": "1.7.3",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.3.0",
|
||||
"intl-messageformat": "11.2.15",
|
||||
"intl-messageformat": "11.2.14",
|
||||
"js-yaml": "5.4.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
export const stopPropagation = (ev) => ev.stopPropagation();
|
||||
|
||||
export const stopKeydownEnterSpacePropagation = (ev: KeyboardEvent) => {
|
||||
if (ev.key === "Enter" || ev.key === " " || ev.key === "Spacebar") {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,8 +123,6 @@ interface ManagedMarker {
|
||||
draggable?: boolean;
|
||||
onDragEnd?: (location: MapLatLng) => void;
|
||||
dragging?: boolean;
|
||||
/** Pre-drag location; the next update echoing it is ignored (see setLocation) */
|
||||
staleLocation?: MapLatLng;
|
||||
mlMarker?: MapLibreMarker;
|
||||
decoration?: MapItemHandle;
|
||||
removed?: boolean;
|
||||
@@ -600,19 +598,10 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
},
|
||||
clusterData: options.clusterData,
|
||||
setLocation: (newLocation) => {
|
||||
// The user's hand wins while dragging; the host is the truth otherwise
|
||||
if (managed.dragging) {
|
||||
return;
|
||||
}
|
||||
// Skip one update echoing the pre-drag location (the save has not returned yet)
|
||||
const stale = managed.staleLocation;
|
||||
managed.staleLocation = undefined;
|
||||
if (
|
||||
stale &&
|
||||
newLocation[0] === stale[0] &&
|
||||
newLocation[1] === stale[1]
|
||||
) {
|
||||
return;
|
||||
}
|
||||
managed.location = newLocation;
|
||||
managed.mlMarker?.setLngLat([newLocation[1], newLocation[0]]);
|
||||
},
|
||||
@@ -663,7 +652,6 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
if (managed.draggable) {
|
||||
managed.mlMarker.on("dragstart", () => {
|
||||
managed.dragging = true;
|
||||
managed.staleLocation = managed.location;
|
||||
});
|
||||
managed.mlMarker.on("dragend", () => {
|
||||
managed.dragging = false;
|
||||
@@ -801,10 +789,8 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
resizeHandle?.setAttribute("aria-valuetext", radiusText);
|
||||
};
|
||||
|
||||
// Skip one update echoing the pre-edit values (the save has not returned yet)
|
||||
// The user's hand wins while dragging; the host is the truth otherwise
|
||||
let dragging = false;
|
||||
let staleCenter: MapLatLng | undefined;
|
||||
let staleRadius: number | undefined;
|
||||
|
||||
if (options.resizable) {
|
||||
const east = pointEastOf(center, options.radius);
|
||||
@@ -847,8 +833,6 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
if (keyboardRadius === undefined) {
|
||||
// Host updates treat a key resize like a drag
|
||||
dragging = true;
|
||||
staleCenter = currentCenter;
|
||||
staleRadius = currentRadius;
|
||||
}
|
||||
currentRadius = Math.max(
|
||||
1,
|
||||
@@ -875,19 +859,11 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
[centerMarker, resizeMarker].forEach((handleMarker) => {
|
||||
handleMarker?.on("dragstart", () => {
|
||||
dragging = true;
|
||||
staleCenter = currentCenter;
|
||||
staleRadius = currentRadius;
|
||||
});
|
||||
handleMarker?.on("dragend", () => {
|
||||
dragging = false;
|
||||
});
|
||||
});
|
||||
const isStale = (candidateCenter: MapLatLng, candidateRadius: number) =>
|
||||
staleCenter !== undefined &&
|
||||
staleRadius !== undefined &&
|
||||
candidateCenter[0] === staleCenter[0] &&
|
||||
candidateCenter[1] === staleCenter[1] &&
|
||||
candidateRadius === staleRadius;
|
||||
|
||||
if (options.moveable) {
|
||||
centerMarker.on("drag", () => {
|
||||
@@ -944,12 +920,6 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
if (dragging) {
|
||||
return;
|
||||
}
|
||||
const stale = isStale(newCenter, newRadius);
|
||||
staleCenter = undefined;
|
||||
staleRadius = undefined;
|
||||
if (stale) {
|
||||
return;
|
||||
}
|
||||
currentCenter = newCenter;
|
||||
currentRadius = newRadius;
|
||||
centerMarker.setLngLat([newCenter[1], newCenter[0]]);
|
||||
@@ -1219,13 +1189,16 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
const clusterable = this._markers.filter(
|
||||
(managed) => managed.options.cluster && !managed.removed
|
||||
);
|
||||
// A member that had focus hands it to the icon replacing it; read before
|
||||
// the open bubble holding it is removed
|
||||
// A member or bubble that had focus hands it to the icon replacing it;
|
||||
// read before the bubble holding it is removed
|
||||
const active = (
|
||||
this._map.getContainer().getRootNode() as Document | ShadowRoot
|
||||
).activeElement;
|
||||
const focusedMember = active
|
||||
? clusterable.find((managed) => managed.element.contains(active))
|
||||
? (clusterable.find((managed) => managed.element.contains(active)) ??
|
||||
this._clusterGroups.find((group) =>
|
||||
group.iconMarker?.getElement().contains(active)
|
||||
)?.members[0])
|
||||
: undefined;
|
||||
this._clusterGroups.forEach((group) => group.iconMarker?.remove());
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { getColorByIndex } from "../color/colors";
|
||||
import { computeDomain } from "../entity/compute_domain";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
|
||||
/**
|
||||
* Map colors for entities, by registry creation order, so an entity has the
|
||||
* same color on every map. The registry comes from the fullEntitiesContext;
|
||||
* entities without an entry (e.g. YAML zones) get a color derived from their
|
||||
* id.
|
||||
*/
|
||||
|
||||
export const HOME_ZONE_ENTITY_ID = "zone.home";
|
||||
|
||||
/** Domains whose entities are colored by creation order */
|
||||
const ORDERED_DOMAINS = ["zone", "person", "device_tracker"];
|
||||
|
||||
// One index per registry update, shared by every map on the page. Zones,
|
||||
// persons and trackers share one sequence, so a person never has the color
|
||||
// of the zone it is in.
|
||||
const creationIndex = memoizeOne(
|
||||
(entries: EntityRegistryEntry[]): Record<string, number> => {
|
||||
const index: Record<string, number> = {};
|
||||
entries
|
||||
.filter(
|
||||
(entry) =>
|
||||
ORDERED_DOMAINS.includes(computeDomain(entry.entity_id)) &&
|
||||
// The home zone has a fixed color and does not take a palette slot
|
||||
entry.entity_id !== HOME_ZONE_ENTITY_ID
|
||||
)
|
||||
.sort((a, b) => a.created_at - b.created_at || a.id.localeCompare(b.id))
|
||||
.forEach((entry, i) => {
|
||||
index[entry.entity_id] = i;
|
||||
});
|
||||
return index;
|
||||
}
|
||||
);
|
||||
|
||||
// For entities without a registry entry
|
||||
const hashIndex = (entityId: string): number => {
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < entityId.length; i++) {
|
||||
hash = (hash * 33 + entityId.charCodeAt(i)) % 2147483647;
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
/** The palette color of an entity on a map */
|
||||
export const entityMapColor = (
|
||||
entityId: string,
|
||||
entries: EntityRegistryEntry[],
|
||||
computedStyles: CSSStyleDeclaration
|
||||
): string =>
|
||||
getColorByIndex(
|
||||
creationIndex(entries)[entityId] ?? hashIndex(entityId),
|
||||
computedStyles
|
||||
);
|
||||
|
||||
/** A zone's color: primary for home, muted for passive, its entity map color otherwise */
|
||||
export const zoneColor = (
|
||||
entityId: string,
|
||||
passive: boolean,
|
||||
entries: EntityRegistryEntry[],
|
||||
computedStyles: CSSStyleDeclaration
|
||||
): string => {
|
||||
if (entityId === HOME_ZONE_ENTITY_ID) {
|
||||
return computedStyles.getPropertyValue("--primary-color");
|
||||
}
|
||||
if (passive) {
|
||||
return computedStyles.getPropertyValue("--secondary-text-color");
|
||||
}
|
||||
return entityMapColor(entityId, entries, computedStyles);
|
||||
};
|
||||
@@ -16,7 +16,11 @@ export const setMarkerAccessibility = (
|
||||
): void => {
|
||||
clearMarkerAccessibility(element);
|
||||
const owned: string[] = [];
|
||||
if (title && !element.hasAttribute("aria-label")) {
|
||||
if (
|
||||
title &&
|
||||
!element.hasAttribute("aria-label") &&
|
||||
!element.hasAttribute("aria-labelledby")
|
||||
) {
|
||||
element.setAttribute("aria-label", title);
|
||||
owned.push("aria-label");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { getContrastedColorHex } from "../color/rgb";
|
||||
|
||||
/** The zone marker: a colored circle with the zone's icon or initials, shared by the map and the zone editor */
|
||||
|
||||
export const ZONE_CIRCLE_SIZE = 36;
|
||||
|
||||
// Content color contrasting the fill; not every theme color parses
|
||||
export const contrastingZoneContent = (color: string): string => {
|
||||
try {
|
||||
return getContrastedColorHex(color.trim());
|
||||
} catch {
|
||||
return "#ffffff";
|
||||
}
|
||||
};
|
||||
|
||||
export const zoneInitials = (name: string): string =>
|
||||
name
|
||||
.split(" ")
|
||||
.map((part) => part[0])
|
||||
.join("")
|
||||
.slice(0, 2);
|
||||
|
||||
export const createZoneMarkerElement = (options: {
|
||||
color: string;
|
||||
icon?: string;
|
||||
/** Path for an ha-svg-icon, when there is no icon name */
|
||||
iconPath?: string;
|
||||
name: string;
|
||||
}): HTMLElement => {
|
||||
const element = document.createElement("div");
|
||||
element.className = "zone-circle";
|
||||
element.style.backgroundColor = options.color;
|
||||
element.style.color = contrastingZoneContent(options.color);
|
||||
if (options.icon) {
|
||||
const icon = document.createElement("ha-icon");
|
||||
icon.setAttribute("icon", options.icon);
|
||||
element.appendChild(icon);
|
||||
} else if (options.iconPath) {
|
||||
const icon = document.createElement("ha-svg-icon");
|
||||
icon.setAttribute("path", options.iconPath);
|
||||
element.appendChild(icon);
|
||||
} else {
|
||||
const initials = document.createElement("span");
|
||||
initials.textContent = zoneInitials(options.name);
|
||||
element.appendChild(initials);
|
||||
}
|
||||
return element;
|
||||
};
|
||||
|
||||
/** Styles for the zone marker, included by ha-map */
|
||||
export const zoneMarkerStyles = `
|
||||
.zone-circle {
|
||||
width: ${ZONE_CIRCLE_SIZE}px;
|
||||
height: ${ZONE_CIRCLE_SIZE}px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid var(--card-background-color, #fff);
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--ha-box-shadow-s);
|
||||
overflow: hidden;
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
--mdc-icon-size: ${ZONE_CIRCLE_SIZE / 2}px;
|
||||
}
|
||||
`;
|
||||
@@ -24,11 +24,13 @@ class HaEntityMarker extends LitElement {
|
||||
|
||||
@property({ attribute: "show-icon", type: Boolean }) public showIcon = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public selected = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
class="marker ${this.entityPicture ? "picture" : ""}"
|
||||
style=${styleMap({ "border-color": this.entityColor })}
|
||||
style=${styleMap({ "--ha-marker-selected-color": this.entityColor })}
|
||||
@click=${this._badgeTap}
|
||||
>
|
||||
${
|
||||
@@ -94,13 +96,22 @@ class HaEntityMarker extends LitElement {
|
||||
height: var(--ha-marker-size, 48px);
|
||||
font-size: var(--ha-marker-font-size, var(--ha-font-size-xl));
|
||||
border-radius: var(--ha-marker-border-radius, 50%);
|
||||
border: 1px solid var(--ha-marker-color, var(--primary-color));
|
||||
border: var(--ha-marker-border-width, 3px) solid
|
||||
var(--ha-marker-color, var(--card-background-color, #fff));
|
||||
box-shadow: var(--ha-marker-shadow, var(--ha-box-shadow-s));
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(--card-background-color);
|
||||
background-color: var(
|
||||
--ha-marker-background,
|
||||
var(--card-background-color)
|
||||
);
|
||||
}
|
||||
.marker.picture {
|
||||
overflow: hidden;
|
||||
}
|
||||
/* A ring in the entity color outside the frame marks the selected marker */
|
||||
:host([selected]) .marker {
|
||||
outline: 3px solid var(--ha-marker-selected-color, var(--primary-color));
|
||||
}
|
||||
.entity-picture {
|
||||
background-size: cover;
|
||||
height: 100%;
|
||||
|
||||
@@ -8,14 +8,22 @@ import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { MAP_MAX_ZOOM } from "../../common/map/base-layer";
|
||||
import type { MapLatLng } from "../../common/map/map-engine";
|
||||
import { circleBoundsPoints } from "../../common/map/map-engine";
|
||||
import { internationalizationContext } from "../../data/context";
|
||||
import type { HomeAssistantInternationalization, ThemeMode } from "../../types";
|
||||
import { internationalizationContext, uiContext } from "../../data/context";
|
||||
import type {
|
||||
HomeAssistantInternationalization,
|
||||
HomeAssistantUI,
|
||||
ThemeMode,
|
||||
} from "../../types";
|
||||
import "../ha-alert";
|
||||
import "../ha-input-helper-text";
|
||||
import "./ha-map";
|
||||
import type { HaMap, HaMapEditableLocation } from "./ha-map";
|
||||
import type { HaIcon } from "../ha-icon";
|
||||
import type { HaSvgIcon } from "../ha-svg-icon";
|
||||
import {
|
||||
createZoneMarkerElement,
|
||||
ZONE_CIRCLE_SIZE,
|
||||
} from "../../common/map/zone-marker";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -68,6 +76,11 @@ export class HaLocationsEditor extends LitElement {
|
||||
|
||||
@state() private _editingAvailable = true;
|
||||
|
||||
// Marker elements bake in theme colors, so they are rebuilt on a theme change
|
||||
@state()
|
||||
@consume({ context: uiContext, subscribe: true })
|
||||
private _ui?: HomeAssistantUI;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n?: HomeAssistantInternationalization;
|
||||
@@ -108,7 +121,10 @@ export class HaLocationsEditor extends LitElement {
|
||||
return html`
|
||||
<div class="map">
|
||||
<ha-map
|
||||
.editableLocations=${this._editableLocations(this.locations)}
|
||||
.editableLocations=${this._editableLocations(
|
||||
this.locations,
|
||||
this._ui?.themes
|
||||
)}
|
||||
.zoom=${this.zoom}
|
||||
.autoFit=${this.autoFit}
|
||||
.themeMode=${this.themeMode}
|
||||
@@ -140,7 +156,14 @@ export class HaLocationsEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _editableLocations = memoizeOne(
|
||||
(locations?: MarkerLocation[]): HaMapEditableLocation[] => {
|
||||
(
|
||||
locations: MarkerLocation[] | undefined,
|
||||
themes: HomeAssistantUI["themes"] | undefined
|
||||
): HaMapEditableLocation[] => {
|
||||
if (themes !== this._elementsThemes) {
|
||||
this._elementsThemes = themes;
|
||||
this._elements.clear();
|
||||
}
|
||||
const ids = new Set((locations ?? []).map((location) => location.id));
|
||||
for (const id of this._elements.keys()) {
|
||||
if (!ids.has(id)) {
|
||||
@@ -152,7 +175,9 @@ export class HaLocationsEditor extends LitElement {
|
||||
location: [location.latitude, location.longitude],
|
||||
radius: location.radius,
|
||||
element: this._elementFor(location),
|
||||
elementSize: [ICON_SIZE, ICON_SIZE],
|
||||
elementSize: location.radius
|
||||
? [ZONE_CIRCLE_SIZE, ZONE_CIRCLE_SIZE]
|
||||
: [ICON_SIZE, ICON_SIZE],
|
||||
title: location.name,
|
||||
color: location.radius_color,
|
||||
locationEditable: location.location_editable,
|
||||
@@ -165,30 +190,46 @@ export class HaLocationsEditor extends LitElement {
|
||||
// Reused while unchanged, so ha-map moves markers instead of rebuilding them
|
||||
private _elements = new Map<string, { key: string; element?: HTMLElement }>();
|
||||
|
||||
private _elementsThemes?: HomeAssistantUI["themes"];
|
||||
|
||||
private _elementFor(location: MarkerLocation): HTMLElement | undefined {
|
||||
const isZone = !!location.radius;
|
||||
const key = JSON.stringify([
|
||||
isZone,
|
||||
location.icon,
|
||||
location.iconPath,
|
||||
location.name,
|
||||
location.location_editable,
|
||||
location.radius_color,
|
||||
]);
|
||||
const cached = this._elements.get(location.id);
|
||||
if (cached?.key === key) {
|
||||
return cached.element;
|
||||
}
|
||||
const element = this._createIcon(location);
|
||||
// The zone marker doubles as the circle's draggable center
|
||||
const element = isZone
|
||||
? this._createZoneMarker(location)
|
||||
: this._createIcon(location);
|
||||
this._elements.set(location.id, { key, element });
|
||||
return element;
|
||||
}
|
||||
|
||||
private _createZoneMarker(location: MarkerLocation): HTMLElement {
|
||||
return createZoneMarkerElement({
|
||||
color:
|
||||
location.radius_color ||
|
||||
getComputedStyle(this).getPropertyValue("--accent-color"),
|
||||
icon: location.icon,
|
||||
iconPath: location.iconPath,
|
||||
name: location.name ?? "",
|
||||
});
|
||||
}
|
||||
|
||||
private _createIcon(location: MarkerLocation): HTMLElement | undefined {
|
||||
if (!location.icon && !location.iconPath) {
|
||||
return undefined;
|
||||
}
|
||||
const el = document.createElement("div");
|
||||
el.className = `named-icon ${
|
||||
location.location_editable ? "draggable" : ""
|
||||
}`;
|
||||
el.className = "named-icon";
|
||||
if (location.name !== undefined) {
|
||||
el.innerText = location.name;
|
||||
}
|
||||
@@ -296,12 +337,13 @@ export class HaLocationsEditor extends LitElement {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
/* Over the map, clear of the zoom control, so a fixed-height host shows it */
|
||||
/* Over the map, clear of the zoom control, so a fixed-height host shows it.
|
||||
The control sits at the physical top-left in either direction. */
|
||||
ha-alert {
|
||||
position: absolute;
|
||||
top: var(--ha-space-2);
|
||||
inset-inline-start: 56px;
|
||||
inset-inline-end: var(--ha-space-2);
|
||||
left: 56px;
|
||||
right: var(--ha-space-2);
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
+294
-68
@@ -1,4 +1,4 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { consume, ContextConsumer } from "@lit/context";
|
||||
import { isToday } from "date-fns";
|
||||
import type { HassConfig, HassEntities } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
@@ -31,15 +31,23 @@ import type {
|
||||
} from "../../common/map/map-engine";
|
||||
import { circleBoundsPoints } from "../../common/map/map-engine";
|
||||
import { editableCircleStyles } from "../../common/map/editable-circle";
|
||||
import { entityMapColor, zoneColor } from "../../common/map/entity-map-colors";
|
||||
import {
|
||||
createZoneMarkerElement,
|
||||
ZONE_CIRCLE_SIZE,
|
||||
zoneMarkerStyles,
|
||||
} from "../../common/map/zone-marker";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import {
|
||||
configContext,
|
||||
connectionContext,
|
||||
formattersContext,
|
||||
fullEntitiesContext,
|
||||
internationalizationContext,
|
||||
statesContext,
|
||||
uiContext,
|
||||
} from "../../data/context";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import { ensureMapTilesToken } from "../../data/map_tiles";
|
||||
import type {
|
||||
HomeAssistantConfig,
|
||||
@@ -211,9 +219,37 @@ export interface HaMapEntity {
|
||||
unit?: string;
|
||||
name?: string;
|
||||
focus?: boolean;
|
||||
hide_accuracy?: boolean;
|
||||
hide_radius?: boolean;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
// Data carried by entity markers for rendering cluster bubbles
|
||||
interface ClusterData {
|
||||
entityId: string;
|
||||
picture?: string;
|
||||
label: string;
|
||||
showIcon: boolean;
|
||||
unit: string;
|
||||
color?: string;
|
||||
selected: boolean;
|
||||
zoneId?: string;
|
||||
}
|
||||
|
||||
const CLUSTER_AVATAR_SIZE = 32;
|
||||
const CLUSTER_BUBBLE_PADDING = 6;
|
||||
const CLUSTER_BUBBLE_GAP = 4;
|
||||
const CLUSTER_MAX_AVATARS = 3;
|
||||
const CLUSTER_MORE_WIDTH = 28;
|
||||
const CLUSTER_MORE_MAX = 99;
|
||||
const CLUSTER_TAIL_SIZE = 10;
|
||||
// The tail is a rotated square on the bubble's bottom edge, reaching half its diagonal below
|
||||
const CLUSTER_TAIL_HEIGHT = Math.round((CLUSTER_TAIL_SIZE * Math.SQRT2) / 2);
|
||||
const CLUSTER_ZONE_SPACING = 2;
|
||||
const CLUSTER_RADIUS = 40;
|
||||
// Same-zone markers share the zone's bubble while they span at most this many
|
||||
// pixels; further apart they show their actual positions
|
||||
const ZONE_GROUP_RADIUS = 160;
|
||||
|
||||
@customElement("ha-map")
|
||||
export class HaMap extends ReactiveElement {
|
||||
@@ -264,6 +300,8 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
@property({ attribute: "fit-zones", type: Boolean }) public fitZones = false;
|
||||
|
||||
private _zonePositions: Record<string, MapLatLng> = {};
|
||||
|
||||
@property({ attribute: "theme-mode", type: String })
|
||||
public themeMode: ThemeMode = "auto";
|
||||
|
||||
@@ -296,6 +334,11 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
|
||||
// Registry creation order decides the palette colors
|
||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||
|
||||
private _registryConsumer?: ContextConsumer<typeof fullEntitiesContext, this>;
|
||||
|
||||
private _entityHandles: MapMarkerHandle[] = [];
|
||||
|
||||
private _zoneHandles: MapItemHandle[] = [];
|
||||
@@ -323,6 +366,21 @@ export class HaMap extends ReactiveElement {
|
||||
this._attachObserver();
|
||||
}
|
||||
|
||||
// Only maps that draw entities ask for the registry; an editor's map, as in
|
||||
// onboarding, never does. The context provider shares one subscription.
|
||||
private _watchRegistry(): void {
|
||||
if (this._registryConsumer || !this.entities?.length) {
|
||||
return;
|
||||
}
|
||||
this._registryConsumer = new ContextConsumer(this, {
|
||||
context: fullEntitiesContext,
|
||||
subscribe: true,
|
||||
callback: (entries) => {
|
||||
this._entityReg = entries;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _handleVisibilityChange = async () => {
|
||||
if (!document.hidden) {
|
||||
setTimeout(() => {
|
||||
@@ -385,7 +443,7 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("clusterMarkers")) {
|
||||
if (changedProps.has("clusterMarkers") || changedProps.has("_entityReg")) {
|
||||
this._drawEntities();
|
||||
}
|
||||
|
||||
@@ -401,6 +459,11 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
if (changedProps.has("_loaded") || changedProps.has("paths")) {
|
||||
this._drawPaths();
|
||||
// Cluster bubbles show entity colors only while trails are visible
|
||||
const oldPaths = changedProps.get("paths") as HaMapPaths[] | undefined;
|
||||
if (!!oldPaths?.length !== !!this.paths?.length) {
|
||||
this._engine?.refreshClusters();
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("_loaded") || changedProps.has("editableLocations")) {
|
||||
@@ -433,13 +496,19 @@ export class HaMap extends ReactiveElement {
|
||||
const oldUi = changedProps.get("_ui") as HomeAssistantUI | undefined;
|
||||
if (
|
||||
!changedProps.has("themeMode") &&
|
||||
(!changedProps.has("_ui") ||
|
||||
(oldUi && oldUi.themes?.darkMode === this._ui.themes?.darkMode))
|
||||
(!changedProps.has("_ui") || (oldUi && oldUi.themes === this._ui.themes))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateMapStyle();
|
||||
// Marker, trail and circle colors were resolved from the theme when drawn
|
||||
this._drawEntities();
|
||||
this._drawPaths();
|
||||
if (this._editableHandles.size) {
|
||||
this._removeEditableLocations();
|
||||
this._drawEditableLocations();
|
||||
}
|
||||
}
|
||||
|
||||
private get _darkMode() {
|
||||
@@ -1031,16 +1100,36 @@ export class HaMap extends ReactiveElement {
|
||||
engine.setClustering(null);
|
||||
return;
|
||||
}
|
||||
this._watchRegistry();
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const zoneColor = computedStyles.getPropertyValue("--accent-color");
|
||||
const passiveZoneColor = computedStyles.getPropertyValue(
|
||||
"--secondary-text-color"
|
||||
);
|
||||
|
||||
const darkPrimaryColor = computedStyles.getPropertyValue(
|
||||
"--dark-primary-color"
|
||||
);
|
||||
// A person's state is "home" for the home zone, the zone name otherwise
|
||||
const zoneByState: Record<string, string> = {};
|
||||
this._zonePositions = {};
|
||||
for (const entity of this.entities) {
|
||||
const stateObj = states[getEntityId(entity)];
|
||||
// A zone that is not drawn cannot anchor a bubble either
|
||||
if (
|
||||
stateObj &&
|
||||
computeStateDomain(stateObj) === "zone" &&
|
||||
(this.renderPassive || !stateObj.attributes.passive)
|
||||
) {
|
||||
zoneByState[
|
||||
stateObj.entity_id === "zone.home"
|
||||
? "home"
|
||||
: computeStateName(stateObj)
|
||||
] = stateObj.entity_id;
|
||||
if (
|
||||
typeof stateObj.attributes.latitude === "number" &&
|
||||
typeof stateObj.attributes.longitude === "number"
|
||||
) {
|
||||
this._zonePositions[stateObj.entity_id] = [
|
||||
stateObj.attributes.latitude,
|
||||
stateObj.attributes.longitude,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of this.entities) {
|
||||
const stateObj = states[getEntityId(entity)];
|
||||
@@ -1069,26 +1158,29 @@ export class HaMap extends ReactiveElement {
|
||||
continue;
|
||||
}
|
||||
|
||||
const zoneMarkerColor = passive ? passiveZoneColor : zoneColor;
|
||||
const hideRadius = typeof entity !== "string" && entity.hide_radius;
|
||||
// A host-set color wins, except passive zones are always muted
|
||||
const markerColor =
|
||||
!passive && typeof entity !== "string" && entity.color
|
||||
? entity.color
|
||||
: zoneColor(
|
||||
stateObj.entity_id,
|
||||
!!passive,
|
||||
this._entityReg,
|
||||
computedStyles
|
||||
);
|
||||
|
||||
if (radius) {
|
||||
if (!hideRadius && radius) {
|
||||
this._zoneHandles.push(
|
||||
engine.addCircle(position, { radius, color: zoneMarkerColor })
|
||||
engine.addCircle(position, { radius, color: markerColor })
|
||||
);
|
||||
}
|
||||
|
||||
// create icon
|
||||
const iconEl = document.createElement("div");
|
||||
iconEl.className = `zone-icon ${this._darkMode ? "dark" : "light"}`;
|
||||
if (icon) {
|
||||
const el = document.createElement("ha-icon");
|
||||
el.setAttribute("icon", icon);
|
||||
iconEl.appendChild(el);
|
||||
} else {
|
||||
const el = document.createElement("span");
|
||||
el.textContent = title;
|
||||
iconEl.appendChild(el);
|
||||
}
|
||||
const circleEl = createZoneMarkerElement({
|
||||
color: markerColor,
|
||||
icon,
|
||||
name: title,
|
||||
});
|
||||
|
||||
if (this.interactiveZones) {
|
||||
const openMoreInfo = (ev: Event) => {
|
||||
@@ -1097,8 +1189,8 @@ export class HaMap extends ReactiveElement {
|
||||
entityId: stateObj.entity_id,
|
||||
});
|
||||
};
|
||||
iconEl.addEventListener("click", openMoreInfo);
|
||||
iconEl.addEventListener("keydown", (ev) => {
|
||||
circleEl.addEventListener("click", openMoreInfo);
|
||||
circleEl.addEventListener("keydown", (ev) => {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.preventDefault();
|
||||
openMoreInfo(ev);
|
||||
@@ -1106,10 +1198,9 @@ export class HaMap extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
const zoneIconSize = this._getMarkerSize(computedStyles) / 2;
|
||||
this._zoneHandles.push(
|
||||
engine.addMarker(iconEl, position, {
|
||||
size: [zoneIconSize, zoneIconSize],
|
||||
engine.addMarker(circleEl, position, {
|
||||
size: [ZONE_CIRCLE_SIZE, ZONE_CIRCLE_SIZE],
|
||||
interactive: this.interactiveZones,
|
||||
title,
|
||||
})
|
||||
@@ -1119,7 +1210,7 @@ export class HaMap extends ReactiveElement {
|
||||
this.fitZones &&
|
||||
(typeof entity === "string" || entity.focus !== false)
|
||||
) {
|
||||
if (radius) {
|
||||
if (!hideRadius && radius) {
|
||||
this._focusZonePoints.push(...circleBoundsPoints(position, radius));
|
||||
} else {
|
||||
this._focusZonePoints.push(position);
|
||||
@@ -1163,19 +1254,42 @@ export class HaMap extends ReactiveElement {
|
||||
entityPicture && (typeof entity === "string" || !entity.label_mode)
|
||||
? this._connection.hassUrl(entityPicture)
|
||||
: "";
|
||||
// A host may leave the color to the map
|
||||
const entityColor =
|
||||
(typeof entity !== "string" ? entity.color : undefined) ||
|
||||
entityMapColor(getEntityId(entity), this._entityReg, computedStyles);
|
||||
entityMarker.entityColor = entityColor;
|
||||
if (typeof entity !== "string") {
|
||||
entityMarker.entityColor = entity.color;
|
||||
entityMarker.selected = entity.selected ?? false;
|
||||
}
|
||||
|
||||
const clusterData: ClusterData = {
|
||||
entityId: getEntityId(entity),
|
||||
picture: entityMarker.entityPicture || undefined,
|
||||
label: entityName,
|
||||
showIcon: entityMarker.showIcon,
|
||||
unit: entityMarker.entityUnit ?? "",
|
||||
color: entityColor,
|
||||
selected: typeof entity !== "string" && (entity.selected ?? false),
|
||||
zoneId: ["person", "device_tracker"].includes(
|
||||
computeStateDomain(stateObj)
|
||||
)
|
||||
? zoneByState[stateObj.state]
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const showAccuracy =
|
||||
!!gpsAccuracy && !(typeof entity !== "string" && entity.hide_accuracy);
|
||||
|
||||
const markerSize = this._getMarkerSize(computedStyles);
|
||||
this._entityHandles.push(
|
||||
engine.addMarker(entityMarker, position, {
|
||||
size: [markerSize, markerSize],
|
||||
title,
|
||||
cluster: true,
|
||||
// create circle around if entity has accuracy
|
||||
decoration: gpsAccuracy
|
||||
? { radius: gpsAccuracy, color: darkPrimaryColor }
|
||||
clusterData,
|
||||
decoration: showAccuracy
|
||||
? { radius: gpsAccuracy!, color: entityColor }
|
||||
: undefined,
|
||||
})
|
||||
);
|
||||
@@ -1187,22 +1301,97 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
engine.setClustering(
|
||||
this.clusterMarkers
|
||||
? { radius: CLUSTER_RADIUS, iconBuilder: this._createClusterIcon }
|
||||
? {
|
||||
radius: CLUSTER_RADIUS,
|
||||
iconBuilder: this._createClusterBubble,
|
||||
// Everyone in a zone shares its bubble until zooming spreads them
|
||||
groupKey: (marker) => (marker.clusterData as ClusterData)?.zoneId,
|
||||
groupRadius: ZONE_GROUP_RADIUS,
|
||||
}
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
// Renders a marker cluster as a circle with the member count
|
||||
private _createClusterIcon = (members: MapMarkerHandle[]): MapClusterIcon => {
|
||||
const size = Math.round(
|
||||
this._getMarkerSize(getComputedStyle(this)) * (2 / 3)
|
||||
);
|
||||
const element = document.createElement("div");
|
||||
element.className = "marker-cluster";
|
||||
const count = document.createElement("span");
|
||||
count.textContent = String(members.length);
|
||||
element.appendChild(count);
|
||||
return { element, size: [size, size] };
|
||||
// Renders a marker cluster as a bubble of its members' avatars
|
||||
private _createClusterBubble = (
|
||||
members: MapMarkerHandle[]
|
||||
): MapClusterIcon => {
|
||||
const data = members.map((member) => member.clusterData as ClusterData);
|
||||
const shown = data.slice(0, CLUSTER_MAX_AVATARS);
|
||||
const hidden = data.length - shown.length;
|
||||
|
||||
// With history trails shown, colored borders match avatars to trails
|
||||
const showColors = !!this.paths?.length;
|
||||
|
||||
const bubble = document.createElement("div");
|
||||
bubble.className = "cluster-bubble";
|
||||
for (const member of shown) {
|
||||
const avatar = document.createElement("ha-entity-marker");
|
||||
avatar.entityId = member?.entityId;
|
||||
avatar.entityName = member?.label ?? "";
|
||||
avatar.entityUnit = member?.unit ?? "";
|
||||
avatar.showIcon = member?.showIcon ?? false;
|
||||
avatar.entityPicture = member?.picture ?? "";
|
||||
avatar.entityColor = member?.color;
|
||||
if (showColors) {
|
||||
avatar.style.setProperty(
|
||||
"--ha-marker-color",
|
||||
member?.color ?? "var(--primary-color)"
|
||||
);
|
||||
avatar.style.setProperty("--ha-marker-border-width", "2px");
|
||||
}
|
||||
if (member?.selected) {
|
||||
avatar.selected = true;
|
||||
}
|
||||
bubble.appendChild(avatar);
|
||||
}
|
||||
|
||||
let width =
|
||||
shown.length * CLUSTER_AVATAR_SIZE +
|
||||
(shown.length - 1) * CLUSTER_BUBBLE_GAP +
|
||||
2 * CLUSTER_BUBBLE_PADDING;
|
||||
if (hidden > 0) {
|
||||
const more = document.createElement("span");
|
||||
more.className = "more";
|
||||
more.textContent =
|
||||
hidden > CLUSTER_MORE_MAX ? `${CLUSTER_MORE_MAX}+` : `+${hidden}`;
|
||||
bubble.appendChild(more);
|
||||
width += CLUSTER_MORE_WIDTH + CLUSTER_BUBBLE_GAP;
|
||||
}
|
||||
|
||||
// A cluster of one zone's occupants attaches to that zone's marker
|
||||
const zoneId = data[0]?.zoneId;
|
||||
const zonePosition = zoneId ? this._zonePositions[zoneId] : undefined;
|
||||
const atZone =
|
||||
!!zoneId &&
|
||||
!!zonePosition &&
|
||||
data.every((member) => member?.zoneId === zoneId);
|
||||
|
||||
let height = CLUSTER_AVATAR_SIZE + 2 * CLUSTER_BUBBLE_PADDING;
|
||||
let root: HTMLElement = bubble;
|
||||
if (atZone) {
|
||||
root = document.createElement("div");
|
||||
root.className = "cluster-marker";
|
||||
const tail = document.createElement("div");
|
||||
tail.className = "cluster-bubble-tail";
|
||||
root.append(bubble, tail);
|
||||
height += CLUSTER_TAIL_HEIGHT;
|
||||
}
|
||||
|
||||
return {
|
||||
element: root,
|
||||
size: [width, height],
|
||||
// Float above the zone circle, tail pointing at it
|
||||
...(atZone && zonePosition
|
||||
? {
|
||||
location: zonePosition,
|
||||
anchor: [
|
||||
width / 2,
|
||||
height + ZONE_CIRCLE_SIZE / 2 + CLUSTER_ZONE_SPACING,
|
||||
] as [number, number],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
||||
private _drawScaleRuler(): void {
|
||||
@@ -1263,6 +1452,7 @@ export class HaMap extends ReactiveElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
isolation: isolate;
|
||||
}
|
||||
.cluster-open-members {
|
||||
display: flex;
|
||||
@@ -1276,6 +1466,13 @@ export class HaMap extends ReactiveElement {
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--ha-box-shadow-s);
|
||||
}
|
||||
/* Both tails are a rotated square whose upper half sits under the bubble;
|
||||
drawn behind it, so it never covers a member's frame or selected ring */
|
||||
.cluster-open-tail,
|
||||
.cluster-bubble-tail {
|
||||
position: relative;
|
||||
z-index: -1;
|
||||
}
|
||||
.cluster-open-tail {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@@ -1349,17 +1546,59 @@ export class HaMap extends ReactiveElement {
|
||||
.leaflet-pane {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
/* Zone icons, sized like the Leaflet divIcon they replaced */
|
||||
.zone-icon {
|
||||
.cluster-marker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
isolation: isolate;
|
||||
}
|
||||
.cluster-bubble-tail {
|
||||
width: ${CLUSTER_TAIL_SIZE}px;
|
||||
height: ${CLUSTER_TAIL_SIZE}px;
|
||||
margin-top: ${-CLUSTER_TAIL_SIZE / 2}px;
|
||||
border-radius: 2px;
|
||||
background: var(--card-background-color, #fff);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.cluster-bubble {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${CLUSTER_BUBBLE_GAP}px;
|
||||
padding: ${CLUSTER_BUBBLE_PADDING}px;
|
||||
box-sizing: border-box;
|
||||
background: var(--card-background-color, #fff);
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--ha-box-shadow-s);
|
||||
--ha-marker-size: ${CLUSTER_AVATAR_SIZE}px;
|
||||
--ha-marker-color: transparent;
|
||||
--ha-marker-border-width: 1px;
|
||||
--ha-marker-shadow: none;
|
||||
--ha-marker-font-size: var(--ha-font-size-s);
|
||||
/* distinguish letter tiles from the bubble background */
|
||||
--ha-marker-background: var(--ha-color-fill-neutral-quiet-resting);
|
||||
}
|
||||
.cluster-bubble .more {
|
||||
flex: none;
|
||||
width: ${CLUSTER_MORE_WIDTH}px;
|
||||
height: ${CLUSTER_AVATAR_SIZE}px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: var(--primary-text-color);
|
||||
border-radius: 10px;
|
||||
background: var(--ha-color-fill-neutral-quiet-resting, #f0f0f0);
|
||||
color: var(--primary-text-color, #212121);
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
.zone-icon.dark {
|
||||
color: #ffffff;
|
||||
/* Markers are rounded squares to match the cluster bubble avatars */
|
||||
ha-entity-marker {
|
||||
--ha-marker-border-radius: var(--ha-border-radius-lg);
|
||||
}
|
||||
.cluster-bubble ha-entity-marker {
|
||||
flex: none;
|
||||
--ha-marker-border-radius: 10px;
|
||||
}
|
||||
${unsafeCSS(zoneMarkerStyles)}
|
||||
.leaflet-control,
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
@@ -1410,19 +1649,6 @@ export class HaMap extends ReactiveElement {
|
||||
ha-icon {
|
||||
--mdc-icon-size: calc(var(--ha-marker-size, 48px) / 2);
|
||||
}
|
||||
|
||||
.marker-cluster {
|
||||
box-sizing: border-box;
|
||||
background-clip: padding-box;
|
||||
background-color: var(--primary-color);
|
||||
border: 3px solid rgba(var(--rgb-primary-color), 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-primary-color);
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
+1
-19
@@ -1,6 +1,4 @@
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
||||
import type { Context, HomeAssistant, ServiceCallRequest } from "../types";
|
||||
import type { Context, HomeAssistant } from "../types";
|
||||
import type { Action } from "./script";
|
||||
|
||||
export const callExecuteScript = (
|
||||
@@ -24,19 +22,3 @@ export const serviceCallWillDisconnect = (
|
||||
"update.home_assistant_core_update",
|
||||
"update.home_assistant_operating_system_update",
|
||||
].includes(serviceData?.entity_id));
|
||||
|
||||
// Core merges the target into the service data, so a target entity_id
|
||||
// replaces the legacy service data one rather than adding to it. Its schema
|
||||
// also accepts comma separated ids and lowercases them.
|
||||
export const getServiceCallEntityIds = (
|
||||
serviceData?: ServiceCallRequest["serviceData"],
|
||||
target?: ServiceCallRequest["target"]
|
||||
): string[] => [
|
||||
...new Set(
|
||||
(ensureArray(target?.entity_id ?? serviceData?.entity_id) ?? [])
|
||||
.filter((id): id is string => typeof id === "string")
|
||||
.flatMap((id) => id.split(","))
|
||||
.map((id) => id.trim().toLowerCase())
|
||||
.filter(isValidEntityId)
|
||||
),
|
||||
];
|
||||
|
||||
@@ -184,15 +184,6 @@ interface EMOutgoingMessageAddEntityTo extends EMMessage {
|
||||
};
|
||||
}
|
||||
|
||||
interface EMOutgoingMessageEntityControlled extends EMMessage {
|
||||
type: "entity/controlled";
|
||||
payload: {
|
||||
entity_ids: string[];
|
||||
domain: string;
|
||||
service: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface EMOutgoingMessageMoreInfoOpened extends EMMessage {
|
||||
type: "more_info/opened";
|
||||
payload: {
|
||||
@@ -248,7 +239,6 @@ type EMOutgoingMessageWithoutAnswer =
|
||||
| EMOutgoingMessageImprovScan
|
||||
| EMOutgoingMessageImprovConfigureDevice
|
||||
| EMOutgoingMessageAddEntityTo
|
||||
| EMOutgoingMessageEntityControlled
|
||||
| EMOutgoingMessageFocusElement
|
||||
| EMOutgoingMessageReloadAndClearCache
|
||||
| EMOutgoingMessageAssistSettings;
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
mdiVideo,
|
||||
mdiWebhook,
|
||||
} from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { formatDate } from "../../../../common/datetime/format_date";
|
||||
import { relativeTime } from "../../../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@@ -20,13 +20,11 @@ import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/item/ha-list-item-base";
|
||||
import "../../../../components/item/ha-list-item-button";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import "../../../../components/list/ha-list-nav";
|
||||
import type { BackupConfig } from "../../../../data/backup";
|
||||
import { cloudBackupEnabled, cloudBackupHealth } from "../../../../data/backup";
|
||||
import { cloudBackupHealth, cloudBackupEnabled } from "../../../../data/backup";
|
||||
import type {
|
||||
CloudStatusLoggedIn,
|
||||
SubscriptionInfo,
|
||||
@@ -262,8 +260,8 @@ export class CloudAccountOverview extends LitElement {
|
||||
/>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.email"
|
||||
@@ -285,16 +283,16 @@ export class CloudAccountOverview extends LitElement {
|
||||
@click=${this._toggleEmail}
|
||||
></ha-icon-button>
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.subscription"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">${this._subscriptionDetail()}</span>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
<div class="card-actions split">
|
||||
<ha-button
|
||||
@@ -350,11 +348,7 @@ export class CloudAccountOverview extends LitElement {
|
||||
"ui.panel.config.cloud.account.overview.features_intro"
|
||||
)}
|
||||
</p>
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.overview.features_title"
|
||||
)}
|
||||
>
|
||||
<ha-md-list>
|
||||
${this._featureRow(
|
||||
mdiEarth,
|
||||
this.hass.localize(
|
||||
@@ -417,7 +411,7 @@ export class CloudAccountOverview extends LitElement {
|
||||
webhookStatus,
|
||||
"/config/cloud/webhooks"
|
||||
)}
|
||||
</ha-list-nav>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -430,12 +424,12 @@ export class CloudAccountOverview extends LitElement {
|
||||
href: string
|
||||
): TemplateResult {
|
||||
return html`
|
||||
<ha-list-item-button href=${href}>
|
||||
<ha-md-list-item type="link" href=${href}>
|
||||
<ha-svg-icon slot="start" .path=${icon}></ha-svg-icon>
|
||||
<span slot="headline">${title}</span>
|
||||
<span slot="supporting-text">${supporting}</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -667,6 +661,9 @@ export class CloudAccountOverview extends LitElement {
|
||||
width: 36px;
|
||||
height: auto;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
.muted {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@@ -695,8 +692,10 @@ export class CloudAccountOverview extends LitElement {
|
||||
.status-dot.disabled {
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
ha-list-base {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
.extras-header {
|
||||
display: flex;
|
||||
@@ -722,12 +721,14 @@ export class CloudAccountOverview extends LitElement {
|
||||
padding-inline: var(--ha-space-4);
|
||||
margin-top: 0;
|
||||
}
|
||||
.extras-content ha-list-nav {
|
||||
.extras-content ha-md-list {
|
||||
padding-top: 0;
|
||||
--ha-row-item-padding-inline: var(--ha-space-4);
|
||||
--md-list-item-leading-space: var(--ha-space-4);
|
||||
--md-list-item-trailing-space: var(--ha-space-4);
|
||||
}
|
||||
.extras-content ha-list-item-button {
|
||||
--ha-row-item-padding-block: var(--ha-space-2);
|
||||
.extras-content ha-md-list-item {
|
||||
--md-list-item-top-space: var(--ha-space-2);
|
||||
--md-list-item-bottom-space: var(--ha-space-2);
|
||||
}
|
||||
.extras-content ha-svg-icon[slot="start"],
|
||||
.extras-content ha-icon-next {
|
||||
|
||||
@@ -11,9 +11,9 @@ import { relativeTime } from "../../../../common/datetime/relative_time";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-spinner";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/item/ha-list-item-base";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import type { BackupConfig, BackupInfo } from "../../../../data/backup";
|
||||
import {
|
||||
CLOUD_AGENT,
|
||||
@@ -137,8 +137,8 @@ export class CloudBackupPref extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${
|
||||
@@ -159,11 +159,11 @@ export class CloudBackupPref extends LitElement {
|
||||
)
|
||||
}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
cloudAgent
|
||||
? html`
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiHarddisk}
|
||||
@@ -171,8 +171,8 @@ export class CloudBackupPref extends LitElement {
|
||||
<span slot="headline">
|
||||
${bytesToString(cloudAgent.size)}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiShieldLock}
|
||||
@@ -188,11 +188,11 @@ export class CloudBackupPref extends LitElement {
|
||||
)
|
||||
}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${
|
||||
@@ -213,8 +213,8 @@ export class CloudBackupPref extends LitElement {
|
||||
)
|
||||
}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button appearance="filled" href="/config/backup?historyBack=1">
|
||||
@@ -308,12 +308,15 @@ export class CloudBackupPref extends LitElement {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
ha-list-base {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
ha-md-list {
|
||||
background: none;
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-list-item-base {
|
||||
--ha-row-item-padding-block: var(--ha-space-2);
|
||||
--ha-row-item-min-height: 40px;
|
||||
ha-md-list-item {
|
||||
--md-list-item-top-space: var(--ha-space-2);
|
||||
--md-list-item-bottom-space: var(--ha-space-2);
|
||||
--md-list-item-one-line-container-height: 40px;
|
||||
}
|
||||
ha-svg-icon[slot="start"] {
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
@@ -4,10 +4,10 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-tip";
|
||||
import "../../../../components/item/ha-list-item-base";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
|
||||
import { formatDate } from "../../../../common/datetime/format_date";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
@@ -179,8 +179,8 @@ export class CloudRemotePref extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.remote.external_activation"
|
||||
@@ -196,8 +196,8 @@ export class CloudRemotePref extends LitElement {
|
||||
.checked=${remote_allow_remote_enable}
|
||||
@change=${this._toggleAllowRemoteEnabledChanged}
|
||||
></ha-switch>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.remote.certificate_info"
|
||||
@@ -232,8 +232,8 @@ export class CloudRemotePref extends LitElement {
|
||||
"ui.panel.config.cloud.account.remote.more_info"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-tip .hass=${this.hass}>
|
||||
@@ -318,8 +318,13 @@ export class CloudRemotePref extends LitElement {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
ha-list-base {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
ha-md-list {
|
||||
background: none;
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-tip {
|
||||
max-width: 600px;
|
||||
|
||||
@@ -5,26 +5,26 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-language-picker";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-select";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-textarea";
|
||||
import "../../../../components/ha-tip";
|
||||
import "../../../../components/voice-assistant-brand-icon";
|
||||
import type {
|
||||
HaSelectOption,
|
||||
HaSelectSelectEvent,
|
||||
} from "../../../../components/ha-select";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-textarea";
|
||||
import type { HaTextArea } from "../../../../components/ha-textarea";
|
||||
import "../../../../components/ha-tip";
|
||||
import "../../../../components/item/ha-list-item-base";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import "../../../../components/voice-assistant-brand-icon";
|
||||
import { showAutomationEditor } from "../../../../data/automation";
|
||||
import type { CloudStatusLoggedIn } from "../../../../data/cloud";
|
||||
import { updateCloudPref } from "../../../../data/cloud";
|
||||
@@ -153,8 +153,8 @@ export class CloudTTSPref extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.tts.default_language"
|
||||
@@ -176,8 +176,8 @@ export class CloudTTSPref extends LitElement {
|
||||
@value-changed=${this._handleLanguageChange}
|
||||
>
|
||||
</ha-language-picker>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.tts.default_voice"
|
||||
@@ -199,8 +199,8 @@ export class CloudTTSPref extends LitElement {
|
||||
}))}
|
||||
>
|
||||
</ha-select>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.components.media-browser.tts.selected_voice_id"
|
||||
@@ -215,8 +215,8 @@ export class CloudTTSPref extends LitElement {
|
||||
)}
|
||||
@click=${this._copyVoiceId}
|
||||
></ha-icon-button>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<div class="try-tts">
|
||||
<span class="try-heading">
|
||||
${this.hass.localize(
|
||||
@@ -541,8 +541,13 @@ export class CloudTTSPref extends LitElement {
|
||||
gap: var(--ha-space-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
ha-list-base {
|
||||
--ha-row-item-padding-inline: var(--ha-space-4);
|
||||
ha-md-list {
|
||||
background: none;
|
||||
--md-list-item-leading-space: var(--ha-space-4);
|
||||
--md-list-item-trailing-space: var(--ha-space-4);
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-language-picker,
|
||||
ha-select {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import { mdiContentCopy, mdiRestore } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
@@ -7,6 +6,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consume } from "@lit/context";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
@@ -19,24 +19,23 @@ import type {
|
||||
LocalizeKeys,
|
||||
} from "../../../common/translations/localize";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-color-picker";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button-next";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-labels-picker";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-select";
|
||||
import type { HaSelectSelectEvent } from "../../../components/ha-select";
|
||||
import "../../../components/ha-state-icon";
|
||||
import "../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import "../../../components/input/ha-input";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import {
|
||||
CAMERA_ORIENTATIONS,
|
||||
CameraEntityFeature,
|
||||
@@ -46,16 +45,16 @@ import {
|
||||
STREAM_TYPE_HLS,
|
||||
updateCameraPrefs,
|
||||
} from "../../../data/camera";
|
||||
import {
|
||||
dirtyStateContext,
|
||||
type DirtyStateContext,
|
||||
} from "../../../data/context/dirty-state";
|
||||
import type { ConfigEntry } from "../../../data/config_entries";
|
||||
import { deleteConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
createConfigFlow,
|
||||
handleConfigFlowStep,
|
||||
} from "../../../data/config_flow";
|
||||
import {
|
||||
dirtyStateContext,
|
||||
type DirtyStateContext,
|
||||
} from "../../../data/context/dirty-state";
|
||||
import type { DataEntryFlowStepCreateEntry } from "../../../data/data_entry_flow";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import { updateDeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
@@ -628,7 +627,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
${
|
||||
SWITCH_AS_DOMAINS_INVERT.includes(this._switchAsDomain)
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.invert.label"
|
||||
@@ -644,7 +643,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.checked=${!!this.entry.options?.switch_as_x?.invert}
|
||||
@change=${this._switchAsInvertChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
} `
|
||||
@@ -961,7 +960,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
${
|
||||
this._cameraPrefs
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.stream.preload_stream"
|
||||
@@ -978,8 +977,8 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._handleCameraPrefsChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.stream.stream_orientation"
|
||||
@@ -1007,97 +1006,103 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.value=${this._cameraPrefs.orientation.toString()}
|
||||
>
|
||||
</ha-select>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-list-base>
|
||||
${
|
||||
this.helperConfigEntry &&
|
||||
this.helperConfigEntry.supports_options &&
|
||||
this.helperConfigEntry.domain !== "switch_as_x"
|
||||
? html`
|
||||
<ha-list-item-button
|
||||
class="menu-item"
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._showOptionsFlow}
|
||||
${
|
||||
this.helperConfigEntry &&
|
||||
this.helperConfigEntry.supports_options &&
|
||||
this.helperConfigEntry.domain !== "switch_as_x"
|
||||
? html`
|
||||
<ha-list-item
|
||||
class="menu-item"
|
||||
twoline
|
||||
hasMeta
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._showOptionsFlow}
|
||||
>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.configure_state",
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
this.helperConfigEntry.domain
|
||||
),
|
||||
}
|
||||
)}</span
|
||||
>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.configure_state",
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
this.helperConfigEntry.domain
|
||||
),
|
||||
}
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.configure_state_secondary",
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
this.helperConfigEntry.domain
|
||||
),
|
||||
}
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-list-item-button
|
||||
class="menu-item"
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleVoiceAssistantsClicked}
|
||||
<span slot="secondary"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.configure_state_secondary",
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
this.helperConfigEntry.domain
|
||||
),
|
||||
}
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
|
||||
<ha-list-item
|
||||
class="menu-item"
|
||||
twoline
|
||||
hasMeta
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleVoiceAssistantsClicked}
|
||||
>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.voice_assistants"
|
||||
)}</span
|
||||
>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.voice_assistants"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">
|
||||
${
|
||||
this.entry.aliases.filter((a) => a !== null).length
|
||||
? this.entry.aliases
|
||||
.filter((a): a is string => a !== null)
|
||||
.join(", ")
|
||||
: this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.no_aliases"
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
${
|
||||
domain === "vacuum" &&
|
||||
stateObj &&
|
||||
supportsFeature(stateObj, VacuumEntityFeature.CLEAN_AREA)
|
||||
? html`
|
||||
<ha-list-item-button
|
||||
class="menu-item"
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleVacuumSegmentMappingClicked}
|
||||
<span slot="secondary">
|
||||
${
|
||||
this.entry.aliases.filter((a) => a !== null).length
|
||||
? this.entry.aliases
|
||||
.filter((a): a is string => a !== null)
|
||||
.join(", ")
|
||||
: this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.no_aliases"
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
|
||||
${
|
||||
domain === "vacuum" &&
|
||||
stateObj &&
|
||||
supportsFeature(stateObj, VacuumEntityFeature.CLEAN_AREA)
|
||||
? html`
|
||||
<ha-list-item
|
||||
class="menu-item"
|
||||
twoline
|
||||
hasMeta
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleVacuumSegmentMappingClicked}
|
||||
>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.vacuum_segment_mapping.title"
|
||||
)}</span
|
||||
>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.vacuum_segment_mapping.title"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.vacuum_segment_mapping.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</ha-list-base>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.vacuum_segment_mapping.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._disabledBy &&
|
||||
this._disabledBy !== "user" &&
|
||||
@@ -1115,7 +1120,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
: nothing
|
||||
}
|
||||
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.enabled_label"
|
||||
@@ -1138,7 +1143,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
@change=${this._enabledChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
|
||||
${
|
||||
this._hiddenBy && this._hiddenBy !== "user"
|
||||
@@ -1155,7 +1160,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
: nothing
|
||||
}
|
||||
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.visible_label"
|
||||
@@ -1172,12 +1177,12 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.disabled=${this.disabled || this._disabledBy}
|
||||
@change=${this._hiddenChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
|
||||
${
|
||||
this.entry.device_id
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.use_device_area"
|
||||
@@ -1213,7 +1218,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._useDeviceAreaChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
this._areaId || this._noDeviceArea
|
||||
? html`<ha-area-picker
|
||||
@@ -1806,10 +1811,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
ha-row-item ha-select {
|
||||
ha-md-list-item ha-select {
|
||||
width: auto;
|
||||
}
|
||||
ha-input,
|
||||
@@ -1821,11 +1823,11 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
.menu-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
overflow: hidden;
|
||||
--mdc-list-side-padding: 13px;
|
||||
}
|
||||
.entityId {
|
||||
direction: ltr;
|
||||
|
||||
@@ -9,16 +9,15 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { round } from "../../../common/number/round";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import { sanitizeHttpUrl } from "../../../common/url/sanitize-http-url";
|
||||
import "../../../components/animation/ha-fade-in";
|
||||
import "../../../components/chart/ha-chart-base";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/animation/ha-fade-in";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-nav";
|
||||
import type { ConfigEntry } from "../../../data/config_entries";
|
||||
import { subscribeConfigEntries } from "../../../data/config_entries";
|
||||
import type {
|
||||
@@ -306,29 +305,24 @@ class HaConfigHardwareOverview extends SubscribeMixin(LitElement) {
|
||||
${
|
||||
documentationURL
|
||||
? html`
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation"
|
||||
)}
|
||||
<ha-md-list-item
|
||||
.href=${documentationURL}
|
||||
type="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ha-list-item-button
|
||||
.href=${documentationURL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation"
|
||||
)}</span
|
||||
>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
|
||||
@@ -140,13 +140,8 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
this._domain = params.domain;
|
||||
this._item = undefined;
|
||||
if (this._domain && this._domain in HELPERS) {
|
||||
this._loading = true;
|
||||
try {
|
||||
await HELPERS[this._domain].import();
|
||||
this._initDirtyTracking({ type: "deep" }, undefined);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
await HELPERS[this._domain].import();
|
||||
this._initDirtyTracking({ type: "deep" }, undefined);
|
||||
}
|
||||
this._open = true;
|
||||
await this.updateComplete;
|
||||
@@ -190,7 +185,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
let content: TemplateResult;
|
||||
let footer: TemplateResult | typeof nothing = nothing;
|
||||
|
||||
if (this._domain && !this._loading) {
|
||||
if (this._domain) {
|
||||
content = html`
|
||||
<div class="form" @value-changed=${this._valueChanged}>
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : ""}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import { ERROR_STATES, RECOVERABLE_STATES } from "../../../data/config_entries";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { PickConfigEntryDialogParams } from "./show-pick-config-entry-dialog";
|
||||
@@ -43,22 +43,21 @@ export class DialogPickConfigEntry extends LitElement {
|
||||
header-title=${title}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${this._params.configEntries.map(
|
||||
(entry) =>
|
||||
html`<ha-list-item-button
|
||||
html`<ha-md-list-item
|
||||
type="button"
|
||||
@click=${this._itemPicked}
|
||||
.entry=${entry}
|
||||
.disabled=${
|
||||
!ERROR_STATES.includes(entry.state) &&
|
||||
!RECOVERABLE_STATES.includes(entry.state)
|
||||
}
|
||||
><span slot="headline"
|
||||
>${entry.title}</span
|
||||
></ha-list-item-button
|
||||
>${entry.title}</ha-md-list-item
|
||||
>`
|
||||
)}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
+26
-20
@@ -4,17 +4,15 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
|
||||
import { stopKeydownEnterSpacePropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import { computeDeviceName } from "../../../../../common/entity/compute_device_name";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import type {
|
||||
BluetoothAllocationsData,
|
||||
BluetoothScannerState,
|
||||
@@ -193,7 +191,7 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
>
|
||||
<div class="container">
|
||||
<ha-card>
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${this._renderAdaptersList(enabledDevices)}
|
||||
${disabledDevices.map(({ device, entry }) => {
|
||||
const areaName =
|
||||
@@ -207,7 +205,7 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
return html`
|
||||
<ha-list-item-base class="disabled">
|
||||
<ha-md-list-item class="disabled">
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${computeDeviceName(device) || entry.title}
|
||||
@@ -220,12 +218,12 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
})}
|
||||
${disabledEntriesWithoutDevice.map(
|
||||
(entry) => html`
|
||||
<ha-list-item-base class="disabled">
|
||||
<ha-md-list-item class="disabled">
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
<div slot="headline">${entry.title}</div>
|
||||
<div slot="supporting-text">
|
||||
@@ -240,10 +238,10 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@@ -264,13 +262,13 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
devices: { device: DeviceRegistryEntry; entry: ConfigEntry }[]
|
||||
) {
|
||||
if (devices.length === 0) {
|
||||
return html`<ha-list-item-base>
|
||||
return html`<ha-md-list-item>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.no_scanner_state_available"
|
||||
)}
|
||||
</div>
|
||||
</ha-list-item-base>`;
|
||||
</ha-md-list-item>`;
|
||||
}
|
||||
|
||||
return devices.map(({ device, entry }) => {
|
||||
@@ -316,7 +314,8 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/bluetooth/connection-monitor?source=${btAddress}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
@@ -329,7 +328,6 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
.path=${mdiCogOutline}
|
||||
.entry=${entry}
|
||||
@click=${this._openOptionFlow}
|
||||
@keydown=${stopKeydownEnterSpacePropagation}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.option_flow"
|
||||
)}
|
||||
@@ -337,7 +335,7 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
: nothing
|
||||
}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
hasMismatch && scannerDetails
|
||||
? this._renderScannerMismatchWarning(
|
||||
@@ -501,14 +499,22 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
margin: 0 auto var(--ha-space-4);
|
||||
}
|
||||
|
||||
ha-list-item-button ha-svg-icon[slot="start"],
|
||||
ha-list-item-base ha-svg-icon[slot="start"] {
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
ha-md-list-item ha-svg-icon[slot="start"] {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-list-item-base.disabled ha-svg-icon[slot="start"],
|
||||
ha-list-item-base.disabled [slot="headline"],
|
||||
ha-list-item-base.disabled [slot="supporting-text"] {
|
||||
ha-md-list-item.disabled ha-svg-icon[slot="start"],
|
||||
ha-md-list-item.disabled [slot="headline"],
|
||||
ha-md-list-item.disabled [slot="supporting-text"] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
+21
-15
@@ -7,16 +7,16 @@ import {
|
||||
mdiLinkVariant,
|
||||
mdiVectorPolyline,
|
||||
} from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type {
|
||||
BluetoothAllocationsData,
|
||||
BluetoothDeviceData,
|
||||
@@ -213,12 +213,11 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
</ha-button>
|
||||
</div>
|
||||
<div class="card-content network-card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.my_network"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button href="/config/bluetooth/adapter-info">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/bluetooth/adapter-info"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAccessPoint}
|
||||
@@ -230,9 +229,10 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/bluetooth/connection-monitor"
|
||||
>
|
||||
<ha-svg-icon
|
||||
@@ -246,9 +246,10 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/bluetooth/advertisement-monitor"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiBroadcast}></ha-svg-icon>
|
||||
@@ -259,8 +260,8 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -285,6 +286,11 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
margin-top: var(--ha-space-6);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.network-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
+11
-10
@@ -9,9 +9,9 @@ import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import type { InfraredDevice } from "../../../../../data/infrared";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
@@ -82,12 +82,8 @@ export class InfraredConfigDashboard extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.infrared.my_devices")}
|
||||
</div>
|
||||
<div class="card-content network-card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.infrared.my_devices"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button href="/config/infrared/devices">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="link" href="/config/infrared/devices">
|
||||
<ha-svg-icon slot="start" .path=${mdiRemote}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
@@ -96,8 +92,8 @@ export class InfraredConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -122,6 +118,11 @@ export class InfraredConfigDashboard extends LitElement {
|
||||
margin-top: var(--ha-space-6);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.network-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
+10
-11
@@ -2,26 +2,25 @@ import { mdiDelete, mdiLock, mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { stopKeydownEnterSpacePropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import type {
|
||||
MatterLockInfo,
|
||||
MatterLockUser,
|
||||
} from "../../../../../data/matter-lock";
|
||||
import {
|
||||
clearMatterLockUser,
|
||||
getMatterLockInfo,
|
||||
getMatterLockUsers,
|
||||
clearMatterLockUser,
|
||||
} from "../../../../../data/matter-lock";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -163,10 +162,11 @@ class DialogMatterLockManage extends LitElement {
|
||||
)}
|
||||
</p>`
|
||||
: html`
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${occupiedUsers.map(
|
||||
(user) => html`
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
.user=${user}
|
||||
@click=${this._handleUserClick}
|
||||
>
|
||||
@@ -191,12 +191,11 @@ class DialogMatterLockManage extends LitElement {
|
||||
.path=${mdiDelete}
|
||||
.user=${user}
|
||||
@click=${this._handleDeleteUserClick}
|
||||
@keydown=${stopKeydownEnterSpacePropagation}
|
||||
></ha-icon-button>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
`
|
||||
}
|
||||
${
|
||||
|
||||
+31
-10
@@ -3,8 +3,8 @@ import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
import "../../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../../components/list/ha-list-base";
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import type { MatterAddDeviceStep } from "../dialog-matter-add-device";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
@@ -23,8 +23,14 @@ class MatterAddDeviceExisting extends LitElement {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-list-base>
|
||||
<ha-list-item-button .step=${"google_home"} @click=${this._onItemClick}>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
.step=${"google_home"}
|
||||
@click=${this._onItemClick}
|
||||
@keydown=${this._onItemClick}
|
||||
>
|
||||
<img
|
||||
src="/static/images/logo_google_home.png"
|
||||
alt=""
|
||||
@@ -37,8 +43,14 @@ class MatterAddDeviceExisting extends LitElement {
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button .step=${"apple_home"} @click=${this._onItemClick}>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
.step=${"apple_home"}
|
||||
@click=${this._onItemClick}
|
||||
@keydown=${this._onItemClick}
|
||||
>
|
||||
<img
|
||||
src="/static/images/logo_apple_home.png"
|
||||
alt=""
|
||||
@@ -51,8 +63,14 @@ class MatterAddDeviceExisting extends LitElement {
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button .step=${"generic"} @click=${this._onItemClick}>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
.step=${"generic"}
|
||||
@click=${this._onItemClick}
|
||||
@keydown=${this._onItemClick}
|
||||
>
|
||||
<div class="logo" slot="start">
|
||||
<ha-svg-icon path=${mdiHomeAutomation}></ha-svg-icon>
|
||||
</div>
|
||||
@@ -62,12 +80,15 @@ class MatterAddDeviceExisting extends LitElement {
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onItemClick(ev) {
|
||||
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||
return;
|
||||
}
|
||||
const item = ev.currentTarget as any;
|
||||
const step = item.step as MatterAddDeviceStep;
|
||||
fireEvent(this, "step-selected", { step });
|
||||
|
||||
+23
-8
@@ -2,8 +2,8 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
import "../../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../../components/list/ha-list-base";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
|
||||
@@ -18,8 +18,14 @@ class MatterAddDeviceMain extends LitElement {
|
||||
${this.hass.localize(`ui.dialogs.matter-add-device.main.question`)}
|
||||
</p>
|
||||
</div>
|
||||
<ha-list-base>
|
||||
<ha-list-item-button .step=${"new"} @click=${this._onItemClick}>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
.step=${"new"}
|
||||
@click=${this._onItemClick}
|
||||
@keydown=${this._onItemClick}
|
||||
>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.matter-add-device.main.answer_new`
|
||||
@@ -31,8 +37,14 @@ class MatterAddDeviceMain extends LitElement {
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button .step=${"existing"} @click=${this._onItemClick}>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
.step=${"existing"}
|
||||
@click=${this._onItemClick}
|
||||
@keydown=${this._onItemClick}
|
||||
>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.matter-add-device.main.answer_existing`
|
||||
@@ -44,12 +56,15 @@ class MatterAddDeviceMain extends LitElement {
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onItemClick(ev) {
|
||||
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||
return;
|
||||
}
|
||||
const item = ev.currentTarget as any;
|
||||
const step = item.step as "new" | "existing";
|
||||
fireEvent(this, "step-selected", { step });
|
||||
|
||||
+3
-2
@@ -23,9 +23,10 @@ export const sharedStyles = css`
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
ha-list-base {
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
--ha-row-item-padding-inline: var(--horizontal-padding, 16px);
|
||||
--md-list-item-leading-space: var(--horizontal-padding, 16px);
|
||||
--md-list-item-trailing-space: var(--horizontal-padding, 16px);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-input {
|
||||
|
||||
+25
-22
@@ -16,9 +16,9 @@ import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import { fetchMatterNetworkTopology } from "../../../../../data/matter";
|
||||
@@ -167,12 +167,9 @@ export class MatterConfigDashboard extends LitElement {
|
||||
</ha-button>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.my_network_title"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
@@ -183,8 +180,9 @@ export class MatterConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
@@ -195,8 +193,8 @@ export class MatterConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -206,12 +204,8 @@ export class MatterConfigDashboard extends LitElement {
|
||||
return html`
|
||||
<ha-card class="nav-card">
|
||||
<div class="card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.options_title"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button href="/config/matter/options">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="link" href="/config/matter/options">
|
||||
<ha-svg-icon slot="start" .path=${mdiTune}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
@@ -224,10 +218,10 @@ export class MatterConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
isComponentLoaded(this.hass.config, "thread")
|
||||
? html`<ha-list-item-button href="/config/thread">
|
||||
? html`<ha-md-list-item type="link" href="/config/thread">
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${THREAD_ICON}
|
||||
@@ -243,10 +237,10 @@ export class MatterConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>`
|
||||
</ha-md-list-item>`
|
||||
: nothing
|
||||
}
|
||||
</ha-list-nav>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -313,6 +307,15 @@ export class MatterConfigDashboard extends LitElement {
|
||||
margin-top: var(--ha-space-6);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
.network-status div.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/item/ha-row-item";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import {
|
||||
acceptSharedMatterDevice,
|
||||
canCommissionMatterExternal,
|
||||
@@ -54,120 +55,122 @@ class MatterOptionsPage extends LitElement {
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
canCommissionMatterExternal(this.hass)
|
||||
? html`<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._startMobileCommissioning}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-row-item>`
|
||||
: nothing
|
||||
}
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._commission}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._acceptSharedDevice}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._setWifi}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._setThread}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-row-item>
|
||||
<ha-md-list>
|
||||
${
|
||||
canCommissionMatterExternal(this.hass)
|
||||
? html`<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._startMobileCommissioning}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>`
|
||||
: nothing
|
||||
}
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._commission}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._acceptSharedDevice}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._setWifi}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
@click=${this._setThread}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
@@ -320,6 +323,15 @@ class MatterOptionsPage extends LitElement {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
+29
-22
@@ -16,11 +16,10 @@ import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import { domainToName } from "../../../../../data/integration";
|
||||
@@ -188,21 +187,22 @@ export class ModbusConfigDashboard extends LitElement {
|
||||
// removed while the connection was listed has nowhere to link to
|
||||
if (!holder.entry) {
|
||||
return html`
|
||||
<ha-list-item-base class="holder">
|
||||
<ha-md-list-item class="holder">
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${source === "yaml" ? mdiFileDocumentOutline : mdiPuzzle}
|
||||
></ha-svg-icon>
|
||||
<div slot="headline">${holder.id}</div>
|
||||
${this._renderUnits(holder)}
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
const { domain, title } = holder.entry;
|
||||
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
class="holder"
|
||||
href=${`/config/integrations/integration/${domain}#config_entry=${holder.id}`}
|
||||
>
|
||||
@@ -225,19 +225,19 @@ export class ModbusConfigDashboard extends LitElement {
|
||||
</div>
|
||||
${this._renderUnits(holder)}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderSerialPortLink(): TemplateResult {
|
||||
return html`
|
||||
<ha-list-item-button class="holder" href="/config/serial">
|
||||
<ha-md-list-item type="link" class="holder" href="/config/serial">
|
||||
<ha-svg-icon slot="start" .path=${mdiCableData}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.panel.config.modbus.view_serial_port")}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -256,12 +256,12 @@ export class ModbusConfigDashboard extends LitElement {
|
||||
|
||||
private _renderConnectionItem(item: ConnectionListItem): TemplateResult {
|
||||
return html`
|
||||
<ha-list-item-base class="connection">
|
||||
<ha-md-list-item class="connection">
|
||||
<ha-svg-icon slot="start" .path=${item.icon}></ha-svg-icon>
|
||||
<div slot="headline">${item.primary}</div>
|
||||
<div slot="supporting-text">${item.secondary}</div>
|
||||
${this._renderState(item)}
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
${item.holders.map((holder) => this._renderHolder(holder, item.source))}
|
||||
${
|
||||
item.serialDevice && isComponentLoaded(this.hass.config, "usb")
|
||||
@@ -283,9 +283,9 @@ export class ModbusConfigDashboard extends LitElement {
|
||||
"ui.panel.config.modbus.connections_description"
|
||||
)}
|
||||
</div>
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${items.map((item) => this._renderConnectionItem(item))}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -474,16 +474,23 @@ export class ModbusConfigDashboard extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-list-item-base,
|
||||
ha-list-item-button {
|
||||
--ha-row-item-padding-block: var(--ha-space-2);
|
||||
--ha-row-item-min-height: 0;
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-list-item-base.connection:not(:first-child) {
|
||||
ha-md-list-item {
|
||||
--md-list-item-top-space: var(--ha-space-2);
|
||||
--md-list-item-bottom-space: var(--ha-space-2);
|
||||
--md-list-item-one-line-container-height: 0;
|
||||
--md-list-item-two-line-container-height: 0;
|
||||
--md-list-item-three-line-container-height: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item.connection:not(:first-child) {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
margin-top: var(--ha-space-2);
|
||||
--ha-row-item-padding-block: var(--ha-space-4) var(--ha-space-2);
|
||||
--md-list-item-top-space: var(--ha-space-4);
|
||||
}
|
||||
|
||||
.state {
|
||||
@@ -505,11 +512,11 @@ export class ModbusConfigDashboard extends LitElement {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
|
||||
.holder {
|
||||
--ha-row-item-padding-inline: var(--ha-space-14) var(--ha-space-4);
|
||||
ha-md-list-item.holder {
|
||||
--md-list-item-leading-space: var(--ha-space-14);
|
||||
}
|
||||
|
||||
.holder img[slot="start"] {
|
||||
ha-md-list-item.holder img[slot="start"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiCheck,
|
||||
@@ -5,37 +9,32 @@ import {
|
||||
mdiShape,
|
||||
mdiTune,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { storage } from "../../../../../common/decorators/storage";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-code-editor";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import type { HaSelectSelectEvent } from "../../../../../components/ha-select";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import type { HaSelectSelectEvent } from "../../../../../components/ha-select";
|
||||
import "../../../../../components/ha-switch";
|
||||
import "../../../../../components/input/ha-input";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import { fetchIntegrationManifest } from "../../../../../data/integration";
|
||||
import type { Action } from "../../../../../data/script";
|
||||
import { callExecuteScript } from "../../../../../data/service";
|
||||
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { mdiMqttLogo } from "../../../../../resources/mqtt-logo-svg";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { brandsUrl } from "../../../../../util/brands-url";
|
||||
import { showToast } from "../../../../../util/toast";
|
||||
import "./mqtt-subscribe-card";
|
||||
import { brandsUrl } from "../../../../../util/brands-url";
|
||||
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { fetchIntegrationManifest } from "../../../../../data/integration";
|
||||
import { mdiMqttLogo } from "../../../../../resources/mqtt-logo-svg";
|
||||
|
||||
const qosLevel = ["0", "1", "2"];
|
||||
|
||||
@@ -187,12 +186,9 @@ export class MQTTConfigPanel extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.mqtt.my_network_title")}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.mqtt.my_network_title"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
@@ -202,8 +198,9 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
@@ -213,8 +210,8 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -224,8 +221,8 @@ export class MQTTConfigPanel extends LitElement {
|
||||
return html`
|
||||
<ha-card class="nav-card">
|
||||
<div class="card-content">
|
||||
<ha-list-base>
|
||||
<ha-list-item-button @click=${this._openOptionFlow}>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="link" @click=${this._openOptionFlow}>
|
||||
<ha-svg-icon slot="start" .path=${mdiTune}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.panel.config.mqtt.option_flow")}
|
||||
@@ -236,8 +233,8 @@ export class MQTTConfigPanel extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button @click=${this._openConfigFlow}>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="link" @click=${this._openConfigFlow}>
|
||||
<ha-svg-icon slot="start" .path=${mdiMqttLogo}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.panel.config.mqtt.config_flow")}
|
||||
@@ -248,8 +245,8 @@ export class MQTTConfigPanel extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -407,6 +404,13 @@ export class MQTTConfigPanel extends LitElement {
|
||||
margin: 0 auto var(--ha-space-4);
|
||||
max-width: 600px;
|
||||
}
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-select {
|
||||
width: 96px;
|
||||
margin: 0 8px;
|
||||
|
||||
+14
-10
@@ -4,9 +4,9 @@ import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import { UNAVAILABLE } from "../../../../../data/entity/entity";
|
||||
import { FALLBACK_DOMAIN_ICONS } from "../../../../../data/icons";
|
||||
import type { RadioFrequencyTransmitter } from "../../../../../data/radio_frequency";
|
||||
@@ -73,12 +73,11 @@ export class RadioFrequencyConfigDashboard extends LitElement {
|
||||
|
||||
<ha-card class="network-card">
|
||||
<div class="card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.radio_frequency.title"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button href="/config/radio-frequency/devices">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/radio-frequency/devices"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${FALLBACK_DOMAIN_ICONS[DOMAIN]}
|
||||
@@ -90,8 +89,8 @@ export class RadioFrequencyConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -112,6 +111,11 @@ export class RadioFrequencyConfigDashboard extends LitElement {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.network-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
+33
-26
@@ -22,11 +22,10 @@ import "../../../../../components/ha-app-icon";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import {
|
||||
domainToName,
|
||||
getConfigPanelPath,
|
||||
@@ -43,13 +42,13 @@ import type {
|
||||
} from "../../../../../data/usb";
|
||||
import { listSerialPortsWithUsage } from "../../../../../data/usb";
|
||||
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { mdiEsphomeLogo } from "../../../../../resources/esphome-logo-svg";
|
||||
import { showSerialPortInfoDialog } from "./show-dialog-serial-port-info";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { panelIsReady } from "../../../../../layouts/panel-ready";
|
||||
import { mdiEsphomeLogo } from "../../../../../resources/esphome-logo-svg";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { brandsUrl } from "../../../../../util/brands-url";
|
||||
import { showSerialPortInfoDialog } from "./show-dialog-serial-port-info";
|
||||
|
||||
const ESPHOME_HASS_SCHEME = "esphome-hass://";
|
||||
|
||||
@@ -271,7 +270,7 @@ export class SerialConfigDashboard extends LitElement {
|
||||
const href = this._consumerHref(consumer);
|
||||
|
||||
return html`
|
||||
<ha-list-item-button href=${href} class="consumer">
|
||||
<ha-md-list-item type="link" href=${href} class="consumer">
|
||||
${
|
||||
consumer.kind === "config_entry"
|
||||
? html`<img
|
||||
@@ -303,13 +302,14 @@ export class SerialConfigDashboard extends LitElement {
|
||||
}
|
||||
<div slot="headline">${this._consumerName(consumer)}</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderDiscoveryFlow(flow: SerialPortDiscoveryFlow): TemplateResult {
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
class="consumer"
|
||||
.flowId=${flow.flow_id}
|
||||
@click=${this._continueFlow}
|
||||
@@ -334,13 +334,13 @@ export class SerialConfigDashboard extends LitElement {
|
||||
})}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderModbusLink(): TemplateResult {
|
||||
return html`
|
||||
<ha-list-item-button class="consumer" href="/config/modbus">
|
||||
<ha-md-list-item type="link" class="consumer" href="/config/modbus">
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiTransitConnectionVariant}
|
||||
@@ -349,7 +349,7 @@ export class SerialConfigDashboard extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.serial.used_by_modbus")}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ export class SerialConfigDashboard extends LitElement {
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-list-item-base class="port">
|
||||
<ha-md-list-item class="port">
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
class=${item.port.present ? "" : "disconnected"}
|
||||
@@ -415,7 +415,7 @@ export class SerialConfigDashboard extends LitElement {
|
||||
></ha-icon-button>`
|
||||
: nothing
|
||||
}
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
${item.consumers.map((consumer) => this._renderConsumer(consumer))}
|
||||
${this._usedByModbus(item.port) ? this._renderModbusLink() : nothing}
|
||||
${item.discoveryFlows.map((flow) => this._renderDiscoveryFlow(flow))}
|
||||
@@ -438,9 +438,9 @@ export class SerialConfigDashboard extends LitElement {
|
||||
<div class="card-header">${header}</div>
|
||||
<div class="card-content">
|
||||
<div class="description">${description}</div>
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${items.map((item) => this._renderPortItem(item))}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -688,28 +688,35 @@ export class SerialConfigDashboard extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-list-item-base,
|
||||
ha-list-item-button {
|
||||
--ha-row-item-padding-block: var(--ha-space-2);
|
||||
--ha-row-item-min-height: 0;
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-list-item-base.port:not(:first-child) {
|
||||
ha-md-list-item {
|
||||
--md-list-item-top-space: var(--ha-space-2);
|
||||
--md-list-item-bottom-space: var(--ha-space-2);
|
||||
--md-list-item-one-line-container-height: 0;
|
||||
--md-list-item-two-line-container-height: 0;
|
||||
--md-list-item-three-line-container-height: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item.port:not(:first-child) {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
margin-top: var(--ha-space-2);
|
||||
--ha-row-item-padding-block: var(--ha-space-4) var(--ha-space-2);
|
||||
--md-list-item-top-space: var(--ha-space-4);
|
||||
}
|
||||
|
||||
ha-list-item-base .disconnected {
|
||||
ha-md-list-item .disconnected {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
|
||||
.consumer {
|
||||
--ha-row-item-padding-inline: var(--ha-space-14) var(--ha-space-4);
|
||||
ha-md-list-item.consumer {
|
||||
--md-list-item-leading-space: var(--ha-space-14);
|
||||
}
|
||||
|
||||
.consumer img[slot="start"],
|
||||
.consumer ha-app-icon[slot="start"] {
|
||||
ha-md-list-item.consumer img[slot="start"],
|
||||
ha-md-list-item.consumer ha-app-icon[slot="start"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
@@ -16,20 +16,18 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import "../../../../../components/buttons/ha-progress-button";
|
||||
import type { HaProgressButton } from "../../../../../components/buttons/ha-progress-button";
|
||||
import { animationStyles } from "../../../../../resources/theme/animations.globals";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import { animationStyles } from "../../../../../resources/theme/animations.globals";
|
||||
import "../../../../../components/buttons/ha-progress-button";
|
||||
import type { HaProgressButton } from "../../../../../components/buttons/ha-progress-button";
|
||||
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import type {
|
||||
@@ -221,12 +219,9 @@ class ZHAConfigDashboard extends LitElement {
|
||||
</ha-button>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.my_network_title"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
@@ -237,8 +232,9 @@ class ZHAConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
@@ -249,8 +245,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button href="/config/zha/groups">
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="link" href="/config/zha/groups">
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiFolderMultipleOutline}
|
||||
@@ -275,8 +271,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -292,12 +288,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
return html`
|
||||
<ha-card class="nav-card">
|
||||
<div class="card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.options_title"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button href="/config/zha/options">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="link" href="/config/zha/options">
|
||||
<ha-svg-icon slot="start" .path=${mdiTune}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
@@ -310,8 +302,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button href="/config/zha/network-info">
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="link" href="/config/zha/network-info">
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiInformationOutline}
|
||||
@@ -327,10 +319,13 @@ class ZHAConfigDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
${dynamicSections.map(
|
||||
(section) => html`
|
||||
<ha-list-item-button href=${`/config/zha/section/${section}`}>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/zha/section/${section}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiTune}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${
|
||||
@@ -340,10 +335,10 @@ class ZHAConfigDashboard extends LitElement {
|
||||
}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-nav>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -353,8 +348,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
return html`
|
||||
<ha-card class="nav-card">
|
||||
<div class="card-content">
|
||||
<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.download_backup"
|
||||
@@ -377,8 +372,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
"ui.panel.config.zha.configuration_page.download_backup_action"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.migrate_radio"
|
||||
@@ -399,8 +394,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
"ui.panel.config.zha.configuration_page.migrate_radio_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -537,6 +532,15 @@ class ZHAConfigDashboard extends LitElement {
|
||||
padding: var(--ha-space-12);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
.network-status div.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -4,8 +4,8 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import type { ZHANetworkSettings } from "../../../../../data/zha";
|
||||
import { fetchZHANetworkSettings } from "../../../../../data/zha";
|
||||
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
@@ -53,8 +53,8 @@ class ZHANetworkInfoPage extends LitElement {
|
||||
<ha-card>
|
||||
${
|
||||
this._networkSettings
|
||||
? html`<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
? html`<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.channel_label"
|
||||
@@ -73,16 +73,16 @@ class ZHANetworkInfoPage extends LitElement {
|
||||
.path=${mdiPencil}
|
||||
@click=${this._showChannelMigrationDialog}
|
||||
></ha-icon-button>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">PAN ID</span>
|
||||
<span slot="supporting-text"
|
||||
>${
|
||||
this._networkSettings.settings.network_info.pan_id
|
||||
}</span
|
||||
>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Extended PAN ID</span>
|
||||
<span slot="supporting-text"
|
||||
>${
|
||||
@@ -90,14 +90,14 @@ class ZHANetworkInfoPage extends LitElement {
|
||||
.extended_pan_id
|
||||
}</span
|
||||
>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Coordinator IEEE</span>
|
||||
<span slot="supporting-text"
|
||||
>${this._networkSettings.settings.node_info.ieee}</span
|
||||
>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.radio_type"
|
||||
@@ -106,8 +106,8 @@ class ZHANetworkInfoPage extends LitElement {
|
||||
<span slot="supporting-text"
|
||||
>${this._networkSettings.radio_type}</span
|
||||
>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.serial_port"
|
||||
@@ -116,12 +116,12 @@ class ZHANetworkInfoPage extends LitElement {
|
||||
<span slot="supporting-text"
|
||||
>${this._networkSettings.device.path}</span
|
||||
>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
this._networkSettings.device.baudrate &&
|
||||
!this._networkSettings.device.path.startsWith("socket://")
|
||||
? html`
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.baudrate"
|
||||
@@ -130,11 +130,11 @@ class ZHANetworkInfoPage extends LitElement {
|
||||
<span slot="supporting-text"
|
||||
>${this._networkSettings.device.baudrate}</span
|
||||
>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</ha-list-base>`
|
||||
</ha-md-list>`
|
||||
: nothing
|
||||
}
|
||||
</ha-card>
|
||||
@@ -174,6 +174,19 @@ class ZHANetworkInfoPage extends LitElement {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
--md-list-item-supporting-text-size: var(
|
||||
--md-list-item-label-text-size,
|
||||
var(--md-sys-typescale-body-large-size, 1rem)
|
||||
);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-select";
|
||||
import "../../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../../components/ha-switch";
|
||||
import "../../../../../components/input/ha-input";
|
||||
import type { HaInput } from "../../../../../components/input/ha-input";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../../components/ha-switch";
|
||||
import type { ZHAConfiguration } from "../../../../../data/zha";
|
||||
import {
|
||||
fetchZHAConfiguration,
|
||||
@@ -115,8 +115,8 @@ class ZHAOptionsPage extends LitElement {
|
||||
${
|
||||
this._configuration
|
||||
? html`
|
||||
<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.enable_identify_on_join_label"
|
||||
@@ -135,8 +135,8 @@ class ZHAOptionsPage extends LitElement {
|
||||
}
|
||||
@change=${this._enableIdentifyOnJoinChanged}
|
||||
></ha-switch>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.default_light_transition_label"
|
||||
@@ -160,8 +160,8 @@ class ZHAOptionsPage extends LitElement {
|
||||
>
|
||||
<span slot="end">s</span>
|
||||
</ha-input>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.enhanced_light_transition_label"
|
||||
@@ -180,8 +180,8 @@ class ZHAOptionsPage extends LitElement {
|
||||
}
|
||||
@change=${this._enhancedLightTransitionChanged}
|
||||
></ha-switch>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.light_transitioning_flag_label"
|
||||
@@ -200,8 +200,8 @@ class ZHAOptionsPage extends LitElement {
|
||||
}
|
||||
@change=${this._lightTransitioningFlagChanged}
|
||||
></ha-switch>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.group_members_assume_state_label"
|
||||
@@ -220,8 +220,8 @@ class ZHAOptionsPage extends LitElement {
|
||||
}
|
||||
@change=${this._groupMembersAssumeStateChanged}
|
||||
></ha-switch>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.consider_unavailable_mains_label"
|
||||
@@ -242,11 +242,11 @@ class ZHAOptionsPage extends LitElement {
|
||||
.options=${this._getUnavailableTimeoutOptions(7200)}
|
||||
@selected=${this._mainsUnavailableChanged}
|
||||
></ha-select>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
this._customMains
|
||||
? html`
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<ha-input
|
||||
slot="end"
|
||||
type="number"
|
||||
@@ -261,11 +261,11 @@ class ZHAOptionsPage extends LitElement {
|
||||
>
|
||||
<span slot="end">s</span>
|
||||
</ha-input>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.consider_unavailable_battery_label"
|
||||
@@ -286,11 +286,11 @@ class ZHAOptionsPage extends LitElement {
|
||||
.options=${this._getUnavailableTimeoutOptions(21600)}
|
||||
@selected=${this._batteryUnavailableChanged}
|
||||
></ha-select>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
this._customBattery
|
||||
? html`
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<ha-input
|
||||
slot="end"
|
||||
type="number"
|
||||
@@ -305,11 +305,11 @@ class ZHAOptionsPage extends LitElement {
|
||||
>
|
||||
<span slot="end">s</span>
|
||||
</ha-input>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.enable_mains_startup_polling_label"
|
||||
@@ -328,8 +328,8 @@ class ZHAOptionsPage extends LitElement {
|
||||
}
|
||||
@change=${this._enableMainsStartupPollingChanged}
|
||||
></ha-switch>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
appearance="filled"
|
||||
@@ -463,6 +463,15 @@ class ZHAOptionsPage extends LitElement {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
ha-select,
|
||||
ha-input {
|
||||
min-width: 210px;
|
||||
|
||||
+23
-11
@@ -1,12 +1,12 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-alert";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
import "../../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../../components/list/ha-list-base";
|
||||
|
||||
@customElement("zwave-js-add-node-select-method")
|
||||
export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
@@ -26,10 +26,12 @@ export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
>`
|
||||
: nothing
|
||||
}
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${
|
||||
!this.hideQrWebcam
|
||||
? html`<ha-list-item-button
|
||||
? html`<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
@click=${this._selectMethod}
|
||||
.value=${"qr_code_webcam"}
|
||||
.disabled=${!window.isSecureContext}
|
||||
@@ -45,10 +47,12 @@ export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>`
|
||||
</ha-md-list-item>`
|
||||
: nothing
|
||||
}
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
@click=${this._selectMethod}
|
||||
.value=${"qr_code_manual"}
|
||||
>
|
||||
@@ -63,8 +67,10 @@ export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
@click=${this._selectMethod}
|
||||
.value=${"search_device"}
|
||||
>
|
||||
@@ -79,8 +85,8 @@ export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -90,6 +96,12 @@ export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
fireEvent(this, "z-wave-method-selected", { method });
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
+10
-13
@@ -2,35 +2,32 @@ import { mdiAccountKey, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import {
|
||||
stopKeydownEnterSpacePropagation,
|
||||
stopPropagation,
|
||||
} from "../../../../../common/dom/stop_propagation";
|
||||
import type { LocalizeKeys } from "../../../../../common/translations/localize";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../../../components/ha-dropdown";
|
||||
import "../../../../../components/ha-dropdown-item";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import type {
|
||||
ZwaveCredentialCapabilities,
|
||||
ZwaveUser,
|
||||
} from "../../../../../data/zwave_js-credentials";
|
||||
import {
|
||||
canAddZwaveUser,
|
||||
compatibleUserTypes,
|
||||
deleteZwaveAllUsers,
|
||||
deleteZwaveUser,
|
||||
getZwaveCredentialCapabilities,
|
||||
getZwaveUsers,
|
||||
compatibleUserTypes,
|
||||
} from "../../../../../data/zwave_js-credentials";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -223,10 +220,11 @@ class DialogZwaveCredentialManage extends LitElement {
|
||||
)}
|
||||
</p>`
|
||||
: html`
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${activeUsers.map(
|
||||
(user) => html`
|
||||
<ha-list-item-button
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
data-user-id=${user.user_id}
|
||||
@click=${this._handleUserClick}
|
||||
>
|
||||
@@ -266,12 +264,11 @@ class DialogZwaveCredentialManage extends LitElement {
|
||||
data-user-id=${user.user_id}
|
||||
?disabled=${this._busy}
|
||||
@click=${this._handleDeleteUserClick}
|
||||
@keydown=${stopKeydownEnterSpacePropagation}
|
||||
></ha-icon-button>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
|
||||
+15
-13
@@ -8,14 +8,14 @@ import { computeAreaName } from "../../../../../../common/entity/compute_area_na
|
||||
import { computeDeviceNameDisplay } from "../../../../../../common/entity/compute_device_name";
|
||||
import { getDeviceArea } from "../../../../../../common/entity/context/get_device_context";
|
||||
import { caseInsensitiveStringCompare } from "../../../../../../common/string/compare";
|
||||
import "../../../../../../components/animation/ha-fade-in";
|
||||
import "../../../../../../components/ha-button";
|
||||
import "../../../../../../components/ha-dialog";
|
||||
import "../../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../../components/ha-domain-icon";
|
||||
import "../../../../../../components/animation/ha-fade-in";
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-spinner";
|
||||
import "../../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../../components/list/ha-list-base";
|
||||
import {
|
||||
configEntriesContext,
|
||||
devicesContext,
|
||||
@@ -115,13 +115,13 @@ class DialogZWaveJSRebuildNetworkRoutesDetail extends DialogMixin<ZWaveJSRebuild
|
||||
)}
|
||||
</p>`
|
||||
: this._zwaveDevices
|
||||
? html`<ha-list-base>
|
||||
? html`<ha-md-list>
|
||||
${this._filteredDevices(
|
||||
this._progress,
|
||||
this._zwaveDevices
|
||||
).map(
|
||||
(device) => html`
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<ha-domain-icon
|
||||
slot="start"
|
||||
.domain=${device.domain}
|
||||
@@ -129,10 +129,10 @@ class DialogZWaveJSRebuildNetworkRoutesDetail extends DialogMixin<ZWaveJSRebuild
|
||||
></ha-domain-icon>
|
||||
<span slot="headline">${device.name}</span>
|
||||
<span slot="supporting-text">${device.areaName}</span>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-base>`
|
||||
</ha-md-list>`
|
||||
: nothing
|
||||
}
|
||||
</ha-dialog>
|
||||
@@ -208,14 +208,16 @@ class DialogZWaveJSRebuildNetworkRoutesDetail extends DialogMixin<ZWaveJSRebuild
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
ha-list-base {
|
||||
--ha-list-gap: var(--ha-space-2);
|
||||
ha-md-list {
|
||||
gap: var(--ha-space-2);
|
||||
min-height: 300px;
|
||||
}
|
||||
ha-list-item-base {
|
||||
--ha-row-item-min-height: 0;
|
||||
--ha-row-item-padding-block: var(--ha-space-1);
|
||||
--ha-row-item-padding-inline: var(--ha-space-2);
|
||||
ha-md-list-item {
|
||||
--md-list-item-two-line-container-height: 0;
|
||||
--md-list-item-top-space: var(--ha-space-1);
|
||||
--md-list-item-bottom-space: var(--ha-space-1);
|
||||
--md-list-item-leading-space: var(--ha-space-2);
|
||||
--md-list-item-trailing-space: var(--ha-space-2);
|
||||
border: var(--ha-border-width-sm) solid
|
||||
var(--ha-color-border-neutral-normal);
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
|
||||
+46
-40
@@ -23,12 +23,10 @@ import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import "../../../../../components/progress/ha-progress-ring";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import {
|
||||
@@ -307,12 +305,9 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
</ha-button>
|
||||
</div>
|
||||
<div class="card-content network-card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.network_card_title"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
@@ -323,8 +318,9 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
@@ -335,10 +331,11 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
this._provisioningEntries?.length
|
||||
? html`<ha-list-item-button
|
||||
? html`<ha-md-list-item
|
||||
type="link"
|
||||
href=${`provisioned?config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiQrcode}></ha-svg-icon>
|
||||
@@ -349,10 +346,10 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>`
|
||||
</ha-md-list-item>`
|
||||
: nothing
|
||||
}
|
||||
</ha-list-nav>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -362,12 +359,9 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
return html`
|
||||
<ha-card class="nav-card">
|
||||
<div class="card-content">
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.navigation.general"
|
||||
)}
|
||||
>
|
||||
<ha-list-item-button
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`options?config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiTune}></ha-svg-icon>
|
||||
@@ -382,8 +376,9 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`statistics?config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPoll}></ha-svg-icon>
|
||||
@@ -398,8 +393,9 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`logs?config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon
|
||||
@@ -417,8 +413,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button href="/config/analytics?section=zwave">
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="link" href="/config/analytics?section=zwave">
|
||||
<ha-svg-icon slot="start" .path=${mdiChartBox}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
@@ -440,8 +436,9 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`network-info?config_entry=${this.configEntryId}`}
|
||||
>
|
||||
<ha-svg-icon
|
||||
@@ -459,8 +456,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -493,8 +490,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
${this._restoreProgress}%
|
||||
</div>`
|
||||
: html`<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
: html`<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.nvm_backup.download_backup"
|
||||
@@ -519,8 +516,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.zwave_js.dashboard.nvm_backup.download_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.nvm_backup.restore_backup"
|
||||
@@ -548,8 +545,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
@change=${this._handleRestoreFileSelected}
|
||||
style="display: none"
|
||||
/>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.nvm_backup.migrate"
|
||||
@@ -570,8 +567,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.zwave_js.dashboard.nvm_backup.migrate_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>`
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>`
|
||||
}
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -908,6 +905,15 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
.network-card .card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+15
-6
@@ -3,8 +3,8 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import type { ZWaveJSControllerStatisticsUpdatedMessage } from "../../../../../data/zwave_js";
|
||||
import { subscribeZwaveControllerStatistics } from "../../../../../data/zwave_js";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
@@ -60,7 +60,7 @@ class ZWaveJSControllerStatistics extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
<div class="container">
|
||||
<ha-card>
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${this._renderStat("messages_tx")}
|
||||
${this._renderStat("messages_rx")}
|
||||
${this._renderStat("messages_dropped_tx")}
|
||||
@@ -69,7 +69,7 @@ class ZWaveJSControllerStatistics extends SubscribeMixin(LitElement) {
|
||||
${this._renderStat("timeout_ack")}
|
||||
${this._renderStat("timeout_response")}
|
||||
${this._renderStat("timeout_callback")}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
@@ -78,7 +78,7 @@ class ZWaveJSControllerStatistics extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _renderStat(key: string) {
|
||||
return html`
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.dashboard.statistics.${key}.label`
|
||||
@@ -90,7 +90,7 @@ class ZWaveJSControllerStatistics extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</span>
|
||||
<span slot="end">${this._statistics?.[key] ?? 0}</span>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,15 @@ class ZWaveJSControllerStatistics extends SubscribeMixin(LitElement) {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
span[slot="end"] {
|
||||
font-size: 0.95em;
|
||||
color: var(--primary-text-color);
|
||||
|
||||
+25
-12
@@ -2,8 +2,8 @@ import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import type { ZWaveJSNetwork } from "../../../../../data/zwave_js";
|
||||
import { fetchZwaveNetworkStatus } from "../../../../../data/zwave_js";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
@@ -49,8 +49,8 @@ class ZWaveJSNetworkInfoPage extends LitElement {
|
||||
<ha-card>
|
||||
${
|
||||
this._network
|
||||
? html`<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
? html`<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.home_id"
|
||||
@@ -59,8 +59,8 @@ class ZWaveJSNetworkInfoPage extends LitElement {
|
||||
<span slot="supporting-text">
|
||||
${formatHomeIdAsHex(this._network.controller.home_id)}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.driver_version"
|
||||
@@ -69,8 +69,8 @@ class ZWaveJSNetworkInfoPage extends LitElement {
|
||||
<span slot="supporting-text">
|
||||
${this._network.client.driver_version}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_version"
|
||||
@@ -79,8 +79,8 @@ class ZWaveJSNetworkInfoPage extends LitElement {
|
||||
<span slot="supporting-text">
|
||||
${this._network.client.server_version}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_url"
|
||||
@@ -89,8 +89,8 @@ class ZWaveJSNetworkInfoPage extends LitElement {
|
||||
<span slot="supporting-text">
|
||||
${this._network.client.ws_server_url}
|
||||
</span>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>`
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>`
|
||||
: nothing
|
||||
}
|
||||
</ha-card>
|
||||
@@ -111,6 +111,19 @@ class ZWaveJSNetworkInfoPage extends LitElement {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
--md-list-item-supporting-text-size: var(
|
||||
--md-list-item-label-text-size,
|
||||
var(--md-sys-typescale-body-large-size, 1rem)
|
||||
);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
+17
-8
@@ -3,8 +3,8 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import type {
|
||||
ZWaveJSClient,
|
||||
ZWaveJSNetwork,
|
||||
@@ -61,8 +61,8 @@ class ZWaveJSOptionsPage extends LitElement {
|
||||
<ha-card>
|
||||
${
|
||||
this._network
|
||||
? html`<ha-list-base>
|
||||
<ha-list-item-base>
|
||||
? html`<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.rebuild_network_routes"
|
||||
@@ -84,8 +84,8 @@ class ZWaveJSOptionsPage extends LitElement {
|
||||
"ui.panel.config.zwave_js.dashboard.rebuild_routes_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
<ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.remove_node"
|
||||
@@ -113,8 +113,8 @@ class ZWaveJSOptionsPage extends LitElement {
|
||||
"ui.panel.config.zwave_js.dashboard.remove_node_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-list-item-base>
|
||||
</ha-list-base>`
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>`
|
||||
: nothing
|
||||
}
|
||||
</ha-card>
|
||||
@@ -149,6 +149,15 @@ class ZWaveJSOptionsPage extends LitElement {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import "../../../components/ha-switch";
|
||||
import type { BackupConfig } from "../../../data/backup";
|
||||
import { fetchBackupConfig } from "../../../data/backup";
|
||||
import { getSupervisorUpdateConfig } from "../../../data/supervisor/update";
|
||||
@@ -157,23 +158,25 @@ export class DialogLabsPreviewFeatureEnable
|
||||
${
|
||||
createBackupTexts
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<span slot="headline">${createBackupTexts.title}</span>
|
||||
${
|
||||
createBackupTexts.description
|
||||
? html`
|
||||
<span slot="supporting-text">
|
||||
${createBackupTexts.description}
|
||||
</span>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.checked=${this._createBackup}
|
||||
@change=${this._createBackupChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">${createBackupTexts.title}</span>
|
||||
${
|
||||
createBackupTexts.description
|
||||
? html`
|
||||
<span slot="supporting-text">
|
||||
${createBackupTexts.description}
|
||||
</span>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.checked=${this._createBackup}
|
||||
@change=${this._createBackupChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
@@ -208,8 +211,10 @@ export class DialogLabsPreviewFeatureEnable
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: var(--ha-space-6);
|
||||
ha-md-list {
|
||||
background: none;
|
||||
--md-list-item-leading-space: var(--ha-space-6);
|
||||
--md-list-item-trailing-space: var(--ha-space-6);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-top: var(--ha-border-width-sm) solid var(--divider-color);
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-nav";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-icon-next";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import "./ha-config-http-form";
|
||||
import "./ha-config-network";
|
||||
@@ -57,14 +57,10 @@ class HaConfigSectionNetwork extends LitElement {
|
||||
"ui.panel.config.network.discovery.title"
|
||||
)}
|
||||
>
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.title"
|
||||
)}
|
||||
>
|
||||
<ha-md-list>
|
||||
${NETWORK_BROWSERS.map(
|
||||
(domain) => html`
|
||||
<ha-list-item-button href="/config/${domain}">
|
||||
<ha-md-list-item type="link" href="/config/${domain}">
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.network.discovery.${domain}`
|
||||
@@ -76,10 +72,10 @@ class HaConfigSectionNetwork extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-nav>
|
||||
</ha-md-list>
|
||||
</ha-card>
|
||||
`
|
||||
: ""
|
||||
@@ -106,6 +102,10 @@ class HaConfigSectionNetwork extends LitElement {
|
||||
margin-bottom: 24px;
|
||||
max-width: 600px;
|
||||
}
|
||||
.discovery-card ha-md-list {
|
||||
background: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import { isIPAddress } from "../../../common/string/is_ip_address";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import type { HaInput } from "../../../components/input/ha-input";
|
||||
import "../../../components/input/ha-input-copy";
|
||||
import type { HaInputCopy } from "../../../components/input/ha-input-copy";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import { fetchCloudStatus } from "../../../data/cloud";
|
||||
import { saveCoreConfig } from "../../../data/core";
|
||||
@@ -161,7 +161,7 @@ class ConfigUrlForm extends SubscribeMixin(LitElement) {
|
||||
${
|
||||
hasCloud
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.url.external_use_ha_cloud"
|
||||
@@ -173,7 +173,7 @@ class ConfigUrlForm extends SubscribeMixin(LitElement) {
|
||||
.checked=${this._cloudChecked}
|
||||
@change=${this._toggleCloud}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
@@ -261,7 +261,7 @@ class ConfigUrlForm extends SubscribeMixin(LitElement) {
|
||||
<h4>
|
||||
${this.hass.localize("ui.panel.config.url.internal_url_label")}
|
||||
</h4>
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.url.internal_url_automatic"
|
||||
@@ -278,7 +278,7 @@ class ConfigUrlForm extends SubscribeMixin(LitElement) {
|
||||
.checked=${!this._showCustomInternalUrl}
|
||||
@change=${this._toggleInternalAutomatic}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
|
||||
<div class="url-container">
|
||||
<ha-input-copy
|
||||
@@ -466,10 +466,12 @@ class ConfigUrlForm extends SubscribeMixin(LitElement) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-block: 0;
|
||||
--ha-row-item-padding-inline: 0;
|
||||
--ha-row-item-min-height: 48px;
|
||||
ha-md-list-item {
|
||||
--md-list-item-top-space: 0;
|
||||
--md-list-item-bottom-space: 0;
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-list-item-two-line-container-height: 48px;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import type {
|
||||
IntegrationManifest,
|
||||
IntegrationSetup,
|
||||
@@ -40,7 +40,7 @@ class IntegrationsStartupTime extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${this._setups?.map((setup) => {
|
||||
const manifest = this._manifests && this._manifests[setup.domain];
|
||||
const docLink =
|
||||
@@ -55,7 +55,7 @@ class IntegrationsStartupTime extends LitElement {
|
||||
|
||||
const setupSeconds = setup.seconds?.toFixed(2);
|
||||
return html`
|
||||
<ha-list-item-button .href=${docLink} target="_blank">
|
||||
<ha-md-list-item .href=${docLink} type="link" target="_blank">
|
||||
<img
|
||||
alt=""
|
||||
loading="lazy"
|
||||
@@ -71,17 +71,17 @@ class IntegrationsStartupTime extends LitElement {
|
||||
referrerpolicy="no-referrer"
|
||||
slot="start"
|
||||
/>
|
||||
<span slot="headline">
|
||||
<span>
|
||||
${domainToName(this.hass.localize, setup.domain, manifest)}
|
||||
</span>
|
||||
<span slot="supporting-text">${setup.domain}</span>
|
||||
<div slot="end">
|
||||
${setupSeconds ? html`${setupSeconds} s` : ""}
|
||||
</div>
|
||||
</ha-list-item-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
})}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,14 @@ import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import "../../../components/voice-assistant-brand-icon";
|
||||
import type { AssistPipeline } from "../../../data/assist_pipeline";
|
||||
import {
|
||||
@@ -53,6 +52,7 @@ import { showVoiceCommandDialog } from "../../../dialogs/voice-command-dialog/sh
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showVoiceAssistantPipelineDetailDialog } from "./show-dialog-voice-assistant-pipeline-detail";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
|
||||
@customElement("assist-pref")
|
||||
export class AssistPref extends LitElement {
|
||||
@@ -123,14 +123,17 @@ export class AssistPref extends LitElement {
|
||||
class="icon-link"
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<ha-list-base>
|
||||
<ha-list>
|
||||
${this._pipelines.map(
|
||||
(pipeline) => html`
|
||||
<ha-list-item-button
|
||||
<ha-list-item
|
||||
twoline
|
||||
hasMeta
|
||||
role="button"
|
||||
.id=${pipeline.id}
|
||||
@click=${this._editPipeline}
|
||||
>
|
||||
<span slot="headline">
|
||||
<span>
|
||||
${pipeline.name}
|
||||
${
|
||||
this._preferred === pipeline.id
|
||||
@@ -138,14 +141,13 @@ export class AssistPref extends LitElement {
|
||||
: ""
|
||||
}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
<span slot="secondary">
|
||||
${formatLanguageCode(pipeline.language, this.hass.locale)}
|
||||
</span>
|
||||
<ha-dropdown
|
||||
slot="end"
|
||||
slot="meta"
|
||||
placement="bottom-end"
|
||||
@click=${stopPropagation}
|
||||
@keydown=${this._handleDropdownKeydown}
|
||||
@wa-select=${this._handlePipelineMenuAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
@@ -197,10 +199,10 @@ export class AssistPref extends LitElement {
|
||||
<ha-svg-icon slot="icon" .path=${mdiTrashCan}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-list-item-button>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-base>
|
||||
</ha-list>
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
@click=${this._addPipeline}
|
||||
@@ -212,7 +214,7 @@ export class AssistPref extends LitElement {
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-button>
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.voice_assistants.expose.expose_new_entities"
|
||||
@@ -229,7 +231,7 @@ export class AssistPref extends LitElement {
|
||||
.disabled=${this._exposeNew === undefined}
|
||||
@change=${this._exposeNewToggleChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@@ -405,12 +407,6 @@ export class AssistPref extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleDropdownKeydown(ev: KeyboardEvent) {
|
||||
if (ev.key === " " || ev.key === "Enter") {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
@@ -432,7 +428,14 @@ export class AssistPref extends LitElement {
|
||||
direction: var(--direction);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-list-item-button span ha-svg-icon {
|
||||
ha-list-item {
|
||||
--mdc-list-item-meta-size: auto;
|
||||
--mdc-list-item-meta-display: flex;
|
||||
--mdc-list-side-padding-right: 8px;
|
||||
--mdc-list-side-padding-left: 16px;
|
||||
}
|
||||
|
||||
ha-list-item span ha-svg-icon {
|
||||
color: currentColor;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import "../../../components/voice-assistant-brand-icon";
|
||||
import type { CloudStatusLoggedIn } from "../../../data/cloud";
|
||||
import { updateCloudPref } from "../../../data/cloud";
|
||||
import type { ExposeEntitySettings } from "../../../data/expose";
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
setExposeNewEntities,
|
||||
} from "../../../data/expose";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../../../components/voice-assistant-brand-icon";
|
||||
|
||||
@customElement("cloud-alexa-pref")
|
||||
export class CloudAlexaPref extends LitElement {
|
||||
@@ -139,7 +139,7 @@ export class CloudAlexaPref extends LitElement {
|
||||
</ha-alert>`
|
||||
: nothing
|
||||
}
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.expose_new_entities"
|
||||
@@ -156,11 +156,11 @@ export class CloudAlexaPref extends LitElement {
|
||||
.disabled=${this._exposeNew === undefined}
|
||||
@change=${this._exposeNewToggleChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
alexa_registered
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.enable_state_reporting"
|
||||
@@ -176,7 +176,7 @@ export class CloudAlexaPref extends LitElement {
|
||||
.checked=${alexa_report_state}
|
||||
@change=${this._reportToggleChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
@@ -262,8 +262,10 @@ export class CloudAlexaPref extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
.header-actions {
|
||||
position: absolute;
|
||||
|
||||
@@ -5,14 +5,13 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import "../../../components/input/ha-input";
|
||||
import type { HaInput } from "../../../components/input/ha-input";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import "../../../components/voice-assistant-brand-icon";
|
||||
import type { CloudStatusLoggedIn } from "../../../data/cloud";
|
||||
import { updateCloudPref } from "../../../data/cloud";
|
||||
import type { ExposeEntitySettings } from "../../../data/expose";
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
} from "../../../data/expose";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
||||
import "../../../components/voice-assistant-brand-icon";
|
||||
|
||||
@customElement("cloud-google-pref")
|
||||
export class CloudGooglePref extends LitElement {
|
||||
@@ -138,7 +138,7 @@ export class CloudGooglePref extends LitElement {
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.expose_new_entities"
|
||||
@@ -155,7 +155,7 @@ export class CloudGooglePref extends LitElement {
|
||||
.disabled=${this._exposeNew === undefined}
|
||||
@change=${this._exposeNewToggleChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
google_registered
|
||||
? html`
|
||||
@@ -183,7 +183,7 @@ export class CloudGooglePref extends LitElement {
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.enable_state_reporting"
|
||||
@@ -199,9 +199,9 @@ export class CloudGooglePref extends LitElement {
|
||||
.checked=${google_report_state}
|
||||
@change=${this._reportToggleChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.google.security_devices"
|
||||
@@ -213,7 +213,7 @@ export class CloudGooglePref extends LitElement {
|
||||
)}</span
|
||||
>
|
||||
<ha-switch slot="end"></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
|
||||
<ha-input
|
||||
id="google_secure_devices_pin"
|
||||
@@ -344,8 +344,10 @@ export class CloudGooglePref extends LitElement {
|
||||
direction: var(--direction);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-input {
|
||||
width: 250px;
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-aliases-editor";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import "../../../components/voice-assistant-brand-icon";
|
||||
import { fetchCloudAlexaEntity } from "../../../data/alexa";
|
||||
import type { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
|
||||
@@ -190,7 +190,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
const exposedToAssist = this.exposed.conversation;
|
||||
|
||||
return html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<h3 slot="headline">
|
||||
${this.hass.localize("ui.dialogs.voice-settings.expose_header")}
|
||||
</h3>
|
||||
@@ -200,7 +200,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
.assistants=${uiAssistants}
|
||||
.checked=${anyExposed}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
${
|
||||
anyExposed
|
||||
? showAssistants.map((key) => {
|
||||
@@ -224,7 +224,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
this._googleEntity?.might_2fa;
|
||||
|
||||
return html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<voice-assistant-brand-icon
|
||||
slot="start"
|
||||
.voiceAssistantId=${key}
|
||||
@@ -274,7 +274,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
.disabled=${manualConfig || (!exposed && !supported)}
|
||||
.checked=${exposed}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
})
|
||||
: nothing
|
||||
@@ -321,7 +321,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</ha-alert>`
|
||||
: html`
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${
|
||||
this.hass.states[this.entityId]
|
||||
@@ -339,7 +339,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
.checked=${(this._aliases ?? this.entry.aliases).includes(null)}
|
||||
@change=${this._toggleEntityNameAlias}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
<ha-aliases-editor
|
||||
.aliases=${(this._aliases ?? this.entry.aliases).filter(
|
||||
(a): a is string => a !== null
|
||||
@@ -445,8 +445,10 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
margin: 32px;
|
||||
margin-top: 0;
|
||||
}
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
img {
|
||||
height: 32px;
|
||||
|
||||
@@ -22,7 +22,12 @@ import type {
|
||||
MarkerLocation,
|
||||
} from "../../../components/map/ha-locations-editor";
|
||||
import { saveCoreConfig } from "../../../data/core";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
import { subscribeEntityRegistry } from "../../../data/entity/entity_registry";
|
||||
import {
|
||||
HOME_ZONE_ENTITY_ID,
|
||||
zoneColor,
|
||||
} from "../../../common/map/entity-map-colors";
|
||||
import type {
|
||||
HomeZoneMutableParams,
|
||||
Zone,
|
||||
@@ -47,6 +52,19 @@ import { configSections } from "../config-sections";
|
||||
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
|
||||
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
|
||||
|
||||
interface PendingEdit {
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
// How close the saved value must come to a pending one to count as saved
|
||||
const PENDING_TOLERANCE: Record<keyof PendingEdit, number> = {
|
||||
latitude: 1e-7,
|
||||
longitude: 1e-7,
|
||||
radius: 0.5,
|
||||
};
|
||||
|
||||
@customElement("ha-config-zone")
|
||||
export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -61,21 +79,36 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _stateItems?: HassEntity[];
|
||||
|
||||
// Values dragged on the map, shown until the saved data reflects them, so
|
||||
// a re-render while the save is in flight does not move the marker back.
|
||||
// A failed save drops them and the marker returns to the saved values.
|
||||
@state() private _pendingEdits: Record<string, PendingEdit> = {};
|
||||
|
||||
@state() private _canEditCore = false;
|
||||
|
||||
@query("ha-locations-editor") private _map?: HaLocationsEditor;
|
||||
|
||||
private _regEntities: string[] = [];
|
||||
|
||||
// Registry creation order decides the zone colors
|
||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||
|
||||
// Storage zone id (its unique id) to entity id
|
||||
@state() private _zoneEntityIds: Record<string, string> = {};
|
||||
|
||||
// Bumped when entity map colors change to recompute the memoized locations
|
||||
@state() private _colorVersion = 0;
|
||||
|
||||
private _getZones = memoizeOne(
|
||||
(storageItems: Zone[], stateItems: HassEntity[]): MarkerLocation[] => {
|
||||
(
|
||||
storageItems: Zone[],
|
||||
stateItems: HassEntity[],
|
||||
zoneEntityIds: Record<string, string>,
|
||||
pendingEdits: Record<string, PendingEdit>,
|
||||
entityReg: EntityRegistryEntry[],
|
||||
_colorVersion: number
|
||||
): MarkerLocation[] => {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const zoneRadiusColor = computedStyles.getPropertyValue("--accent-color");
|
||||
const passiveRadiusColor = computedStyles.getPropertyValue(
|
||||
"--secondary-text-color"
|
||||
);
|
||||
const homeRadiusColor =
|
||||
computedStyles.getPropertyValue("--primary-color");
|
||||
|
||||
const stateLocations: MarkerLocation[] = stateItems.map(
|
||||
(entityState) => ({
|
||||
@@ -85,21 +118,28 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
latitude: entityState.attributes.latitude,
|
||||
longitude: entityState.attributes.longitude,
|
||||
radius: entityState.attributes.radius,
|
||||
radius_color:
|
||||
entityState.entity_id === "zone.home"
|
||||
? homeRadiusColor
|
||||
: entityState.attributes.passive
|
||||
? passiveRadiusColor
|
||||
: zoneRadiusColor,
|
||||
...pendingEdits[entityState.entity_id],
|
||||
radius_color: zoneColor(
|
||||
entityState.entity_id,
|
||||
!!entityState.attributes.passive,
|
||||
entityReg,
|
||||
computedStyles
|
||||
),
|
||||
location_editable:
|
||||
entityState.entity_id === "zone.home" && this._canEditCore,
|
||||
entityState.entity_id === HOME_ZONE_ENTITY_ID && this._canEditCore,
|
||||
radius_editable:
|
||||
entityState.entity_id === "zone.home" && this._canEditCore,
|
||||
entityState.entity_id === HOME_ZONE_ENTITY_ID && this._canEditCore,
|
||||
})
|
||||
);
|
||||
const storageLocations: MarkerLocation[] = storageItems.map((zone) => ({
|
||||
...zone,
|
||||
radius_color: zone.passive ? passiveRadiusColor : zoneRadiusColor,
|
||||
...pendingEdits[zone.id],
|
||||
radius_color: zoneColor(
|
||||
zoneEntityIds[zone.id] ?? `zone.${zone.id}`,
|
||||
!!zone.passive,
|
||||
entityReg,
|
||||
computedStyles
|
||||
),
|
||||
location_editable: true,
|
||||
radius_editable: true,
|
||||
}));
|
||||
@@ -110,9 +150,18 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entityReg = entities;
|
||||
this._regEntities = entities.map(
|
||||
(registryEntry) => registryEntry.entity_id
|
||||
);
|
||||
this._zoneEntityIds = Object.fromEntries(
|
||||
entities
|
||||
.filter((registryEntry) => registryEntry.platform === "zone")
|
||||
.map((registryEntry) => [
|
||||
registryEntry.unique_id,
|
||||
registryEntry.entity_id,
|
||||
])
|
||||
);
|
||||
this._filterStates();
|
||||
}),
|
||||
];
|
||||
@@ -265,7 +314,11 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
<ha-locations-editor
|
||||
.locations=${this._getZones(
|
||||
this._storageItems,
|
||||
this._stateItems
|
||||
this._stateItems,
|
||||
this._zoneEntityIds,
|
||||
this._pendingEdits,
|
||||
this._entityReg,
|
||||
this._colorVersion
|
||||
)}
|
||||
@location-updated=${this._locationUpdated}
|
||||
@radius-updated=${this._radiusUpdated}
|
||||
@@ -295,7 +348,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
protected updated() {
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
super.updated(changedProps);
|
||||
if (
|
||||
!this.route.path.startsWith("/edit/") ||
|
||||
!this._stateItems ||
|
||||
@@ -313,11 +367,99 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues<this>) {
|
||||
super.updated(changedProps);
|
||||
super.willUpdate(changedProps);
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (oldHass && this._stateItems) {
|
||||
this._getStates(oldHass);
|
||||
}
|
||||
// Zone colors come from theme variables
|
||||
if (oldHass && oldHass.themes !== this.hass.themes) {
|
||||
this._colorVersion++;
|
||||
}
|
||||
this._settlePendingEdits();
|
||||
}
|
||||
|
||||
// A pending edit is done once the saved data carries its values
|
||||
private _settlePendingEdits() {
|
||||
for (const [id, pending] of Object.entries(this._pendingEdits)) {
|
||||
const saved =
|
||||
this._storageItems?.find((zone) => zone.id === id) ??
|
||||
this.hass.states[id]?.attributes;
|
||||
if (
|
||||
saved &&
|
||||
(Object.keys(pending) as (keyof PendingEdit)[]).every(
|
||||
(key) =>
|
||||
Math.abs((saved[key] as number) - pending[key]!) <
|
||||
PENDING_TOLERANCE[key]
|
||||
)
|
||||
) {
|
||||
this._dropPendingEdit(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _dropPendingEdit(id: string) {
|
||||
const { [id]: _done, ...rest } = this._pendingEdits;
|
||||
this._pendingEdits = rest;
|
||||
}
|
||||
|
||||
// Saves for one zone run in order, so each sees the entry the previous one
|
||||
// produced and a failure only drops the values its own request carried
|
||||
private _saveQueue: Record<string, Promise<void>> = {};
|
||||
|
||||
private _saveEdit(id: string, pending: PendingEdit): Promise<void> {
|
||||
this._pendingEdits = {
|
||||
...this._pendingEdits,
|
||||
[id]: { ...this._pendingEdits[id], ...pending },
|
||||
};
|
||||
const save = (this._saveQueue[id] ?? Promise.resolve())
|
||||
.then(() => this._performSave(id, pending))
|
||||
.finally(() => {
|
||||
// Only the last save in the chain removes the queue entry
|
||||
if (this._saveQueue[id] === save) {
|
||||
delete this._saveQueue[id];
|
||||
}
|
||||
});
|
||||
this._saveQueue[id] = save;
|
||||
return save;
|
||||
}
|
||||
|
||||
private async _performSave(id: string, pending: PendingEdit) {
|
||||
try {
|
||||
if (id === HOME_ZONE_ENTITY_ID) {
|
||||
await saveCoreConfig(this.hass, pending);
|
||||
return;
|
||||
}
|
||||
const entry = this._storageItems!.find((item) => item.id === id);
|
||||
if (entry) {
|
||||
await this._updateEntry(entry, pending);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// The saved values are the truth again for what this request changed;
|
||||
// a later edit of other values stays pending for its own save
|
||||
this._dropPendingValues(id, pending);
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.zone.can_not_edit"),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _dropPendingValues(id: string, failed: PendingEdit) {
|
||||
const current = this._pendingEdits[id];
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
const rest = Object.fromEntries(
|
||||
Object.entries(current).filter(
|
||||
([key, value]) => failed[key as keyof PendingEdit] !== value
|
||||
)
|
||||
) as PendingEdit;
|
||||
if (Object.keys(rest).length) {
|
||||
this._pendingEdits = { ...this._pendingEdits, [id]: rest };
|
||||
} else {
|
||||
this._dropPendingEdit(id);
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
@@ -359,37 +501,19 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _locationUpdated(ev: CustomEvent) {
|
||||
if (ev.detail.id === "zone.home" && this._canEditCore) {
|
||||
await saveCoreConfig(this.hass, {
|
||||
latitude: ev.detail.location[0],
|
||||
longitude: ev.detail.location[1],
|
||||
});
|
||||
return;
|
||||
}
|
||||
const entry = this._storageItems!.find((item) => item.id === ev.detail.id);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
this._updateEntry(entry, {
|
||||
private _locationUpdated(ev: CustomEvent) {
|
||||
this._saveEdit(ev.detail.id, {
|
||||
latitude: ev.detail.location[0],
|
||||
longitude: ev.detail.location[1],
|
||||
});
|
||||
}
|
||||
|
||||
private async _radiusUpdated(ev: CustomEvent) {
|
||||
if (ev.detail.id === "zone.home" && this._canEditCore) {
|
||||
await saveCoreConfig(this.hass, {
|
||||
radius: Math.round(ev.detail.radius),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const entry = this._storageItems!.find((item) => item.id === ev.detail.id);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
this._updateEntry(entry, {
|
||||
radius: ev.detail.radius,
|
||||
private _radiusUpdated(ev: CustomEvent) {
|
||||
this._saveEdit(ev.detail.id, {
|
||||
radius:
|
||||
ev.detail.id === HOME_ZONE_ENTITY_ID
|
||||
? Math.round(ev.detail.radius)
|
||||
: ev.detail.radius,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -416,7 +540,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
|
||||
const entryId: string = (ev.currentTarget! as any).value;
|
||||
|
||||
if (this.narrow && entryId === "zone.home") {
|
||||
if (this.narrow && entryId === HOME_ZONE_ENTITY_ID) {
|
||||
this._editHomeZone(ev);
|
||||
return;
|
||||
}
|
||||
@@ -475,7 +599,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
longitude: values.longitude,
|
||||
radius: values.radius,
|
||||
});
|
||||
this._zoomZone("zone.home");
|
||||
this._zoomZone(HOME_ZONE_ENTITY_ID);
|
||||
}
|
||||
|
||||
private async _updateEntry(
|
||||
|
||||
@@ -8,8 +8,13 @@ import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { getColorByIndex } from "../../../common/color/colors";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { consume, ContextConsumer } from "@lit/context";
|
||||
import { resolveThemeColor } from "../../../common/color/compute-color";
|
||||
import {
|
||||
entityMapColor,
|
||||
zoneColor,
|
||||
} from "../../../common/map/entity-map-colors";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
@@ -31,6 +36,10 @@ import type {
|
||||
import type { MapLatLng } from "../../../common/map/map-engine";
|
||||
import type { HistoryStates } from "../../../data/history";
|
||||
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
|
||||
import type { Themes } from "../../../data/ws-themes";
|
||||
import { fullEntitiesContext, uiContext } from "../../../data/context";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import {
|
||||
@@ -58,6 +67,18 @@ interface GeoEntity {
|
||||
|
||||
@customElement("hui-map-card")
|
||||
class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
constructor() {
|
||||
super();
|
||||
new ContextConsumer(this, {
|
||||
context: fullEntitiesContext,
|
||||
subscribe: true,
|
||||
callback: (entries) => {
|
||||
this._entityReg = entries;
|
||||
this._mapEntities = this._getMapEntities();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public layout?: string;
|
||||
@@ -76,12 +97,19 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
|
||||
private _filteredMapEntities: HaMapEntity[] = [];
|
||||
|
||||
private _colorDict: Record<string, string> = {};
|
||||
|
||||
private _colorIndex = 0;
|
||||
|
||||
@state() private _error?: { code: string; message: string };
|
||||
|
||||
// Registry creation order decides the palette colors
|
||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||
|
||||
// Palette colors are read from the theme when the entities are built
|
||||
@state()
|
||||
@consume({ context: uiContext, subscribe: true })
|
||||
@transform<ContextType<typeof uiContext>, Themes>({
|
||||
transformer: ({ themes }) => themes,
|
||||
})
|
||||
private _themes?: Themes;
|
||||
|
||||
@state() private _clusterMarkers = true;
|
||||
|
||||
private _subscribed?: Promise<(() => Promise<void>) | undefined>;
|
||||
@@ -213,7 +241,12 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
<ha-map
|
||||
.entities=${this._filteredMapEntities}
|
||||
.zoom=${this._config.default_zoom ?? DEFAULT_ZOOM}
|
||||
.paths=${this._getHistoryPaths(this._config, this._stateHistory)}
|
||||
.paths=${this._getHistoryPaths(
|
||||
this._config,
|
||||
this._stateHistory,
|
||||
this._entityReg,
|
||||
this._themes
|
||||
)}
|
||||
.autoFit=${this._config.auto_fit || false}
|
||||
.fitZones=${this._config.fit_zones || false}
|
||||
.themeMode=${themeMode}
|
||||
@@ -321,6 +354,10 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
) {
|
||||
this._mapEntities = this._getMapEntities();
|
||||
}
|
||||
// Private state is not in keyof this
|
||||
if ((changedProps as PropertyValues).has("_themes") && this.hasUpdated) {
|
||||
this._mapEntities = this._getMapEntities();
|
||||
}
|
||||
|
||||
// Filter entities by conditions
|
||||
if (this._config?.conditions && this._mapEntities) {
|
||||
@@ -432,18 +469,19 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
this._clusterMarkers = !this._clusterMarkers;
|
||||
}
|
||||
|
||||
// The same color for an entity on every map; a config color still wins
|
||||
private _getColor(entityId: string): string {
|
||||
let color = this._colorDict[entityId];
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
const computedStyles = getComputedStyle(this);
|
||||
color = getColorByIndex(this._colorIndex, computedStyles);
|
||||
if (color) {
|
||||
this._colorIndex++;
|
||||
this._colorDict[entityId] = color;
|
||||
if (computeDomain(entityId) === "zone") {
|
||||
const stateObj = this.hass?.states[entityId];
|
||||
return zoneColor(
|
||||
entityId,
|
||||
!!stateObj?.attributes.passive,
|
||||
this._entityReg,
|
||||
computedStyles
|
||||
);
|
||||
}
|
||||
return color;
|
||||
return entityMapColor(entityId, this._entityReg, computedStyles);
|
||||
}
|
||||
|
||||
private _getSourceEntities(states?: HassEntities): GeoEntity[] {
|
||||
@@ -503,7 +541,10 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
private _getHistoryPaths = memoizeOne(
|
||||
(
|
||||
config: MapCardConfig,
|
||||
history?: HistoryStates
|
||||
history: HistoryStates | undefined,
|
||||
// Trail colors follow the registry order and the theme like the markers
|
||||
_entityReg: EntityRegistryEntry[],
|
||||
_themes: Themes | undefined
|
||||
): HaMapPaths[] | undefined => {
|
||||
if (!history || !(config.hours_to_show ?? DEFAULT_HOURS_TO_SHOW)) {
|
||||
return undefined;
|
||||
|
||||
@@ -12,11 +12,11 @@ import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-grid-size-picker";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-slider";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import "../../../../components/item/ha-row-item";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
@@ -145,7 +145,7 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
.columnMax=${gridOptions.max_columns}
|
||||
.step=${this._preciseMode ? 1 : GRID_COLUMN_MULTIPLIER}
|
||||
></ha-grid-size-picker>
|
||||
<ha-row-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.layout.auto_height"
|
||||
@@ -162,8 +162,8 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
.checked=${options.rows === "auto"}
|
||||
name="auto-height"
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.layout.full_width"
|
||||
@@ -180,8 +180,8 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
.checked=${options.columns === "full"}
|
||||
name="full-width"
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.layout.precise_mode"
|
||||
@@ -198,7 +198,7 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
.checked=${this._preciseMode}
|
||||
name="precise-mode"
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -4,11 +4,11 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import "../../../../components/item/ha-list-item-base";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import "../../../../components/user/ha-user-badge";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-switch";
|
||||
import type {
|
||||
LovelaceViewConfig,
|
||||
ShowViewConfig,
|
||||
@@ -66,10 +66,10 @@ export class HuiViewVisibilityEditor extends LitElement {
|
||||
"ui.panel.lovelace.editor.edit_view.visibility.select_users"
|
||||
)}
|
||||
</p>
|
||||
<ha-list-base>
|
||||
<ha-md-list>
|
||||
${this._sortedUsers(this._users).map(
|
||||
(user) => html`
|
||||
<ha-list-item-base>
|
||||
<ha-md-list-item>
|
||||
<ha-user-badge slot="start" .user=${user}></ha-user-badge>
|
||||
<span slot="headline">${user.name}</span>
|
||||
<ha-switch
|
||||
@@ -78,10 +78,10 @@ export class HuiViewVisibilityEditor extends LitElement {
|
||||
@change=${this._valChange}
|
||||
.checked=${this.checkUser(user.id)}
|
||||
></ha-switch>
|
||||
</ha-list-item-base>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-list-base>
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -135,10 +135,15 @@ export class HuiViewVisibilityEditor extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-list-item-base {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
--ha-row-item-padding-block: var(--ha-space-1);
|
||||
--ha-row-item-min-height: 48px;
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-list-item-top-space: var(--ha-space-1);
|
||||
--md-list-item-bottom-space: var(--ha-space-1);
|
||||
--md-list-item-one-line-container-height: 48px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,7 @@ import {
|
||||
subscribeFrontendUserData,
|
||||
} from "../data/frontend";
|
||||
import { forwardHaptic } from "../data/haptics";
|
||||
import {
|
||||
getServiceCallEntityIds,
|
||||
serviceCallWillDisconnect,
|
||||
} from "../data/service";
|
||||
import { serviceCallWillDisconnect } from "../data/service";
|
||||
import {
|
||||
DateFormat,
|
||||
FirstWeekday,
|
||||
@@ -35,12 +32,7 @@ import { preserveUnchangedRecord } from "../common/util/preserve-unchanged-recor
|
||||
import { subscribeFloorRegistry } from "../data/ws-floor_registry";
|
||||
import { subscribePanels } from "../data/ws-panels";
|
||||
import { translationMetadata } from "../resources/translations-metadata";
|
||||
import type {
|
||||
Constructor,
|
||||
HomeAssistant,
|
||||
ServiceCallRequest,
|
||||
ServiceCallResponse,
|
||||
} from "../types";
|
||||
import type { Constructor, HomeAssistant, ServiceCallResponse } from "../types";
|
||||
import {
|
||||
addBrandsAuth,
|
||||
clearBrandsTokenRefresh,
|
||||
@@ -122,7 +114,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
);
|
||||
}
|
||||
try {
|
||||
const response = (await callService(
|
||||
return (await callService(
|
||||
conn,
|
||||
domain,
|
||||
service,
|
||||
@@ -130,24 +122,11 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
target,
|
||||
returnResponse
|
||||
)) as ServiceCallResponse;
|
||||
this._reportEntityControlToExternalApp(
|
||||
domain,
|
||||
service,
|
||||
serviceData,
|
||||
target
|
||||
);
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
if (
|
||||
err.error?.code === ERR_CONNECTION_LOST &&
|
||||
serviceCallWillDisconnect(domain, service, serviceData)
|
||||
) {
|
||||
this._reportEntityControlToExternalApp(
|
||||
domain,
|
||||
service,
|
||||
serviceData,
|
||||
target
|
||||
);
|
||||
return { context: { id: "" } };
|
||||
}
|
||||
if (this.hass?.debugConnection) {
|
||||
@@ -426,28 +405,4 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
this._updateHass({});
|
||||
}
|
||||
}
|
||||
|
||||
private _reportEntityControlToExternalApp(
|
||||
domain: string,
|
||||
service: string,
|
||||
serviceData?: ServiceCallRequest["serviceData"],
|
||||
target?: ServiceCallRequest["target"]
|
||||
) {
|
||||
const external = this.hass?.auth.external;
|
||||
if (!external) {
|
||||
return;
|
||||
}
|
||||
const entityIds = getServiceCallEntityIds(serviceData, target);
|
||||
if (!entityIds.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
external.fireMessage({
|
||||
type: "entity/controlled",
|
||||
payload: { entity_ids: entityIds, domain, service },
|
||||
});
|
||||
} catch (_err) {
|
||||
// Reporting is best effort and must not fail the service call.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { EntityRegistryEntry } from "../../../src/data/entity/entity_registry";
|
||||
import {
|
||||
entityMapColor,
|
||||
HOME_ZONE_ENTITY_ID,
|
||||
zoneColor,
|
||||
} from "../../../src/common/map/entity-map-colors";
|
||||
|
||||
// Palette slot N reads back as "color-N", so an index is visible in the result
|
||||
const styles = {
|
||||
getPropertyValue: (name: string) => name.replace("--", ""),
|
||||
} as unknown as CSSStyleDeclaration;
|
||||
|
||||
const entry = (entityId: string, createdAt: number, id = entityId) =>
|
||||
({
|
||||
entity_id: entityId,
|
||||
created_at: createdAt,
|
||||
id,
|
||||
}) as EntityRegistryEntry;
|
||||
|
||||
describe("entity map colors", () => {
|
||||
it("colors zones, persons and trackers in one creation order", () => {
|
||||
const entries = [
|
||||
entry("zone.work", 40),
|
||||
entry("zone.school", 10),
|
||||
entry("person.anne", 20),
|
||||
entry("device_tracker.phone", 30),
|
||||
entry("light.kitchen", 25),
|
||||
];
|
||||
|
||||
expect(entityMapColor("zone.school", entries, styles)).toBe("color-1");
|
||||
// A person takes the next slot, never a zone's color
|
||||
expect(entityMapColor("person.anne", entries, styles)).toBe("color-2");
|
||||
expect(entityMapColor("device_tracker.phone", entries, styles)).toBe(
|
||||
"color-3"
|
||||
);
|
||||
expect(entityMapColor("zone.work", entries, styles)).toBe("color-4");
|
||||
});
|
||||
|
||||
it("breaks creation ties by registry id", () => {
|
||||
const entries = [entry("zone.b", 10, "b"), entry("zone.a", 10, "a")];
|
||||
|
||||
expect(entityMapColor("zone.a", entries, styles)).toBe("color-1");
|
||||
expect(entityMapColor("zone.b", entries, styles)).toBe("color-2");
|
||||
});
|
||||
|
||||
it("keeps the home zone out of the palette", () => {
|
||||
const entries = [entry(HOME_ZONE_ENTITY_ID, 10), entry("zone.work", 20)];
|
||||
|
||||
expect(entityMapColor("zone.work", entries, styles)).toBe("color-1");
|
||||
expect(zoneColor(HOME_ZONE_ENTITY_ID, false, entries, styles)).toBe(
|
||||
"primary-color"
|
||||
);
|
||||
});
|
||||
|
||||
it("mutes passive zones", () => {
|
||||
const entries = [entry("zone.quiet", 10)];
|
||||
|
||||
expect(zoneColor("zone.quiet", true, entries, styles)).toBe(
|
||||
"secondary-text-color"
|
||||
);
|
||||
expect(zoneColor("zone.quiet", false, entries, styles)).toBe("color-1");
|
||||
});
|
||||
|
||||
it("gives entities outside the registry a stable color", () => {
|
||||
const entries = [entry("zone.work", 10)];
|
||||
|
||||
const color = entityMapColor("zone.yaml_zone", entries, styles);
|
||||
expect(entityMapColor("zone.yaml_zone", entries, styles)).toBe(color);
|
||||
expect(entityMapColor("zone.other_yaml_zone", entries, styles)).not.toBe(
|
||||
color
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1030,10 +1030,7 @@ describe("MapLibreMapEngine", () => {
|
||||
expect(handle.radius).toBeCloseTo(110, 5);
|
||||
|
||||
element.dispatchEvent(new KeyboardEvent("keyup", { key: "ArrowUp" }));
|
||||
// The host echoes the old radius once before its save returns
|
||||
handle.update([52, 4], 100);
|
||||
expect(handle.radius).toBeCloseTo(110, 5);
|
||||
// A later identical update is a real change
|
||||
// Once committed, the host is the truth again
|
||||
handle.update([52, 4], 100);
|
||||
expect(handle.radius).toBe(100);
|
||||
});
|
||||
@@ -1100,25 +1097,24 @@ describe("MapLibreMapEngine", () => {
|
||||
expect(element.getAttribute("aria-valuenow")).toBe("150000");
|
||||
});
|
||||
|
||||
it("ignores one update echoing the values from before a drag", async () => {
|
||||
it("ignores host updates only while a drag is in progress", async () => {
|
||||
const { engine, ready } = await createEngine();
|
||||
await ready;
|
||||
const { handle, center } = addCircle(engine);
|
||||
|
||||
center.lngLat = [4.01, 52];
|
||||
center.fire("dragstart");
|
||||
// Nothing moves while a drag is in progress
|
||||
// The user's hand wins while dragging
|
||||
handle.update([53, 5], 500);
|
||||
expect(handle.center).toEqual([52, 4]);
|
||||
center.fire("drag");
|
||||
center.fire("dragend");
|
||||
|
||||
// The host saves and echoes the old values once; that is not a move back
|
||||
handle.update([52, 4], 100);
|
||||
expect(handle.center).toEqual([52, 4.01]);
|
||||
// A later identical update is a real change
|
||||
|
||||
// Afterwards the host is the truth, even when it moves the circle back
|
||||
handle.update([52, 4], 100);
|
||||
expect(handle.center).toEqual([52, 4]);
|
||||
expect(handle.radius).toBe(100);
|
||||
});
|
||||
|
||||
it("activates the center by click, Enter and Space, but not after a drag", async () => {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getServiceCallEntityIds } from "../../src/data/service";
|
||||
|
||||
describe("getServiceCallEntityIds", () => {
|
||||
it("returns an empty list when no entities are targeted", () => {
|
||||
expect(getServiceCallEntityIds()).toEqual([]);
|
||||
expect(getServiceCallEntityIds({ brightness: 50 }, {})).toEqual([]);
|
||||
expect(getServiceCallEntityIds({}, { area_id: "kitchen" })).toEqual([]);
|
||||
});
|
||||
|
||||
it("reads a single entity from target or service data", () => {
|
||||
expect(getServiceCallEntityIds({}, { entity_id: "light.a" })).toEqual([
|
||||
"light.a",
|
||||
]);
|
||||
expect(getServiceCallEntityIds({ entity_id: "light.a" })).toEqual([
|
||||
"light.a",
|
||||
]);
|
||||
});
|
||||
|
||||
it("prefers the target over the legacy service data entity ids", () => {
|
||||
expect(
|
||||
getServiceCallEntityIds(
|
||||
{ entity_id: ["light.a", "light.b"] },
|
||||
{ entity_id: ["light.b", "light.c"] }
|
||||
)
|
||||
).toEqual(["light.b", "light.c"]);
|
||||
});
|
||||
|
||||
it("deduplicates entity ids", () => {
|
||||
expect(
|
||||
getServiceCallEntityIds({}, { entity_id: ["light.a", "light.a"] })
|
||||
).toEqual(["light.a"]);
|
||||
});
|
||||
|
||||
it("splits comma separated ids and lowercases them like Core does", () => {
|
||||
expect(
|
||||
getServiceCallEntityIds({}, { entity_id: "Light.A, light.b ,light.a" })
|
||||
).toEqual(["light.a", "light.b"]);
|
||||
});
|
||||
|
||||
it("ignores wildcard, malformed, and non-string entity ids", () => {
|
||||
expect(getServiceCallEntityIds({ entity_id: "all" })).toEqual([]);
|
||||
expect(
|
||||
getServiceCallEntityIds(
|
||||
{},
|
||||
{ entity_id: ["all", "none", "", "light", 5] as unknown as string[] }
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
+15
-15
@@ -1,15 +1,4 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import {
|
||||
activateDemoSidebarPanel,
|
||||
DEMO_THEME_STORAGE_KEY,
|
||||
demoCardSelector,
|
||||
expectDemoDarkMode,
|
||||
expectStoredDemoTheme,
|
||||
loadDemo,
|
||||
moreInfoCardSelector,
|
||||
openDemoSidebar,
|
||||
reloadDemo,
|
||||
} from "./demo/helpers";
|
||||
import {
|
||||
expectNoPageErrors,
|
||||
NAVIGATION_TIMEOUT,
|
||||
@@ -18,6 +7,17 @@ import {
|
||||
SHELL_TIMEOUT,
|
||||
trackPageErrors,
|
||||
} from "./helpers";
|
||||
import {
|
||||
DEMO_THEME_STORAGE_KEY,
|
||||
activateDemoSidebarPanel,
|
||||
demoCardSelector,
|
||||
expectDemoDarkMode,
|
||||
expectStoredDemoTheme,
|
||||
loadDemo,
|
||||
moreInfoCardSelector,
|
||||
openDemoSidebar,
|
||||
reloadDemo,
|
||||
} from "./demo/helpers";
|
||||
|
||||
test.describe("Home Assistant Demo", () => {
|
||||
let pageErrors: ReturnType<typeof trackPageErrors>;
|
||||
@@ -82,10 +82,10 @@ test.describe("Home Assistant Demo", () => {
|
||||
|
||||
// Adapters come from the config entries, connections and advertisements
|
||||
// from the subscriptions. A count of zero means the data never arrived.
|
||||
const rows = dashboard.locator("ha-list-item-button div[slot='headline']");
|
||||
const rows = dashboard.locator("ha-md-list-item div[slot='headline']");
|
||||
await expect(rows).toHaveCount(3, { timeout: PANEL_TIMEOUT });
|
||||
// Asserted over the list as a whole so a single empty count still fails.
|
||||
await expect(dashboard.locator("ha-list-nav")).not.toHaveText(/\b0\b/, {
|
||||
await expect(dashboard.locator("ha-md-list")).not.toHaveText(/\b0\b/, {
|
||||
timeout: QUICK_TIMEOUT,
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ test.describe("Home Assistant Demo", () => {
|
||||
await loadDemo(page, "/#/config/bluetooth/adapter-info");
|
||||
|
||||
const adapters = page.locator(
|
||||
"bluetooth-adapter-info-page ha-list-item-button"
|
||||
"bluetooth-adapter-info-page ha-md-list-item"
|
||||
);
|
||||
await expect(adapters).toHaveCount(3, { timeout: PANEL_TIMEOUT });
|
||||
await expect(adapters.locator("ha-icon-button")).toHaveCount(1, {
|
||||
@@ -120,7 +120,7 @@ test.describe("Home Assistant Demo", () => {
|
||||
});
|
||||
// The device and entity counts come from the registries; a zero means the
|
||||
// fixtures did not reach them.
|
||||
await expect(dashboard.locator("ha-list-nav").first()).not.toHaveText(
|
||||
await expect(dashboard.locator("ha-md-list").first()).not.toHaveText(
|
||||
/\b0\b/,
|
||||
{ timeout: QUICK_TIMEOUT }
|
||||
);
|
||||
|
||||
@@ -2798,12 +2798,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-messageformat-parser@npm:3.5.18":
|
||||
version: 3.5.18
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.18"
|
||||
"@formatjs/icu-messageformat-parser@npm:3.5.17":
|
||||
version: 3.5.17
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.17"
|
||||
dependencies:
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.1.11"
|
||||
checksum: 10/fc2215a16571fcff3eb9f446878ef7649d40064a79648352ab79f68f4b48ce7b196125495eec82caa02edec78a9a094d8fd6fd5db0abeb353b95afb62672c2a2
|
||||
checksum: 10/cbb9daf23f65e4ef3697eae3be4c6888eda942fcde18929dfc6e96ef6c45052409d20340ece13476eeadd062e222809c46a2f26e3b453c9cbd4979bb6c0b0ae5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10472,7 +10472,7 @@ __metadata:
|
||||
html-minifier-terser: "npm:7.2.0"
|
||||
husky: "npm:9.1.7"
|
||||
idb-keyval: "npm:6.3.0"
|
||||
intl-messageformat: "npm:11.2.15"
|
||||
intl-messageformat: "npm:11.2.14"
|
||||
js-yaml: "npm:5.4.1"
|
||||
jsdom: "npm:30.0.1"
|
||||
jszip: "npm:3.10.2"
|
||||
@@ -10879,13 +10879,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intl-messageformat@npm:11.2.15":
|
||||
version: 11.2.15
|
||||
resolution: "intl-messageformat@npm:11.2.15"
|
||||
"intl-messageformat@npm:11.2.14":
|
||||
version: 11.2.14
|
||||
resolution: "intl-messageformat@npm:11.2.14"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.1.7"
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.5.18"
|
||||
checksum: 10/71d1448964e685c821b3680a89668aae3fe92f6b7774842ea5eb08830417846d4fcbcaf691930e4da1ceb2cc414eeab468c7e3dd4eb530a38ffed91530468ac5
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.5.17"
|
||||
checksum: 10/2720155c42bfd99a00d41f7bc9e2f5c2c83c2b6cb2353006b7d8e736a0f1ed20d0147406c1aeba0748731a0ce588c67304b52eb2fe92b42b928bf1ef6bffdcdf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user