mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-15 12:52:07 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f9538890a |
@@ -103,12 +103,29 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
|
||||
|
||||
if (!toProcess) {
|
||||
console.error("Unknown category", group.category);
|
||||
if (!group.pages) {
|
||||
if (!group.subsections && !group.pages) {
|
||||
group.pages = [];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (group.subsections) {
|
||||
// Listed pages keep their per-subsection order.
|
||||
for (const subsection of group.subsections) {
|
||||
for (const page of subsection.pages) {
|
||||
if (!toProcess.delete(page)) {
|
||||
console.error("Found unreferenced demo", page);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Any remaining pages land in a trailing "Other" subsection.
|
||||
const leftover = Array.from(toProcess).sort();
|
||||
if (leftover.length) {
|
||||
group.subsections.push({ header: "Other", pages: leftover });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Any pre-defined groups will not be sorted.
|
||||
if (group.pages) {
|
||||
for (const page of group.pages) {
|
||||
|
||||
@@ -62,6 +62,17 @@ Use `sidebar.js` when a page needs a visible section, section header, or determi
|
||||
- New categories without a sidebar entry are appended by the generator with their category name as the header.
|
||||
- If a listed page does not exist, the generator logs an error during `gather-gallery-pages`.
|
||||
|
||||
### Subsections
|
||||
|
||||
A section can group its pages under named subsections instead of one flat list. Use this for large categories where related pages should sit together.
|
||||
|
||||
- `subsections` is an array of `{ header, pages }`. It is mutually exclusive with a flat `pages` array on the same group.
|
||||
- Each subsection `header` is a non-collapsible label rendered inside the section's expansion panel; the section stays the only collapsible level.
|
||||
- Listed pages keep their per-subsection order.
|
||||
- Any pages found in the category but not listed in a subsection are collected into a generated `Other` subsection, appended alphabetically. The `Other` subsection is omitted when there are no leftovers.
|
||||
- A listed page that does not exist still logs an error during `gather-gallery-pages`.
|
||||
- Use sentence case for subsection headers and follow the content standards below.
|
||||
|
||||
## Markdown Pages
|
||||
|
||||
Use markdown pages for explanations, design guidance, API notes, and copy standards.
|
||||
|
||||
+164
-9
@@ -10,6 +10,10 @@ import {
|
||||
mdiViewDashboard,
|
||||
} from "@mdi/js";
|
||||
|
||||
// A group may list its pages flat in `pages`, or group them under named
|
||||
// `subsections`. The two are mutually exclusive. Listed pages keep their order;
|
||||
// any pages found in the category but not listed are appended alphabetically
|
||||
// (to a generated "Other" subsection when the group uses subsections).
|
||||
export default [
|
||||
{
|
||||
// This section has no header and so all page links are shown directly in the sidebar
|
||||
@@ -27,31 +31,162 @@ export default [
|
||||
category: "components",
|
||||
icon: mdiPuzzle,
|
||||
header: "Components",
|
||||
subsections: [
|
||||
{
|
||||
header: "Form and selectors",
|
||||
pages: [
|
||||
"ha-form",
|
||||
"ha-selector",
|
||||
"ha-select-box",
|
||||
"ha-input",
|
||||
"ha-textarea",
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Controls and sliders",
|
||||
pages: [
|
||||
"ha-button",
|
||||
"ha-control-button",
|
||||
"ha-progress-button",
|
||||
"ha-switch",
|
||||
"ha-control-switch",
|
||||
"ha-slider",
|
||||
"ha-control-slider",
|
||||
"ha-control-circular-slider",
|
||||
"ha-control-number-buttons",
|
||||
"ha-control-select",
|
||||
"ha-control-select-menu",
|
||||
"ha-hs-color-picker",
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Overlays",
|
||||
pages: [
|
||||
"ha-dialog",
|
||||
"ha-dialogs",
|
||||
"ha-adaptive-dialog",
|
||||
"ha-adaptive-popover",
|
||||
"ha-dropdown",
|
||||
"ha-tooltip",
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Lists and disclosure",
|
||||
pages: ["ha-list", "ha-expansion-panel", "ha-faded"],
|
||||
},
|
||||
{
|
||||
header: "Feedback and status",
|
||||
pages: ["ha-alert", "ha-spinner", "ha-tip", "ha-bar", "ha-gauge"],
|
||||
},
|
||||
{
|
||||
header: "Labels and text",
|
||||
pages: ["ha-badge", "ha-label-badge", "ha-chips", "ha-marquee-text"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "lovelace",
|
||||
icon: mdiViewDashboard,
|
||||
// Label for in the sidebar
|
||||
header: "Dashboards",
|
||||
// Specify order of pages. Any pages in the category folder but not listed here will
|
||||
// automatically be added after the pages listed here.
|
||||
pages: ["introduction"],
|
||||
subsections: [
|
||||
{
|
||||
header: "Introduction",
|
||||
pages: ["introduction"],
|
||||
},
|
||||
{
|
||||
header: "Entity cards",
|
||||
pages: [
|
||||
"entities-card",
|
||||
"entity-button-card",
|
||||
"entity-filter-card",
|
||||
"glance-card",
|
||||
"tile-card",
|
||||
"area-card",
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Picture cards",
|
||||
pages: [
|
||||
"picture-card",
|
||||
"picture-elements-card",
|
||||
"picture-entity-card",
|
||||
"picture-glance-card",
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Domain cards",
|
||||
pages: [
|
||||
"light-card",
|
||||
"thermostat-card",
|
||||
"alarm-panel-card",
|
||||
"gauge-card",
|
||||
"plant-card",
|
||||
"map-card",
|
||||
"media-control-card",
|
||||
"media-player-row",
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Layout and utility",
|
||||
pages: [
|
||||
"grid-and-stack-card",
|
||||
"conditional-card",
|
||||
"iframe-card",
|
||||
"markdown-card",
|
||||
"todo-list-card",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "more-info",
|
||||
icon: mdiInformationOutline,
|
||||
header: "More Info dialogs",
|
||||
subsections: [
|
||||
{
|
||||
header: "Climate and water",
|
||||
pages: ["climate", "humidifier", "water-heater", "fan"],
|
||||
},
|
||||
{
|
||||
header: "Covers and access",
|
||||
pages: ["cover", "lock", "lawn-mower", "vacuum"],
|
||||
},
|
||||
{
|
||||
header: "Lighting",
|
||||
pages: ["light", "scene"],
|
||||
},
|
||||
{
|
||||
header: "Media",
|
||||
pages: ["media-player"],
|
||||
},
|
||||
{
|
||||
header: "Inputs and values",
|
||||
pages: ["input-number", "input-text", "number", "timer"],
|
||||
},
|
||||
{
|
||||
header: "System",
|
||||
pages: ["update"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "automation",
|
||||
icon: mdiRobot,
|
||||
header: "Automation",
|
||||
pages: [
|
||||
"editor-trigger",
|
||||
"editor-condition",
|
||||
"editor-action",
|
||||
"trace",
|
||||
"trace-timeline",
|
||||
subsections: [
|
||||
{
|
||||
header: "Editors",
|
||||
pages: ["editor-trigger", "editor-condition", "editor-action"],
|
||||
},
|
||||
{
|
||||
header: "Descriptions",
|
||||
pages: ["describe-trigger", "describe-condition", "describe-action"],
|
||||
},
|
||||
{
|
||||
header: "Traces",
|
||||
pages: ["trace", "trace-timeline"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -64,6 +199,26 @@ export default [
|
||||
category: "date-time",
|
||||
icon: mdiCalendarClock,
|
||||
header: "Date and Time",
|
||||
subsections: [
|
||||
{
|
||||
header: "Date",
|
||||
pages: ["date"],
|
||||
},
|
||||
{
|
||||
header: "Time",
|
||||
pages: ["time", "time-seconds", "time-weekday"],
|
||||
},
|
||||
{
|
||||
header: "Combined",
|
||||
pages: [
|
||||
"date-time",
|
||||
"date-time-numeric",
|
||||
"date-time-seconds",
|
||||
"date-time-short",
|
||||
"date-time-short-year",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "misc",
|
||||
|
||||
+60
-20
@@ -40,15 +40,26 @@ interface GalleryPage {
|
||||
demo?: unknown;
|
||||
}
|
||||
|
||||
interface GallerySidebarSubsection {
|
||||
header: string;
|
||||
pages: string[];
|
||||
}
|
||||
|
||||
interface GallerySidebarGroup {
|
||||
category: string;
|
||||
header?: string;
|
||||
icon?: string;
|
||||
pages: string[];
|
||||
pages?: string[];
|
||||
subsections?: GallerySidebarSubsection[];
|
||||
}
|
||||
|
||||
const groupPages = (group: GallerySidebarGroup): string[] =>
|
||||
group.subsections
|
||||
? group.subsections.flatMap((subsection) => subsection.pages)
|
||||
: (group.pages ?? []);
|
||||
|
||||
const GALLERY_SIDEBAR = SIDEBAR as GallerySidebarGroup[];
|
||||
const DEFAULT_PAGE = `${GALLERY_SIDEBAR[0].category}/${GALLERY_SIDEBAR[0].pages[0]}`;
|
||||
const DEFAULT_PAGE = `${GALLERY_SIDEBAR[0].category}/${groupPages(GALLERY_SIDEBAR[0])[0]}`;
|
||||
|
||||
const mql = matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
@@ -284,26 +295,15 @@ class HaGallery extends LitElement {
|
||||
const sidebar: unknown[] = [];
|
||||
|
||||
for (const group of GALLERY_SIDEBAR) {
|
||||
const links: unknown[] = [];
|
||||
const expanded = group.pages.some(
|
||||
const expanded = groupPages(group).some(
|
||||
(page) => this._page === `${group.category}/${page}`
|
||||
);
|
||||
|
||||
for (const page of group.pages) {
|
||||
const key = `${group.category}/${page}`;
|
||||
if (!(key in PAGES)) {
|
||||
console.error("Undefined page referenced in sidebar.js:", key);
|
||||
continue;
|
||||
}
|
||||
links.push(
|
||||
this._renderPageLink(
|
||||
key,
|
||||
PAGES[key].metadata.title || page,
|
||||
group.header ? undefined : "main-navigation",
|
||||
group.header ? undefined : group.icon
|
||||
const content = group.subsections
|
||||
? group.subsections.map((subsection) =>
|
||||
this._renderSidebarSubsection(group, subsection)
|
||||
)
|
||||
);
|
||||
}
|
||||
: this._renderPageLinks(group, group.pages ?? []);
|
||||
|
||||
sidebar.push(
|
||||
group.header
|
||||
@@ -321,16 +321,46 @@ class HaGallery extends LitElement {
|
||||
.path=${group.icon}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
${links}
|
||||
${content}
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: links
|
||||
: content
|
||||
);
|
||||
}
|
||||
|
||||
return sidebar;
|
||||
}
|
||||
|
||||
private _renderSidebarSubsection(
|
||||
group: GallerySidebarGroup,
|
||||
subsection: GallerySidebarSubsection
|
||||
) {
|
||||
return html`
|
||||
<div class="gallery-sidebar-subheader">${subsection.header}</div>
|
||||
${this._renderPageLinks(group, subsection.pages)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPageLinks(group: GallerySidebarGroup, pages: string[]) {
|
||||
const links: unknown[] = [];
|
||||
for (const page of pages) {
|
||||
const key = `${group.category}/${page}`;
|
||||
if (!(key in PAGES)) {
|
||||
console.error("Undefined page referenced in sidebar.js:", key);
|
||||
continue;
|
||||
}
|
||||
links.push(
|
||||
this._renderPageLink(
|
||||
key,
|
||||
PAGES[key].metadata.title || page,
|
||||
group.header ? undefined : "main-navigation",
|
||||
group.header ? undefined : group.icon
|
||||
)
|
||||
);
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
private _renderPageLink(
|
||||
page: string,
|
||||
title: string,
|
||||
@@ -585,6 +615,16 @@ class HaGallery extends LitElement {
|
||||
width: var(--ha-sidebar-expanded-section-item-width, 248px);
|
||||
}
|
||||
|
||||
.gallery-sidebar-subheader {
|
||||
margin: var(--ha-space-2) var(--ha-space-4) var(--ha-space-1);
|
||||
color: var(--secondary-text-color);
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.gallery-sidebar-icon,
|
||||
.gallery-nav-item ha-svg-icon[slot="start"] {
|
||||
color: var(--sidebar-icon-color);
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import type { ReactiveControllerHost } from "lit";
|
||||
import { clamp } from "../number/clamp";
|
||||
|
||||
// Count columns from the container's real width (not the viewport) so a
|
||||
// docked sidebar is accounted for, like the dashboard sections view.
|
||||
const MIN_COLUMN_WIDTH = 320;
|
||||
const DEFAULT_COLUMN_GAP = 16;
|
||||
|
||||
const parsePx = (value: string) => parseInt(value, 10) || 0;
|
||||
|
||||
export const createColumnsController = (
|
||||
host: ReactiveControllerHost & Element,
|
||||
maxColumns: number
|
||||
) =>
|
||||
new ResizeController<number>(host, {
|
||||
target: null,
|
||||
skipInitial: true,
|
||||
callback: (entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) {
|
||||
return maxColumns;
|
||||
}
|
||||
const width = entry.contentRect.width;
|
||||
const gap =
|
||||
parsePx(getComputedStyle(entry.target).columnGap) || DEFAULT_COLUMN_GAP;
|
||||
const columns = Math.floor((width + gap) / (MIN_COLUMN_WIDTH + gap));
|
||||
return clamp(columns, 1, maxColumns);
|
||||
},
|
||||
});
|
||||
@@ -34,7 +34,6 @@ import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import { createColumnsController } from "../../../common/util/responsive-columns";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
@@ -140,8 +139,6 @@ const NAVIGATION_ACTIONS: {
|
||||
},
|
||||
] as const;
|
||||
|
||||
const MAX_COLUMNS = 3;
|
||||
|
||||
@customElement("ha-config-area-page")
|
||||
class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -162,8 +159,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _columnsController = createColumnsController(this, MAX_COLUMNS);
|
||||
|
||||
private _memberships = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
@@ -362,267 +357,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
)
|
||||
);
|
||||
|
||||
const infoColumn = html`
|
||||
${area.picture
|
||||
? html`<div class="img-container">
|
||||
<img alt=${area.name} src=${area.picture} />
|
||||
<ha-icon-button
|
||||
.path=${mdiPencil}
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.areas.edit_settings"
|
||||
)}
|
||||
class="img-edit-btn"
|
||||
></ha-icon-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
${area.picture && !this._newTriggersConditions
|
||||
? nothing
|
||||
: html`<div class="action-buttons">
|
||||
${area.picture
|
||||
? nothing
|
||||
: html`<ha-button
|
||||
appearance="filled"
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiImagePlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.areas.add_picture")}
|
||||
</ha-button>`}
|
||||
${this._newTriggersConditions
|
||||
? html`<ha-button
|
||||
appearance="filled"
|
||||
variant="brand"
|
||||
@click=${this._showAddToDialog}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.item"
|
||||
)}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
</div>`}
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
>${devices.length
|
||||
? html`<ha-list>
|
||||
${devices.map(
|
||||
(device) => html`
|
||||
<a href="/config/devices/device/${device.id}">
|
||||
<ha-list-item hasMeta>
|
||||
<span>${device.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`
|
||||
)}
|
||||
</ha-list>`
|
||||
: html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize("ui.panel.config.devices.no_devices")}
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.areas.editor.linked_entities_caption"
|
||||
)}
|
||||
>
|
||||
${nonAutomatedEntities.length
|
||||
? html`<ha-list>
|
||||
${nonAutomatedEntities.map(
|
||||
(entity) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openEntity}
|
||||
.entity=${entity}
|
||||
hasMeta
|
||||
>
|
||||
<span>${entity.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}</ha-list
|
||||
>`
|
||||
: html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.editor.no_linked_entities"
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
`;
|
||||
|
||||
const relatedColumn = html`
|
||||
${isComponentLoaded(this.hass.config, "automation")
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
)}
|
||||
>
|
||||
${groupedAutomations?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${groupedAutomations.map((automation) =>
|
||||
this._renderAutomation(
|
||||
automation.name,
|
||||
automation.entity
|
||||
)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${relatedAutomations?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${relatedAutomations.map((automation) =>
|
||||
this._renderAutomation(
|
||||
automation.name,
|
||||
automation.entity
|
||||
)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${!groupedAutomations?.length && !relatedAutomations?.length
|
||||
? html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.no_automations"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${isComponentLoaded(this.hass.config, "scene")
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.scenes_heading"
|
||||
)}
|
||||
>
|
||||
${groupedScenes?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${groupedScenes.map((scene) =>
|
||||
this._renderScene(scene.name, scene.entity)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${relatedScenes?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${relatedScenes.map((scene) =>
|
||||
this._renderScene(scene.name, scene.entity)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${!groupedScenes?.length && !relatedScenes?.length
|
||||
? html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.no_scenes"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${isComponentLoaded(this.hass.config, "script")
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.script.scripts_heading"
|
||||
)}
|
||||
>
|
||||
${groupedScripts?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
${groupedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}`
|
||||
: ""}
|
||||
${relatedScripts?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
${relatedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}`
|
||||
: ""}
|
||||
${!groupedScripts?.length && !relatedScripts?.length
|
||||
? html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.script.no_scripts"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
|
||||
const logbookColumn = html`
|
||||
${isComponentLoaded(this.hass.config, "logbook")
|
||||
? html`
|
||||
<ha-card outlined .header=${this.hass.localize("panel.logbook")}>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._allEntities(memberships)}
|
||||
.deviceIds=${this._allDeviceIds(memberships.devices)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
|
||||
// In 2 columns the logbook goes on the right, under the shorter
|
||||
// automations/scenes/scripts column, to balance the column heights.
|
||||
const columns =
|
||||
this._columnsController.value ?? (this.narrow ? 1 : MAX_COLUMNS);
|
||||
|
||||
const columnContents =
|
||||
columns >= 3
|
||||
? [[infoColumn], [relatedColumn], [logbookColumn]]
|
||||
: columns === 2
|
||||
? [[infoColumn], [relatedColumn, logbookColumn]]
|
||||
: [[infoColumn, relatedColumn, logbookColumn]];
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -667,10 +401,266 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<div class="container" ${this._columnsController.target()}>
|
||||
${columnContents.map(
|
||||
(contents) => html`<div class="column">${contents}</div>`
|
||||
)}
|
||||
<div class="container">
|
||||
<div class="column">
|
||||
${area.picture
|
||||
? html`<div class="img-container">
|
||||
<img alt=${area.name} src=${area.picture} />
|
||||
<ha-icon-button
|
||||
.path=${mdiPencil}
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.areas.edit_settings"
|
||||
)}
|
||||
class="img-edit-btn"
|
||||
></ha-icon-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
${area.picture && !this._newTriggersConditions
|
||||
? nothing
|
||||
: html`<div class="action-buttons">
|
||||
${area.picture
|
||||
? nothing
|
||||
: html`<ha-button
|
||||
appearance="filled"
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiImagePlus}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.add_picture"
|
||||
)}
|
||||
</ha-button>`}
|
||||
${this._newTriggersConditions
|
||||
? html`<ha-button
|
||||
appearance="filled"
|
||||
variant="brand"
|
||||
@click=${this._showAddToDialog}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.item"
|
||||
)}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
</div>`}
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
>${devices.length
|
||||
? html`<ha-list>
|
||||
${devices.map(
|
||||
(device) => html`
|
||||
<a href="/config/devices/device/${device.id}">
|
||||
<ha-list-item hasMeta>
|
||||
<span>${device.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`
|
||||
)}
|
||||
</ha-list>`
|
||||
: html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.no_devices"
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.areas.editor.linked_entities_caption"
|
||||
)}
|
||||
>
|
||||
${nonAutomatedEntities.length
|
||||
? html`<ha-list>
|
||||
${nonAutomatedEntities.map(
|
||||
(entity) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openEntity}
|
||||
.entity=${entity}
|
||||
hasMeta
|
||||
>
|
||||
<span>${entity.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}</ha-list
|
||||
>`
|
||||
: html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.editor.no_linked_entities"
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
</div>
|
||||
<div class="column">
|
||||
${isComponentLoaded(this.hass.config, "automation")
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
)}
|
||||
>
|
||||
${groupedAutomations?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${groupedAutomations.map((automation) =>
|
||||
this._renderAutomation(
|
||||
automation.name,
|
||||
automation.entity
|
||||
)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${relatedAutomations?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${relatedAutomations.map((automation) =>
|
||||
this._renderAutomation(
|
||||
automation.name,
|
||||
automation.entity
|
||||
)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${!groupedAutomations?.length && !relatedAutomations?.length
|
||||
? html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.no_automations"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${isComponentLoaded(this.hass.config, "scene")
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.scenes_heading"
|
||||
)}
|
||||
>
|
||||
${groupedScenes?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${groupedScenes.map((scene) =>
|
||||
this._renderScene(scene.name, scene.entity)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${relatedScenes?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
${relatedScenes.map((scene) =>
|
||||
this._renderScene(scene.name, scene.entity)
|
||||
)}</ha-list
|
||||
>`
|
||||
: ""}
|
||||
${!groupedScenes?.length && !relatedScenes?.length
|
||||
? html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.no_scenes"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${isComponentLoaded(this.hass.config, "script")
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.script.scripts_heading"
|
||||
)}
|
||||
>
|
||||
${groupedScripts?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
${groupedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}`
|
||||
: ""}
|
||||
${relatedScripts?.length
|
||||
? html`<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
${relatedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}`
|
||||
: ""}
|
||||
${!groupedScripts?.length && !relatedScripts?.length
|
||||
? html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.script.no_scripts"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="column">
|
||||
${isComponentLoaded(this.hass.config, "logbook")
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize("panel.logbook")}
|
||||
>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._allEntities(memberships)}
|
||||
.deviceIds=${this._allDeviceIds(memberships.devices)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@@ -914,31 +904,30 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--ha-space-4);
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
box-sizing: border-box;
|
||||
padding: var(--ha-space-2) var(--ha-space-4);
|
||||
margin-top: var(--ha-space-8);
|
||||
margin-bottom: var(--ha-space-8);
|
||||
max-width: 1000px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.column {
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 33%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.fullwidth {
|
||||
padding: var(--ha-space-2);
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.column > *:not(:first-child) {
|
||||
margin-top: var(--ha-space-4);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
:host([narrow]) .column {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host([narrow]) .container {
|
||||
|
||||
@@ -38,7 +38,6 @@ import { stringCompare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
import { createColumnsController } from "../../../common/util/responsive-columns";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
@@ -176,8 +175,6 @@ export interface DeviceAlert {
|
||||
|
||||
const DEVICE_ALERTS_INTERVAL = 30000;
|
||||
|
||||
const MAX_COLUMNS = 3;
|
||||
|
||||
@customElement("ha-config-device-page")
|
||||
export class HaConfigDevicePage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -214,8 +211,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _columnsController = createColumnsController(this, MAX_COLUMNS);
|
||||
|
||||
private _integrations = memoizeOne(
|
||||
(
|
||||
device: DeviceRegistryEntry,
|
||||
@@ -754,176 +749,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`
|
||||
: "";
|
||||
|
||||
const infoColumn = html`
|
||||
${this._deviceAlerts?.length
|
||||
? html`
|
||||
<div>
|
||||
${this._deviceAlerts.map(
|
||||
(alert) => html`
|
||||
<ha-alert .alertType=${alert.level}> ${alert.text} </ha-alert>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-device-info-card .hass=${this.hass} .device=${device}>
|
||||
${deviceInfo}
|
||||
${firstDeviceAction || actions.length
|
||||
? html`
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-button
|
||||
href=${ifDefined(firstDeviceAction!.href)}
|
||||
rel=${ifDefined(
|
||||
firstDeviceAction!.target ? "noreferrer" : undefined
|
||||
)}
|
||||
appearance="plain"
|
||||
target=${ifDefined(firstDeviceAction!.target)}
|
||||
class=${ifDefined(firstDeviceAction!.classes)}
|
||||
.variant=${firstDeviceAction!.classes?.includes("warning")
|
||||
? "danger"
|
||||
: "brand"}
|
||||
.action=${firstDeviceAction!.action}
|
||||
@click=${this._deviceActionClicked}
|
||||
>
|
||||
${firstDeviceAction!.label}
|
||||
${firstDeviceAction!.icon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class=${ifDefined(firstDeviceAction!.classes)}
|
||||
.path=${firstDeviceAction!.icon}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
${firstDeviceAction!.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${firstDeviceAction!.trailingIcon}
|
||||
slot="end"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
</ha-button>
|
||||
|
||||
${actions.length
|
||||
? html`
|
||||
<ha-dropdown
|
||||
@wa-select=${this._deviceActionSelected}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${actions.map((deviceAction, idx) => {
|
||||
const dropdownItem = html`<ha-dropdown-item
|
||||
.value=${idx}
|
||||
.data=${deviceAction}
|
||||
.variant=${deviceAction.classes?.includes("warning")
|
||||
? "danger"
|
||||
: "default"}
|
||||
>
|
||||
${deviceAction.icon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${deviceAction.icon}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
${deviceAction.label}
|
||||
${deviceAction.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
slot="details"
|
||||
.path=${deviceAction.trailingIcon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</ha-dropdown-item>`;
|
||||
return deviceAction.href
|
||||
? html`<a
|
||||
href=${deviceAction.href}
|
||||
target=${ifDefined(deviceAction.target)}
|
||||
rel=${ifDefined(
|
||||
deviceAction.target ? "noreferrer" : undefined
|
||||
)}
|
||||
>${dropdownItem}
|
||||
</a>`
|
||||
: dropdownItem;
|
||||
})}
|
||||
</ha-dropdown>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-device-info-card>
|
||||
`;
|
||||
|
||||
const entitiesColumn = html`
|
||||
${(
|
||||
[
|
||||
"control",
|
||||
"sensor",
|
||||
"notify",
|
||||
"event",
|
||||
"assist",
|
||||
"config",
|
||||
"diagnostic",
|
||||
] as const
|
||||
).map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
? html`
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.header=${this.hass.localize(
|
||||
`ui.panel.config.devices.entities.${category}`
|
||||
)}
|
||||
.deviceName=${deviceName}
|
||||
.entities=${entitiesByCategory[category]}
|
||||
.showHidden=${device.disabled_by !== null}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
<ha-device-via-devices-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
></ha-device-via-devices-card>
|
||||
`;
|
||||
|
||||
const logbookColumn = isComponentLoaded(this.hass.config, "logbook")
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<h1 class="card-header">${this.hass.localize("panel.logbook")}</h1>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._entityIds(entities)}
|
||||
.deviceIds=${this._deviceIdInList(this.deviceId)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: nothing;
|
||||
|
||||
const columns =
|
||||
this._columnsController.value ?? (this.narrow ? 1 : MAX_COLUMNS);
|
||||
|
||||
const columnContents =
|
||||
columns >= 3
|
||||
? [[infoColumn, relatedCard], [entitiesColumn], [logbookColumn]]
|
||||
: columns === 2
|
||||
? [[infoColumn, relatedCard, logbookColumn], [entitiesColumn]]
|
||||
: [[infoColumn, entitiesColumn, relatedCard, logbookColumn]];
|
||||
|
||||
return html`<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
@@ -971,7 +796,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<div class="container" ${this._columnsController.target()}>
|
||||
<div class="container">
|
||||
<div class="header fullwidth">
|
||||
${area
|
||||
? html`<div class="header-name">
|
||||
@@ -1024,9 +849,175 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
${columnContents.map(
|
||||
(contents) => html`<div class="column">${contents}</div>`
|
||||
)}
|
||||
<div class="column">
|
||||
${this._deviceAlerts?.length
|
||||
? html`
|
||||
<div>
|
||||
${this._deviceAlerts.map(
|
||||
(alert) => html`
|
||||
<ha-alert .alertType=${alert.level}>
|
||||
${alert.text}
|
||||
</ha-alert>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-device-info-card .hass=${this.hass} .device=${device}>
|
||||
${deviceInfo}
|
||||
${firstDeviceAction || actions.length
|
||||
? html`
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-button
|
||||
href=${ifDefined(firstDeviceAction!.href)}
|
||||
rel=${ifDefined(
|
||||
firstDeviceAction!.target ? "noreferrer" : undefined
|
||||
)}
|
||||
appearance="plain"
|
||||
target=${ifDefined(firstDeviceAction!.target)}
|
||||
class=${ifDefined(firstDeviceAction!.classes)}
|
||||
.variant=${firstDeviceAction!.classes?.includes("warning")
|
||||
? "danger"
|
||||
: "brand"}
|
||||
.action=${firstDeviceAction!.action}
|
||||
@click=${this._deviceActionClicked}
|
||||
>
|
||||
${firstDeviceAction!.label}
|
||||
${firstDeviceAction!.icon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class=${ifDefined(firstDeviceAction!.classes)}
|
||||
.path=${firstDeviceAction!.icon}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
${firstDeviceAction!.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${firstDeviceAction!.trailingIcon}
|
||||
slot="end"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
</ha-button>
|
||||
|
||||
${actions.length
|
||||
? html`
|
||||
<ha-dropdown
|
||||
@wa-select=${this._deviceActionSelected}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${actions.map((deviceAction, idx) => {
|
||||
const dropdownItem = html`<ha-dropdown-item
|
||||
.value=${idx}
|
||||
.data=${deviceAction}
|
||||
.variant=${deviceAction.classes?.includes(
|
||||
"warning"
|
||||
)
|
||||
? "danger"
|
||||
: "default"}
|
||||
>
|
||||
${deviceAction.icon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${deviceAction.icon}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
${deviceAction.label}
|
||||
${deviceAction.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
slot="details"
|
||||
.path=${deviceAction.trailingIcon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</ha-dropdown-item>`;
|
||||
return deviceAction.href
|
||||
? html`<a
|
||||
href=${deviceAction.href}
|
||||
target=${ifDefined(deviceAction.target)}
|
||||
rel=${ifDefined(
|
||||
deviceAction.target
|
||||
? "noreferrer"
|
||||
: undefined
|
||||
)}
|
||||
>${dropdownItem}
|
||||
</a>`
|
||||
: dropdownItem;
|
||||
})}
|
||||
</ha-dropdown>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-device-info-card>
|
||||
${!this.narrow ? relatedCard : ""}
|
||||
</div>
|
||||
<div class="column">
|
||||
${(
|
||||
[
|
||||
"control",
|
||||
"sensor",
|
||||
"notify",
|
||||
"event",
|
||||
"assist",
|
||||
"config",
|
||||
"diagnostic",
|
||||
] as const
|
||||
).map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
? html`
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.header=${this.hass.localize(
|
||||
`ui.panel.config.devices.entities.${category}`
|
||||
)}
|
||||
.deviceName=${deviceName}
|
||||
.entities=${entitiesByCategory[category]}
|
||||
.showHidden=${device.disabled_by !== null}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
<ha-device-via-devices-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
></ha-device-via-devices-card>
|
||||
</div>
|
||||
<div class="column">
|
||||
${this.narrow ? relatedCard : ""}
|
||||
${isComponentLoaded(this.hass.config, "logbook")
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<h1 class="card-header">
|
||||
${this.hass.localize("panel.logbook")}
|
||||
</h1>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._entityIds(entities)}
|
||||
.deviceIds=${this._deviceIdInList(this.deviceId)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</hass-subpage>`;
|
||||
}
|
||||
@@ -1636,17 +1627,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--ha-space-4);
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
box-sizing: border-box;
|
||||
padding: var(--ha-space-2) var(--ha-space-4);
|
||||
max-width: 1000px;
|
||||
margin-top: var(--ha-space-8);
|
||||
margin-bottom: var(--ha-space-8);
|
||||
}
|
||||
@@ -1707,11 +1692,12 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
.column,
|
||||
.fullwidth {
|
||||
padding: var(--ha-space-2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.column {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 33%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
@@ -1753,6 +1739,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
margin-top: var(--ha-space-4);
|
||||
}
|
||||
|
||||
:host([narrow]) .column {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
|
||||
Reference in New Issue
Block a user