mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20240904.0 (#21876)
This commit is contained in:
commit
b457d27c4c
@ -15,23 +15,29 @@ const brotliOptions = {
|
|||||||
};
|
};
|
||||||
const zopfliOptions = { threshold: 150 };
|
const zopfliOptions = { threshold: 150 };
|
||||||
|
|
||||||
const compressDistBrotli = (rootDir, modernDir) =>
|
const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
|
||||||
gulp
|
gulp
|
||||||
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
|
.src(
|
||||||
base: rootDir,
|
[
|
||||||
})
|
`${modernDir}/**/${filesGlob}`,
|
||||||
|
compressServiceWorker ? `${rootDir}/sw-modern.js` : undefined,
|
||||||
|
].filter(Boolean),
|
||||||
|
{
|
||||||
|
base: rootDir,
|
||||||
|
}
|
||||||
|
)
|
||||||
.pipe(brotli(brotliOptions))
|
.pipe(brotli(brotliOptions))
|
||||||
.pipe(gulp.dest(rootDir));
|
.pipe(gulp.dest(rootDir));
|
||||||
|
|
||||||
const compressDistZopfli = (rootDir, modernDir) =>
|
const compressDistZopfli = (rootDir, modernDir, compressModern = false) =>
|
||||||
gulp
|
gulp
|
||||||
.src(
|
.src(
|
||||||
[
|
[
|
||||||
`${rootDir}/**/${filesGlob}`,
|
`${rootDir}/**/${filesGlob}`,
|
||||||
`!${modernDir}/**/${filesGlob}`,
|
compressModern ? undefined : `!${modernDir}/**/${filesGlob}`,
|
||||||
`!${rootDir}/{sw-modern,service_worker}.js`,
|
`!${rootDir}/{sw-modern,service_worker}.js`,
|
||||||
`${rootDir}/{authorize,onboarding}.html`,
|
`${rootDir}/{authorize,onboarding}.html`,
|
||||||
],
|
].filter(Boolean),
|
||||||
{ base: rootDir }
|
{ base: rootDir }
|
||||||
)
|
)
|
||||||
.pipe(zopfli(zopfliOptions))
|
.pipe(zopfli(zopfliOptions))
|
||||||
@ -40,12 +46,20 @@ const compressDistZopfli = (rootDir, modernDir) =>
|
|||||||
const compressAppBrotli = () =>
|
const compressAppBrotli = () =>
|
||||||
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
||||||
const compressHassioBrotli = () =>
|
const compressHassioBrotli = () =>
|
||||||
compressDistBrotli(paths.hassio_output_root, paths.hassio_output_latest);
|
compressDistBrotli(
|
||||||
|
paths.hassio_output_root,
|
||||||
|
paths.hassio_output_latest,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const compressAppZopfli = () =>
|
const compressAppZopfli = () =>
|
||||||
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
|
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
|
||||||
const compressHassioZopfli = () =>
|
const compressHassioZopfli = () =>
|
||||||
compressDistZopfli(paths.hassio_output_root, paths.hassio_output_latest);
|
compressDistZopfli(
|
||||||
|
paths.hassio_output_root,
|
||||||
|
paths.hassio_output_latest,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
|
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
|
||||||
gulp.task(
|
gulp.task(
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"@babel/runtime": "7.25.6",
|
"@babel/runtime": "7.25.6",
|
||||||
"@braintree/sanitize-url": "7.1.0",
|
"@braintree/sanitize-url": "7.1.0",
|
||||||
"@codemirror/autocomplete": "6.18.0",
|
"@codemirror/autocomplete": "6.18.0",
|
||||||
"@codemirror/commands": "6.6.0",
|
"@codemirror/commands": "6.6.1",
|
||||||
"@codemirror/language": "6.10.2",
|
"@codemirror/language": "6.10.2",
|
||||||
"@codemirror/legacy-modes": "6.4.1",
|
"@codemirror/legacy-modes": "6.4.1",
|
||||||
"@codemirror/search": "6.5.6",
|
"@codemirror/search": "6.5.6",
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240903.1"
|
version = "20240904.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -25,7 +25,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
import { debounce } from "../../common/util/debounce";
|
import { debounce } from "../../common/util/debounce";
|
||||||
import { groupBy } from "../../common/util/group-by";
|
import { groupBy } from "../../common/util/group-by";
|
||||||
import { nextRender } from "../../common/util/render-status";
|
|
||||||
import { haStyleScrollbar } from "../../resources/styles";
|
import { haStyleScrollbar } from "../../resources/styles";
|
||||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
@ -35,6 +34,7 @@ import "../ha-svg-icon";
|
|||||||
import "../search-input";
|
import "../search-input";
|
||||||
import { filterData, sortData } from "./sort-filter";
|
import { filterData, sortData } from "./sort-filter";
|
||||||
import { LocalizeFunc } from "../../common/translations/localize";
|
import { LocalizeFunc } from "../../common/translations/localize";
|
||||||
|
import { nextRender } from "../../common/util/render-status";
|
||||||
|
|
||||||
export interface RowClickedEvent {
|
export interface RowClickedEvent {
|
||||||
id: string;
|
id: string;
|
||||||
@ -169,8 +169,6 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||||
|
|
||||||
@state() private _items: DataTableRowData[] = [];
|
|
||||||
|
|
||||||
@state() private _collapsedGroups: string[] = [];
|
@state() private _collapsedGroups: string[] = [];
|
||||||
|
|
||||||
private _checkableRowsCount?: number;
|
private _checkableRowsCount?: number;
|
||||||
@ -179,7 +177,9 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
private _sortColumns: SortableColumnContainer = {};
|
private _sortColumns: SortableColumnContainer = {};
|
||||||
|
|
||||||
private curRequest = 0;
|
private _curRequest = 0;
|
||||||
|
|
||||||
|
private _lastUpdate = 0;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@restoreScroll(".scroller") private _savedScrollPos?: number;
|
@restoreScroll(".scroller") private _savedScrollPos?: number;
|
||||||
@ -206,9 +206,9 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (this._items.length) {
|
if (this._filteredData.length) {
|
||||||
// Force update of location of rows
|
// Force update of location of rows
|
||||||
this._items = [...this._items];
|
this._filteredData = [...this._filteredData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,16 +291,13 @@ export class HaDataTable extends LitElement {
|
|||||||
properties.has("columns") ||
|
properties.has("columns") ||
|
||||||
properties.has("_filter") ||
|
properties.has("_filter") ||
|
||||||
properties.has("sortColumn") ||
|
properties.has("sortColumn") ||
|
||||||
properties.has("sortDirection") ||
|
properties.has("sortDirection")
|
||||||
properties.has("groupColumn") ||
|
|
||||||
properties.has("groupOrder") ||
|
|
||||||
properties.has("_collapsedGroups")
|
|
||||||
) {
|
) {
|
||||||
this._sortFilterData();
|
this._sortFilterData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.has("selectable") || properties.has("hiddenColumns")) {
|
if (properties.has("selectable") || properties.has("hiddenColumns")) {
|
||||||
this._items = [...this._items];
|
this._filteredData = [...this._filteredData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,7 +464,15 @@ export class HaDataTable extends LitElement {
|
|||||||
scroller
|
scroller
|
||||||
class="mdc-data-table__content scroller ha-scrollbar"
|
class="mdc-data-table__content scroller ha-scrollbar"
|
||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
.items=${this._items}
|
.items=${this._groupData(
|
||||||
|
this._filteredData,
|
||||||
|
localize,
|
||||||
|
this.appendRow,
|
||||||
|
this.hasFab,
|
||||||
|
this.groupColumn,
|
||||||
|
this.groupOrder,
|
||||||
|
this._collapsedGroups
|
||||||
|
)}
|
||||||
.keyFunction=${this._keyFunction}
|
.keyFunction=${this._keyFunction}
|
||||||
.renderItem=${renderRow}
|
.renderItem=${renderRow}
|
||||||
></lit-virtualizer>
|
></lit-virtualizer>
|
||||||
@ -602,8 +607,13 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
private async _sortFilterData() {
|
private async _sortFilterData() {
|
||||||
const startTime = new Date().getTime();
|
const startTime = new Date().getTime();
|
||||||
this.curRequest++;
|
const timeBetweenUpdate = startTime - this._lastUpdate;
|
||||||
const curRequest = this.curRequest;
|
const timeBetweenRequest = startTime - this._curRequest;
|
||||||
|
this._curRequest = startTime;
|
||||||
|
|
||||||
|
const forceUpdate =
|
||||||
|
!this._lastUpdate ||
|
||||||
|
(timeBetweenUpdate > 500 && timeBetweenRequest < 500);
|
||||||
|
|
||||||
let filteredData = this.data;
|
let filteredData = this.data;
|
||||||
if (this._filter) {
|
if (this._filter) {
|
||||||
@ -614,6 +624,10 @@ export class HaDataTable extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!forceUpdate && this._curRequest !== startTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const prom = this.sortColumn
|
const prom = this.sortColumn
|
||||||
? sortData(
|
? sortData(
|
||||||
filteredData,
|
filteredData,
|
||||||
@ -634,91 +648,103 @@ export class HaDataTable extends LitElement {
|
|||||||
setTimeout(resolve, 100 - elapsed);
|
setTimeout(resolve, 100 - elapsed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.curRequest !== curRequest) {
|
|
||||||
|
if (!forceUpdate && this._curRequest !== startTime) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localize = this.localizeFunc || this.hass.localize;
|
this._lastUpdate = startTime;
|
||||||
|
|
||||||
if (this.appendRow || this.hasFab || this.groupColumn) {
|
|
||||||
let items = [...data];
|
|
||||||
|
|
||||||
if (this.groupColumn) {
|
|
||||||
const grouped = groupBy(items, (item) => item[this.groupColumn!]);
|
|
||||||
if (grouped.undefined) {
|
|
||||||
// make sure ungrouped items are at the bottom
|
|
||||||
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
|
|
||||||
delete grouped.undefined;
|
|
||||||
}
|
|
||||||
const sorted: {
|
|
||||||
[key: string]: DataTableRowData[];
|
|
||||||
} = Object.keys(grouped)
|
|
||||||
.sort((a, b) => {
|
|
||||||
const orderA = this.groupOrder?.indexOf(a) ?? -1;
|
|
||||||
const orderB = this.groupOrder?.indexOf(b) ?? -1;
|
|
||||||
if (orderA !== orderB) {
|
|
||||||
if (orderA === -1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (orderB === -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return orderA - orderB;
|
|
||||||
}
|
|
||||||
return stringCompare(
|
|
||||||
["", "-", "—"].includes(a) ? "zzz" : a,
|
|
||||||
["", "-", "—"].includes(b) ? "zzz" : b,
|
|
||||||
this.hass.locale.language
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.reduce((obj, key) => {
|
|
||||||
obj[key] = grouped[key];
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
const groupedItems: DataTableRowData[] = [];
|
|
||||||
Object.entries(sorted).forEach(([groupName, rows]) => {
|
|
||||||
groupedItems.push({
|
|
||||||
append: true,
|
|
||||||
content: html`<div
|
|
||||||
class="mdc-data-table__cell group-header"
|
|
||||||
role="cell"
|
|
||||||
.group=${groupName}
|
|
||||||
@click=${this._collapseGroup}
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
.path=${mdiChevronUp}
|
|
||||||
class=${this._collapsedGroups.includes(groupName)
|
|
||||||
? "collapsed"
|
|
||||||
: ""}
|
|
||||||
>
|
|
||||||
</ha-icon-button>
|
|
||||||
${groupName === UNDEFINED_GROUP_KEY
|
|
||||||
? localize("ui.components.data-table.ungrouped")
|
|
||||||
: groupName || ""}
|
|
||||||
</div>`,
|
|
||||||
});
|
|
||||||
if (!this._collapsedGroups.includes(groupName)) {
|
|
||||||
groupedItems.push(...rows);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
items = groupedItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.appendRow) {
|
|
||||||
items.push({ append: true, content: this.appendRow });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasFab) {
|
|
||||||
items.push({ empty: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
this._items = items;
|
|
||||||
} else {
|
|
||||||
this._items = data;
|
|
||||||
}
|
|
||||||
this._filteredData = data;
|
this._filteredData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _groupData = memoizeOne(
|
||||||
|
(
|
||||||
|
data: DataTableRowData[],
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
appendRow,
|
||||||
|
hasFab: boolean,
|
||||||
|
groupColumn: string | undefined,
|
||||||
|
groupOrder: string[] | undefined,
|
||||||
|
collapsedGroups: string[]
|
||||||
|
) => {
|
||||||
|
if (appendRow || hasFab || groupColumn) {
|
||||||
|
let items = [...data];
|
||||||
|
|
||||||
|
if (groupColumn) {
|
||||||
|
const grouped = groupBy(items, (item) => item[groupColumn]);
|
||||||
|
if (grouped.undefined) {
|
||||||
|
// make sure ungrouped items are at the bottom
|
||||||
|
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
|
||||||
|
delete grouped.undefined;
|
||||||
|
}
|
||||||
|
const sorted: {
|
||||||
|
[key: string]: DataTableRowData[];
|
||||||
|
} = Object.keys(grouped)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const orderA = groupOrder?.indexOf(a) ?? -1;
|
||||||
|
const orderB = groupOrder?.indexOf(b) ?? -1;
|
||||||
|
if (orderA !== orderB) {
|
||||||
|
if (orderA === -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (orderB === -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return orderA - orderB;
|
||||||
|
}
|
||||||
|
return stringCompare(
|
||||||
|
["", "-", "—"].includes(a) ? "zzz" : a,
|
||||||
|
["", "-", "—"].includes(b) ? "zzz" : b,
|
||||||
|
this.hass.locale.language
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.reduce((obj, key) => {
|
||||||
|
obj[key] = grouped[key];
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
const groupedItems: DataTableRowData[] = [];
|
||||||
|
Object.entries(sorted).forEach(([groupName, rows]) => {
|
||||||
|
groupedItems.push({
|
||||||
|
append: true,
|
||||||
|
content: html`<div
|
||||||
|
class="mdc-data-table__cell group-header"
|
||||||
|
role="cell"
|
||||||
|
.group=${groupName}
|
||||||
|
@click=${this._collapseGroup}
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiChevronUp}
|
||||||
|
class=${collapsedGroups.includes(groupName)
|
||||||
|
? "collapsed"
|
||||||
|
: ""}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
${groupName === UNDEFINED_GROUP_KEY
|
||||||
|
? localize("ui.components.data-table.ungrouped")
|
||||||
|
: groupName || ""}
|
||||||
|
</div>`,
|
||||||
|
});
|
||||||
|
if (!collapsedGroups.includes(groupName)) {
|
||||||
|
groupedItems.push(...rows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
items = groupedItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appendRow) {
|
||||||
|
items.push({ append: true, content: appendRow });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFab) {
|
||||||
|
items.push({ empty: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _memFilterData = memoizeOne(
|
private _memFilterData = memoizeOne(
|
||||||
(
|
(
|
||||||
data: DataTableRowData[],
|
data: DataTableRowData[],
|
||||||
@ -802,8 +828,8 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
private _checkedRowsChanged() {
|
private _checkedRowsChanged() {
|
||||||
// force scroller to update, change it's items
|
// force scroller to update, change it's items
|
||||||
if (this._items.length) {
|
if (this._filteredData.length) {
|
||||||
this._items = [...this._items];
|
this._filteredData = [...this._filteredData];
|
||||||
}
|
}
|
||||||
fireEvent(this, "selection-changed", {
|
fireEvent(this, "selection-changed", {
|
||||||
value: this._checkedRows,
|
value: this._checkedRows,
|
||||||
|
155
src/components/ha-badge.ts
Normal file
155
src/components/ha-badge.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import "./ha-ripple";
|
||||||
|
|
||||||
|
type BadgeType = "badge" | "button";
|
||||||
|
|
||||||
|
@customElement("ha-badge")
|
||||||
|
export class HaBadge extends LitElement {
|
||||||
|
@property() public type: BadgeType = "badge";
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "icon-only" }) iconOnly = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const label = this.label;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="badge ${classMap({
|
||||||
|
"icon-only": this.iconOnly,
|
||||||
|
})}"
|
||||||
|
role=${ifDefined(this.type === "button" ? "button" : undefined)}
|
||||||
|
tabindex=${ifDefined(this.type === "button" ? "0" : undefined)}
|
||||||
|
>
|
||||||
|
<ha-ripple .disabled=${this.type !== "button"}></ha-ripple>
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
${this.iconOnly
|
||||||
|
? nothing
|
||||||
|
: html`<span class="info">
|
||||||
|
${label ? html`<span class="label">${label}</span>` : nothing}
|
||||||
|
<span class="content"><slot></slot></span>
|
||||||
|
</span>`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
--badge-color: var(--secondary-text-color);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
position: relative;
|
||||||
|
--ha-ripple-color: var(--badge-color);
|
||||||
|
--ha-ripple-hover-opacity: 0.04;
|
||||||
|
--ha-ripple-pressed-opacity: 0.12;
|
||||||
|
transition:
|
||||||
|
box-shadow 180ms ease-in-out,
|
||||||
|
border-color 180ms ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
height: var(--ha-badge-size, 36px);
|
||||||
|
min-width: var(--ha-badge-size, 36px);
|
||||||
|
padding: 0px 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: auto;
|
||||||
|
border-radius: var(
|
||||||
|
--ha-badge-border-radius,
|
||||||
|
calc(var(--ha-badge-size, 36px) / 2)
|
||||||
|
);
|
||||||
|
background: var(
|
||||||
|
--ha-card-background,
|
||||||
|
var(--card-background-color, white)
|
||||||
|
);
|
||||||
|
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||||
|
backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||||
|
border-width: var(--ha-card-border-width, 1px);
|
||||||
|
box-shadow: var(--ha-card-box-shadow, none);
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(
|
||||||
|
--ha-card-border-color,
|
||||||
|
var(--divider-color, #e0e0e0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.badge:focus-visible {
|
||||||
|
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||||
|
--shadow-focus: 0 0 0 1px var(--badge-color);
|
||||||
|
border-color: var(--badge-color);
|
||||||
|
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||||
|
}
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
[role="button"]:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-inline-start: initial;
|
||||||
|
text-align: center;
|
||||||
|
font-family: Roboto;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 10px;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
::slotted([slot="icon"]) {
|
||||||
|
--mdc-icon-size: 18px;
|
||||||
|
color: var(--badge-color);
|
||||||
|
line-height: 0;
|
||||||
|
margin-left: -4px;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-inline-start: -4px;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
::slotted(img[slot="icon"]) {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-inline-start: -10px;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
.badge.icon-only {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.badge.icon-only ::slotted([slot="icon"]) {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-inline-start: 0;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-badge": HaBadge;
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { computeCssColor } from "../../../common/color/compute-color";
|
import { computeCssColor } from "../../../common/color/compute-color";
|
||||||
@ -12,6 +11,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
|
|||||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
import { stateActive } from "../../../common/entity/state_active";
|
import { stateActive } from "../../../common/entity/state_active";
|
||||||
import { stateColorCss } from "../../../common/entity/state_color";
|
import { stateColorCss } from "../../../common/entity/state_color";
|
||||||
|
import "../../../components/ha-badge";
|
||||||
import "../../../components/ha-ripple";
|
import "../../../components/ha-ripple";
|
||||||
import "../../../components/ha-state-icon";
|
import "../../../components/ha-state-icon";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
@ -160,15 +160,14 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
|||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return html`
|
return html`
|
||||||
<div class="badge error">
|
<ha-badge .label=${entityId} class="error">
|
||||||
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon>
|
<ha-svg-icon
|
||||||
<span class="info">
|
slot="icon"
|
||||||
<span class="label">${entityId}</span>
|
.hass=${this.hass}
|
||||||
<span class="content">
|
.path=${mdiAlertCircle}
|
||||||
${this.hass.localize("ui.badge.entity.not_found")}
|
></ha-svg-icon>
|
||||||
</span>
|
${this.hass.localize("ui.badge.entity.not_found")}
|
||||||
</span>
|
</ha-badge>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,42 +203,32 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
|||||||
const content = showState ? stateDisplay : showName ? name : undefined;
|
const content = showState ? stateDisplay : showName ? name : undefined;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<ha-badge
|
||||||
style=${styleMap(style)}
|
.type=${this.hasAction ? "button" : "badge"}
|
||||||
class="badge ${classMap({
|
|
||||||
active,
|
|
||||||
"no-info": !showState && !showName,
|
|
||||||
"no-icon": !showIcon,
|
|
||||||
})}"
|
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
.actionHandler=${actionHandler({
|
.actionHandler=${actionHandler({
|
||||||
hasHold: hasAction(this._config!.hold_action),
|
hasHold: hasAction(this._config!.hold_action),
|
||||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||||
})}
|
})}
|
||||||
role=${ifDefined(this.hasAction ? "button" : undefined)}
|
.label=${label}
|
||||||
tabindex=${ifDefined(this.hasAction ? "0" : undefined)}
|
.iconOnly=${!content}
|
||||||
|
style=${styleMap(style)}
|
||||||
|
class=${classMap({ active })}
|
||||||
>
|
>
|
||||||
<ha-ripple .disabled=${!this.hasAction}></ha-ripple>
|
|
||||||
${showIcon
|
${showIcon
|
||||||
? imageUrl
|
? imageUrl
|
||||||
? html`<img src=${imageUrl} aria-hidden />`
|
? html`<img slot="icon" src=${imageUrl} aria-hidden />`
|
||||||
: html`
|
: html`
|
||||||
<ha-state-icon
|
<ha-state-icon
|
||||||
|
slot="icon"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.stateObj=${stateObj}
|
.stateObj=${stateObj}
|
||||||
.icon=${this._config.icon}
|
.icon=${this._config.icon}
|
||||||
></ha-state-icon>
|
></ha-state-icon>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${content
|
${content}
|
||||||
? html`
|
</ha-badge>
|
||||||
<span class="info">
|
|
||||||
${label ? html`<span class="label">${name}</span>` : nothing}
|
|
||||||
<span class="content">${content}</span>
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,119 +238,15 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
ha-badge {
|
||||||
--badge-color: var(--state-inactive-color);
|
--badge-color: var(--state-inactive-color);
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
}
|
||||||
.badge.error {
|
ha-badge.error {
|
||||||
--badge-color: var(--red-color);
|
--badge-color: var(--red-color);
|
||||||
}
|
}
|
||||||
.badge {
|
ha-badge.active {
|
||||||
position: relative;
|
|
||||||
--ha-ripple-color: var(--badge-color);
|
|
||||||
--ha-ripple-hover-opacity: 0.04;
|
|
||||||
--ha-ripple-pressed-opacity: 0.12;
|
|
||||||
transition:
|
|
||||||
box-shadow 180ms ease-in-out,
|
|
||||||
border-color 180ms ease-in-out;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
height: var(--ha-badge-size, 36px);
|
|
||||||
min-width: var(--ha-badge-size, 36px);
|
|
||||||
padding: 0px 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: auto;
|
|
||||||
border-radius: var(
|
|
||||||
--ha-badge-border-radius,
|
|
||||||
calc(var(--ha-badge-size, 36px) / 2)
|
|
||||||
);
|
|
||||||
background: var(
|
|
||||||
--ha-card-background,
|
|
||||||
var(--card-background-color, white)
|
|
||||||
);
|
|
||||||
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
|
|
||||||
backdrop-filter: var(--ha-card-backdrop-filter, none);
|
|
||||||
border-width: var(--ha-card-border-width, 1px);
|
|
||||||
box-shadow: var(--ha-card-box-shadow, none);
|
|
||||||
border-style: solid;
|
|
||||||
border-color: var(
|
|
||||||
--ha-card-border-color,
|
|
||||||
var(--divider-color, #e0e0e0)
|
|
||||||
);
|
|
||||||
--mdc-icon-size: 18px;
|
|
||||||
text-align: center;
|
|
||||||
font-family: Roboto;
|
|
||||||
}
|
|
||||||
.badge:focus-visible {
|
|
||||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
|
||||||
--shadow-focus: 0 0 0 1px var(--badge-color);
|
|
||||||
border-color: var(--badge-color);
|
|
||||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
|
||||||
}
|
|
||||||
button,
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
[role="button"]:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.badge.active {
|
|
||||||
--badge-color: var(--primary-color);
|
--badge-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
.info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding-right: 4px;
|
|
||||||
padding-inline-end: 4px;
|
|
||||||
padding-inline-start: initial;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-size: 10px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 10px;
|
|
||||||
letter-spacing: 0.1px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 16px;
|
|
||||||
letter-spacing: 0.1px;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
ha-state-icon,
|
|
||||||
ha-svg-icon {
|
|
||||||
color: var(--badge-color);
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.badge.no-info {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.badge:not(.no-icon):not(.no-info) img {
|
|
||||||
margin-left: -6px;
|
|
||||||
margin-inline-start: -6px;
|
|
||||||
margin-inline-end: initial;
|
|
||||||
}
|
|
||||||
.badge.no-icon .info {
|
|
||||||
padding-right: 4px;
|
|
||||||
padding-left: 4px;
|
|
||||||
padding-inline-end: 4px;
|
|
||||||
padding-inline-start: 4px;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@ import { mdiAlertCircle } from "@mdi/js";
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import "../../../components/ha-label-badge";
|
import "../../../components/ha-badge";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { showAlertDialog } from "../custom-card-helpers";
|
import { showAlertDialog } from "../custom-card-helpers";
|
||||||
import { LovelaceBadge } from "../types";
|
import { LovelaceBadge } from "../types";
|
||||||
import { HuiEntityBadge } from "./hui-entity-badge";
|
|
||||||
import { ErrorBadgeConfig } from "./types";
|
import { ErrorBadgeConfig } from "./types";
|
||||||
|
|
||||||
export const createErrorBadgeElement = (config) => {
|
export const createErrorBadgeElement = (config) => {
|
||||||
@ -55,41 +54,36 @@ export class HuiErrorBadge extends LitElement implements LovelaceBadge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<button class="badge error" @click=${this._viewDetail}>
|
<ha-badge
|
||||||
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon>
|
class="error"
|
||||||
<ha-ripple></ha-ripple>
|
@click=${this._viewDetail}
|
||||||
<span class="content">
|
type="button"
|
||||||
<span class="name">Error</span>
|
label="Error"
|
||||||
<span class="state">${this._config.error}</span>
|
>
|
||||||
</span>
|
<ha-svg-icon slot="icon" .path=${mdiAlertCircle}></ha-svg-icon>
|
||||||
</button>
|
<div class="content">${this._config.error}</div>
|
||||||
|
</ha-badge>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return css`
|
||||||
HuiEntityBadge.styles,
|
ha-badge {
|
||||||
css`
|
--badge-color: var(--error-color);
|
||||||
.badge.error {
|
--ha-card-border-color: var(--error-color);
|
||||||
--badge-color: var(--error-color);
|
}
|
||||||
border-color: var(--badge-color);
|
.content {
|
||||||
}
|
max-width: 100px;
|
||||||
ha-svg-icon {
|
overflow: hidden;
|
||||||
color: var(--badge-color);
|
text-overflow: ellipsis;
|
||||||
}
|
white-space: nowrap;
|
||||||
.state {
|
}
|
||||||
max-width: 100px;
|
pre {
|
||||||
overflow: hidden;
|
font-family: var(--code-font-family, monospace);
|
||||||
text-overflow: ellipsis;
|
white-space: break-spaces;
|
||||||
white-space: nowrap;
|
user-select: text;
|
||||||
}
|
}
|
||||||
pre {
|
`;
|
||||||
font-family: var(--code-font-family, monospace);
|
|
||||||
white-space: break-spaces;
|
|
||||||
user-select: text;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -1458,15 +1458,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@codemirror/commands@npm:6.6.0":
|
"@codemirror/commands@npm:6.6.1":
|
||||||
version: 6.6.0
|
version: 6.6.1
|
||||||
resolution: "@codemirror/commands@npm:6.6.0"
|
resolution: "@codemirror/commands@npm:6.6.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@codemirror/language": "npm:^6.0.0"
|
"@codemirror/language": "npm:^6.0.0"
|
||||||
"@codemirror/state": "npm:^6.4.0"
|
"@codemirror/state": "npm:^6.4.0"
|
||||||
"@codemirror/view": "npm:^6.27.0"
|
"@codemirror/view": "npm:^6.27.0"
|
||||||
"@lezer/common": "npm:^1.1.0"
|
"@lezer/common": "npm:^1.1.0"
|
||||||
checksum: 10/e984511b8ac06a1afe01005a50af7a0b6765cf350547d281a13aedbcccbc8620187c6dce55caa0f7c8ba409771e4abb4aa3bae824b6870249c8176b57cc4a42e
|
checksum: 10/533f33db08a4ff7e400ff61e48d34525afdc894f92442a5bf469ed24b1992a61bf726b61647e2faa552a2f4703c149c92026d9a1f9169f615582cdfc36fcb010
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -8922,7 +8922,7 @@ __metadata:
|
|||||||
"@braintree/sanitize-url": "npm:7.1.0"
|
"@braintree/sanitize-url": "npm:7.1.0"
|
||||||
"@bundle-stats/plugin-webpack-filter": "npm:4.15.0"
|
"@bundle-stats/plugin-webpack-filter": "npm:4.15.0"
|
||||||
"@codemirror/autocomplete": "npm:6.18.0"
|
"@codemirror/autocomplete": "npm:6.18.0"
|
||||||
"@codemirror/commands": "npm:6.6.0"
|
"@codemirror/commands": "npm:6.6.1"
|
||||||
"@codemirror/language": "npm:6.10.2"
|
"@codemirror/language": "npm:6.10.2"
|
||||||
"@codemirror/legacy-modes": "npm:6.4.1"
|
"@codemirror/legacy-modes": "npm:6.4.1"
|
||||||
"@codemirror/search": "npm:6.5.6"
|
"@codemirror/search": "npm:6.5.6"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user