mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-15 21:02:10 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 856086e4e8 |
@@ -0,0 +1,30 @@
|
||||
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,6 +34,7 @@ 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";
|
||||
@@ -139,6 +140,8 @@ 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;
|
||||
@@ -159,6 +162,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _columnsController = createColumnsController(this, MAX_COLUMNS);
|
||||
|
||||
private _memberships = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
@@ -357,6 +362,267 @@ 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}
|
||||
@@ -401,266 +667,10 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<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 class="container" ${this._columnsController.target()}>
|
||||
${columnContents.map(
|
||||
(contents) => html`<div class="column">${contents}</div>`
|
||||
)}
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@@ -904,30 +914,31 @@ 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: 1000px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
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);
|
||||
}
|
||||
.column {
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
width: 33%;
|
||||
flex-grow: 1;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.fullwidth {
|
||||
padding: 8px;
|
||||
padding: var(--ha-space-2);
|
||||
width: 100%;
|
||||
}
|
||||
.column > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
:host([narrow]) .column {
|
||||
width: 100%;
|
||||
margin-top: var(--ha-space-4);
|
||||
}
|
||||
|
||||
:host([narrow]) .container {
|
||||
|
||||
@@ -38,6 +38,7 @@ 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";
|
||||
@@ -175,6 +176,8 @@ 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;
|
||||
@@ -211,6 +214,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _columnsController = createColumnsController(this, MAX_COLUMNS);
|
||||
|
||||
private _integrations = memoizeOne(
|
||||
(
|
||||
device: DeviceRegistryEntry,
|
||||
@@ -749,6 +754,176 @@ 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}
|
||||
@@ -796,7 +971,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<div class="container">
|
||||
<div class="container" ${this._columnsController.target()}>
|
||||
<div class="header fullwidth">
|
||||
${area
|
||||
? html`<div class="header-name">
|
||||
@@ -849,175 +1024,9 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
</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>
|
||||
${columnContents.map(
|
||||
(contents) => html`<div class="column">${contents}</div>`
|
||||
)}
|
||||
</div>
|
||||
</hass-subpage>`;
|
||||
}
|
||||
@@ -1627,11 +1636,17 @@ 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: 1000px;
|
||||
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);
|
||||
}
|
||||
@@ -1692,12 +1707,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
.column,
|
||||
.fullwidth {
|
||||
padding: var(--ha-space-2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.column {
|
||||
width: 33%;
|
||||
flex-grow: 1;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
@@ -1739,10 +1753,6 @@ 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