Compare commits

..

1 Commits

Author SHA1 Message Date
Paul Bottein
ebe5207b6e Improve datatable 2024-06-25 17:05:51 +02:00
50 changed files with 981 additions and 1974 deletions

View File

@@ -1,6 +1,5 @@
import type { IFuseOptions } from "fuse.js";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { stripDiacritics } from "../../../src/common/string/strip-diacritics"; import type { IFuseOptions } from "fuse.js";
import { StoreAddon } from "../../../src/data/supervisor/store"; import { StoreAddon } from "../../../src/data/supervisor/store";
export function filterAndSort(addons: StoreAddon[], filter: string) { export function filterAndSort(addons: StoreAddon[], filter: string) {
@@ -9,8 +8,7 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: Math.min(filter.length, 2), minMatchCharLength: Math.min(filter.length, 2),
threshold: 0.2, threshold: 0.2,
getFn: (obj, path) => stripDiacritics(Fuse.config.getFn(obj, path)),
}; };
const fuse = new Fuse(addons, options); const fuse = new Fuse(addons, options);
return fuse.search(stripDiacritics(filter)).map((result) => result.item); return fuse.search(filter).map((result) => result.item);
} }

View File

@@ -178,7 +178,7 @@
"@types/leaflet-draw": "1.0.11", "@types/leaflet-draw": "1.0.11",
"@types/lodash.merge": "4.6.9", "@types/lodash.merge": "4.6.9",
"@types/luxon": "3.4.2", "@types/luxon": "3.4.2",
"@types/mocha": "10.0.7", "@types/mocha": "10.0.6",
"@types/qrcode": "1.5.5", "@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4", "@types/serve-handler": "6.1.4",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
@@ -237,7 +237,7 @@
"terser-webpack-plugin": "5.3.10", "terser-webpack-plugin": "5.3.10",
"transform-async-modules-webpack-plugin": "1.1.1", "transform-async-modules-webpack-plugin": "1.1.1",
"ts-lit-plugin": "2.0.2", "ts-lit-plugin": "2.0.2",
"typescript": "5.5.2", "typescript": "5.4.5",
"webpack": "5.92.1", "webpack": "5.92.1",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.4", "webpack-dev-server": "5.0.4",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,3 +0,0 @@
<svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 430 B

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20240626.0" version = "20240610.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"

View File

@@ -1,4 +1,3 @@
import { stripDiacritics } from "../strip-diacritics";
import { fuzzyScore } from "./filter"; import { fuzzyScore } from "./filter";
/** /**
@@ -20,10 +19,10 @@ export const fuzzySequentialMatch = (
for (const word of item.strings) { for (const word of item.strings) {
const scores = fuzzyScore( const scores = fuzzyScore(
filter, filter,
stripDiacritics(filter.toLowerCase()), filter.toLowerCase(),
0, 0,
word, word,
stripDiacritics(word.toLowerCase()), word.toLowerCase(),
0, 0,
true true
); );

View File

@@ -1,2 +0,0 @@
export const stripDiacritics = (str) =>
str.normalize("NFD").replace(/[\u0300-\u036F]/g, "");

View File

@@ -1,280 +0,0 @@
import "@material/mwc-list";
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { createCloseHeading } from "../ha-dialog";
import "../ha-list-item";
import "../ha-sortable";
import "../ha-button";
import { DataTableColumnContainer, DataTableColumnData } from "./ha-data-table";
import { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("dialog-data-table-settings")
export class DialogDataTableSettings extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: DataTableSettingsDialogParams;
@state() private _columnOrder?: string[];
@state() private _hiddenColumns?: string[];
public showDialog(params: DataTableSettingsDialogParams) {
this._params = params;
this._columnOrder = params.columnOrder;
this._hiddenColumns = params.hiddenColumns;
}
public closeDialog() {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
private _sortedColumns = memoizeOne(
(
columns: DataTableColumnContainer,
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) =>
Object.keys(columns)
.filter((col) => !columns[col].hidden)
.sort((a, b) => {
const orderA = columnOrder?.indexOf(a) ?? -1;
const orderB = columnOrder?.indexOf(b) ?? -1;
const hiddenA =
hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden);
const hiddenB =
hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden);
if (hiddenA !== hiddenB) {
return hiddenA ? 1 : -1;
}
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
}
return orderA - orderB;
})
.reduce(
(arr, key) => {
arr.push({ key, ...columns[key] });
return arr;
},
[] as (DataTableColumnData & { key: string })[]
)
);
protected render() {
if (!this._params) {
return nothing;
}
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.components.data-table.settings.header")
)}
>
<ha-sortable
@item-moved=${this._columnMoved}
draggable-selector=".draggable"
handle-selector=".handle"
>
<mwc-list>
${repeat(
columns,
(col) => col.key,
(col, _idx) => {
const canMove = !col.main && col.moveable !== false;
const canHide = !col.main && col.hideable !== false;
const isVisible = !(this._columnOrder &&
this._columnOrder.includes(col.key)
? this._hiddenColumns?.includes(col.key) ?? col.defaultHidden
: col.defaultHidden);
return html`<ha-list-item
hasMeta
class=${classMap({
hidden: !isVisible,
draggable: canMove && isVisible,
})}
graphic="icon"
noninteractive
>${col.title || col.label || col.key}
${canMove && isVisible
? html`<ha-svg-icon
class="handle"
.path=${mdiDrag}
slot="graphic"
></ha-svg-icon>`
: nothing}
<ha-icon-button
tabindex="0"
class="action"
.disabled=${!canHide}
.hidden=${!isVisible}
.path=${isVisible ? mdiEye : mdiEyeOff}
slot="meta"
.label=${this.hass!.localize(
`ui.components.data-table.settings.${isVisible ? "hide" : "show"}`,
{ title: typeof col.title === "string" ? col.title : "" }
)}
.column=${col.key}
@click=${this._toggle}
></ha-icon-button>
</ha-list-item>`;
}
)}
</mwc-list>
</ha-sortable>
<ha-button slot="secondaryAction" @click=${this._reset}
>${this.hass.localize(
"ui.components.data-table.settings.restore"
)}</ha-button
>
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.components.data-table.settings.done")}
</ha-button>
</ha-dialog>
`;
}
private _columnMoved(ev: CustomEvent): void {
ev.stopPropagation();
if (!this._params) {
return;
}
const { oldIndex, newIndex } = ev.detail;
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
const columnOrder = columns.map((column) => column.key);
const option = columnOrder.splice(oldIndex, 1)[0];
columnOrder.splice(newIndex, 0, option);
this._columnOrder = columnOrder;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
}
_toggle(ev) {
if (!this._params) {
return;
}
const column = ev.target.column;
const wasHidden = ev.target.hidden;
const hidden = [
...(this._hiddenColumns ??
Object.entries(this._params.columns)
.filter(([_key, col]) => col.defaultHidden)
.map(([key]) => key)),
];
if (wasHidden && hidden.includes(column)) {
hidden.splice(hidden.indexOf(column), 1);
} else if (!wasHidden) {
hidden.push(column);
}
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
if (!this._columnOrder) {
this._columnOrder = columns.map((col) => col.key);
} else {
columns.forEach((col) => {
if (!this._columnOrder!.includes(col.key)) {
this._columnOrder!.push(col.key);
if (col.defaultHidden) {
hidden.push(col.key);
}
}
});
}
this._hiddenColumns = hidden;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
}
_reset() {
this._columnOrder = undefined;
this._hiddenColumns = undefined;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-max-width: 500px;
--dialog-z-index: 10;
--dialog-content-padding: 0 8px;
}
@media all and (max-width: 451px) {
ha-dialog {
--vertical-align-dialog: flex-start;
--dialog-surface-margin-top: 250px;
--ha-dialog-border-radius: 28px 28px 0 0;
--mdc-dialog-min-height: calc(100% - 250px);
--mdc-dialog-max-height: calc(100% - 250px);
}
}
ha-list-item {
--mdc-list-side-padding: 12px;
overflow: visible;
}
.hidden {
color: var(--disabled-text-color);
}
.handle {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}
.actions {
display: flex;
flex-direction: row;
}
ha-icon-button {
display: block;
margin: -12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-data-table-settings": DialogDataTableSettings;
}
}

View File

@@ -65,10 +65,6 @@ export interface DataTableSortColumnData {
valueColumn?: string; valueColumn?: string;
direction?: SortingDirection; direction?: SortingDirection;
groupable?: boolean; groupable?: boolean;
moveable?: boolean;
hideable?: boolean;
defaultHidden?: boolean;
showNarrow?: boolean;
} }
export interface DataTableColumnData<T = any> extends DataTableSortColumnData { export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
@@ -83,7 +79,6 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
| "overflow-menu" | "overflow-menu"
| "flex"; | "flex";
template?: (row: T) => TemplateResult | string | typeof nothing; template?: (row: T) => TemplateResult | string | typeof nothing;
extraTemplate?: (row: T) => TemplateResult | string | typeof nothing;
width?: string; width?: string;
maxWidth?: string; maxWidth?: string;
grows?: boolean; grows?: boolean;
@@ -110,8 +105,6 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
export class HaDataTable extends LitElement { export class HaDataTable extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@property({ type: Object }) public columns: DataTableColumnContainer = {}; @property({ type: Object }) public columns: DataTableColumnContainer = {};
@property({ type: Array }) public data: DataTableRowData[] = []; @property({ type: Array }) public data: DataTableRowData[] = [];
@@ -152,10 +145,6 @@ export class HaDataTable extends LitElement {
@property({ attribute: false }) public initialCollapsedGroups?: string[]; @property({ attribute: false }) public initialCollapsedGroups?: string[];
@property({ attribute: false }) public hiddenColumns?: string[];
@property({ attribute: false }) public columnOrder?: string[];
@state() private _filterable = false; @state() private _filterable = false;
@state() private _filter = ""; @state() private _filter = "";
@@ -246,7 +235,6 @@ export class HaDataTable extends LitElement {
(column: ClonedDataTableColumnData) => { (column: ClonedDataTableColumnData) => {
delete column.title; delete column.title;
delete column.template; delete column.template;
delete column.extraTemplate;
} }
); );
@@ -284,44 +272,12 @@ export class HaDataTable extends LitElement {
this._sortFilterData(); this._sortFilterData();
} }
if (properties.has("selectable") || properties.has("hiddenColumns")) { if (properties.has("selectable")) {
this._items = [...this._items]; this._items = [...this._items];
} }
} }
private _sortedColumns = memoizeOne(
(columns: DataTableColumnContainer, columnOrder?: string[]) => {
if (!columnOrder || !columnOrder.length) {
return columns;
}
return Object.keys(columns)
.sort((a, b) => {
const orderA = columnOrder!.indexOf(a);
const orderB = columnOrder!.indexOf(b);
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
}
return orderA - orderB;
})
.reduce((obj, key) => {
obj[key] = columns[key];
return obj;
}, {}) as DataTableColumnContainer;
}
);
protected render() { protected render() {
const columns = this._sortedColumns(this.columns, this.columnOrder);
const renderRow = (row: DataTableRowData, index: number) =>
this._renderRow(columns, this.narrow, row, index);
return html` return html`
<div class="mdc-data-table"> <div class="mdc-data-table">
<slot name="header" @slotchange=${this._calcTableHeight}> <slot name="header" @slotchange=${this._calcTableHeight}>
@@ -370,14 +326,9 @@ export class HaDataTable extends LitElement {
</div> </div>
` `
: ""} : ""}
${Object.entries(columns).map(([key, column]) => { ${Object.entries(this.columns).map(([key, column]) => {
if ( if (column.hidden) {
column.hidden || return "";
(this.columnOrder && this.columnOrder.includes(key)
? this.hiddenColumns?.includes(key) ?? column.defaultHidden
: column.defaultHidden)
) {
return nothing;
} }
const sorted = key === this.sortColumn; const sorted = key === this.sortColumn;
const classes = { const classes = {
@@ -448,7 +399,7 @@ export class HaDataTable extends LitElement {
@scroll=${this._saveScrollPos} @scroll=${this._saveScrollPos}
.items=${this._items} .items=${this._items}
.keyFunction=${this._keyFunction} .keyFunction=${this._keyFunction}
.renderItem=${renderRow} .renderItem=${this._renderRow}
></lit-virtualizer> ></lit-virtualizer>
`} `}
</div> </div>
@@ -458,12 +409,7 @@ export class HaDataTable extends LitElement {
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row; private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
private _renderRow = ( private _renderRow = (row: DataTableRowData, index: number) => {
columns: DataTableColumnContainer,
narrow: boolean,
row: DataTableRowData,
index: number
) => {
// not sure how this happens... // not sure how this happens...
if (!row) { if (!row) {
return nothing; return nothing;
@@ -508,14 +454,8 @@ export class HaDataTable extends LitElement {
</div> </div>
` `
: ""} : ""}
${Object.entries(columns).map(([key, column]) => { ${Object.entries(this.columns).map(([key, column]) => {
if ( if (column.hidden) {
(narrow && !column.main && !column.showNarrow) ||
column.hidden ||
(this.columnOrder && this.columnOrder.includes(key)
? this.hiddenColumns?.includes(key) ?? column.defaultHidden
: column.defaultHidden)
) {
return nothing; return nothing;
} }
return html` return html`
@@ -542,38 +482,7 @@ export class HaDataTable extends LitElement {
}) })
: ""} : ""}
> >
${column.template ${column.template ? column.template(row) : row[key]}
? column.template(row)
: narrow && column.main
? html`<div class="primary">${row[key]}</div>
<div class="secondary">
${Object.entries(columns)
.filter(
([key2, column2]) =>
!column2.hidden &&
!column2.main &&
!column2.showNarrow &&
!(this.columnOrder &&
this.columnOrder.includes(key2)
? this.hiddenColumns?.includes(key2) ??
column2.defaultHidden
: column2.defaultHidden)
)
.map(
([key2, column2], i) =>
html`${i !== 0
? " ⸱ "
: nothing}${column2.template
? column2.template(row)
: row[key2]}`
)}
</div>
${column.extraTemplate
? column.extraTemplate(row)
: nothing}`
: html`${row[key]}${column.extraTemplate
? column.extraTemplate(row)
: nothing}`}
</div> </div>
`; `;
})} })}
@@ -952,7 +861,6 @@ export class HaDataTable extends LitElement {
width: 100%; width: 100%;
border: 0; border: 0;
white-space: nowrap; white-space: nowrap;
position: relative;
} }
.mdc-data-table__cell { .mdc-data-table__cell {

View File

@@ -1,26 +0,0 @@
import { fireEvent } from "../../common/dom/fire_event";
import { DataTableColumnContainer } from "./ha-data-table";
export interface DataTableSettingsDialogParams {
columns: DataTableColumnContainer;
onUpdate: (
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) => void;
hiddenColumns?: string[];
columnOrder?: string[];
}
export const loadDataTableSettingsDialog = () =>
import("./dialog-data-table-settings");
export const showDataTableSettingsDialog = (
element: HTMLElement,
dialogParams: DataTableSettingsDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-data-table-settings",
dialogImport: loadDataTableSettingsDialog,
dialogParams,
});
};

View File

@@ -1,6 +1,5 @@
import { expose } from "comlink"; import { expose } from "comlink";
import { stringCompare } from "../../common/string/compare"; import { stringCompare } from "../../common/string/compare";
import { stripDiacritics } from "../../common/string/strip-diacritics";
import type { import type {
ClonedDataTableColumnData, ClonedDataTableColumnData,
DataTableRowData, DataTableRowData,
@@ -13,18 +12,20 @@ const filterData = (
columns: SortableColumnContainer, columns: SortableColumnContainer,
filter: string filter: string
) => { ) => {
filter = stripDiacritics(filter.toLowerCase()); filter = filter.toUpperCase();
return data.filter((row) => return data.filter((row) =>
Object.entries(columns).some((columnEntry) => { Object.entries(columns).some((columnEntry) => {
const [key, column] = columnEntry; const [key, column] = columnEntry;
if (column.filterable) { if (column.filterable) {
const value = String( if (
String(
column.filterKey column.filterKey
? row[column.valueColumn || key][column.filterKey] ? row[column.valueColumn || key][column.filterKey]
: row[column.valueColumn || key] : row[column.valueColumn || key]
); )
.toUpperCase()
if (stripDiacritics(value).toLowerCase().includes(filter)) { .includes(filter)
) {
return true; return true;
} }
} }

View File

@@ -90,8 +90,7 @@ class HaAnsiToHtml extends LitElement {
private _parseTextToColoredPre(text) { private _parseTextToColoredPre(text) {
const pre = document.createElement("pre"); const pre = document.createElement("pre");
// eslint-disable-next-line no-control-regex const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
const re = /\x1b(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1b\\))/g;
let i = 0; let i = 0;
const state: State = { const state: State = {

View File

@@ -65,8 +65,6 @@ interface ExtHassService extends Omit<HassService, "fields"> {
Omit<HassService["fields"][string], "selector"> & { Omit<HassService["fields"][string], "selector"> & {
key: string; key: string;
selector?: Selector; selector?: Selector;
fields?: Record<string, Omit<HassService["fields"][string], "selector">>;
collapsed?: boolean;
} }
>; >;
hasSelector: string[]; hasSelector: string[];
@@ -249,7 +247,20 @@ export class HaServiceControl extends LitElement {
} }
); );
private _getTargetedEntities = memoizeOne((target, value) => { private _filterFields = memoizeOne(
(serviceData: ExtHassService | undefined, value: this["value"]) =>
serviceData?.fields?.filter(
(field) =>
!field.filter ||
this._filterField(serviceData.target, field.filter, value)
)
);
private _filterField(
target: ExtHassService["target"],
filter: ExtHassService["fields"][number]["filter"],
value: this["value"]
) {
const targetSelector = target ? { target } : { target: {} }; const targetSelector = target ? { target } : { target: {} };
const targetEntities = const targetEntities =
ensureArray( ensureArray(
@@ -319,13 +330,6 @@ export class HaServiceControl extends LitElement {
); );
}); });
} }
return targetEntities;
});
private _filterField(
filter: ExtHassService["fields"][number]["filter"],
targetEntities: string[]
) {
if (!targetEntities.length) { if (!targetEntities.length) {
return false; return false;
} }
@@ -387,10 +391,7 @@ export class HaServiceControl extends LitElement {
serviceData?.fields.some((field) => showOptionalToggle(field)) serviceData?.fields.some((field) => showOptionalToggle(field))
); );
const targetEntities = this._getTargetedEntities( const filteredFields = this._filterFields(serviceData, this._value);
serviceData?.target,
this._value
);
const domain = this._value?.service const domain = this._value?.service
? computeDomain(this._value.service) ? computeDomain(this._value.service)
@@ -484,54 +485,12 @@ export class HaServiceControl extends LitElement {
.defaultValue=${this._value?.data} .defaultValue=${this._value?.data}
@value-changed=${this._dataChanged} @value-changed=${this._dataChanged}
></ha-yaml-editor>` ></ha-yaml-editor>`
: serviceData?.fields.map((dataField) => : filteredFields?.map((dataField) => {
dataField.fields
? html`<ha-expansion-panel
leftChevron
.expanded=${!dataField.collapsed}
.header=${this.hass.localize(
`component.${domain}.services.${serviceName}.sections.${dataField.key}.name`
) ||
dataField.name ||
dataField.key}
>
${Object.entries(dataField.fields).map(([key, field]) =>
this._renderField(
{ key, ...field },
hasOptional,
domain,
serviceName,
targetEntities
)
)}
</ha-expansion-panel>`
: this._renderField(
dataField,
hasOptional,
domain,
serviceName,
targetEntities
)
)} `;
}
private _renderField = (
dataField: ExtHassService["fields"][number],
hasOptional: boolean,
domain: string | undefined,
serviceName: string | undefined,
targetEntities: string[]
) => {
if (
dataField.filter &&
!this._filterField(dataField.filter, targetEntities)
) {
return nothing;
}
const selector = dataField?.selector ?? { text: undefined }; const selector = dataField?.selector ?? { text: undefined };
const type = Object.keys(selector)[0]; const type = Object.keys(selector)[0];
const enhancedSelector = ["action", "condition", "trigger"].includes(type) const enhancedSelector = ["action", "condition", "trigger"].includes(
type
)
? { ? {
[type]: { [type]: {
...selector[type], ...selector[type],
@@ -545,7 +504,8 @@ export class HaServiceControl extends LitElement {
return dataField.selector && return dataField.selector &&
(!dataField.advanced || (!dataField.advanced ||
this.showAdvanced || this.showAdvanced ||
(this._value?.data && this._value.data[dataField.key] !== undefined)) (this._value?.data &&
this._value.data[dataField.key] !== undefined))
? html`<ha-settings-row .narrow=${this.narrow}> ? html`<ha-settings-row .narrow=${this.narrow}>
${!showOptional ${!showOptional
? hasOptional ? hasOptional
@@ -591,7 +551,8 @@ export class HaServiceControl extends LitElement {
></ha-selector> ></ha-selector>
</ha-settings-row>` </ha-settings-row>`
: ""; : "";
}; })} `;
}
private _localizeValueCallback = (key: string) => { private _localizeValueCallback = (key: string) => {
if (!this._value?.service) { if (!this._value?.service) {
@@ -878,11 +839,6 @@ export class HaServiceControl extends LitElement {
.description p { .description p {
direction: ltr; direction: ltr;
} }
ha-expansion-panel {
--ha-card-border-radius: 0;
--expansion-panel-summary-padding: 0 16px;
--expansion-panel-content-padding: 0;
}
`; `;
} }
} }

View File

@@ -352,22 +352,6 @@ export const saveAutomationConfig = (
config: AutomationConfig config: AutomationConfig
) => hass.callApi<void>("POST", `config/automation/config/${id}`, config); ) => hass.callApi<void>("POST", `config/automation/config/${id}`, config);
export const normalizeAutomationConfig = <
T extends Partial<AutomationConfig> | AutomationConfig,
>(
config: T
): T => {
// Normalize data: ensure trigger, action and condition are lists
// Happens when people copy paste their automations into the config
for (const key of ["trigger", "condition", "action"]) {
const value = config[key];
if (value && !Array.isArray(value)) {
config[key] = [value];
}
}
return config;
};
export const showAutomationEditor = (data?: Partial<AutomationConfig>) => { export const showAutomationEditor = (data?: Partial<AutomationConfig>) => {
initialAutomationEditorData = data; initialAutomationEditorData = data;
navigate("/config/automation/edit/new"); navigate("/config/automation/edit/new");

View File

@@ -1,6 +1,4 @@
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { ManualAutomationConfig } from "./automation";
import { ManualScriptConfig } from "./script";
import { Selector } from "./selector"; import { Selector } from "./selector";
export type BlueprintDomain = "automation" | "script"; export type BlueprintDomain = "automation" | "script";
@@ -44,11 +42,6 @@ export interface BlueprintImportResult {
validation_errors: string[] | null; validation_errors: string[] | null;
} }
export interface BlueprintSubstituteResults {
automation: { substituted_config: ManualAutomationConfig };
script: { substituted_config: ManualScriptConfig };
}
export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) => export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) =>
hass.callWS<Blueprints>({ type: "blueprint/list", domain }); hass.callWS<Blueprints>({ type: "blueprint/list", domain });
@@ -98,18 +91,3 @@ export const getBlueprintSourceType = (
} }
return "community"; return "community";
}; };
export const substituteBlueprint = <
T extends BlueprintDomain = BlueprintDomain,
>(
hass: HomeAssistant,
domain: T,
path: string,
input: Record<string, any>
) =>
hass.callWS<BlueprintSubstituteResults[T]>({
type: "blueprint/substitute",
domain,
path,
input,
});

View File

@@ -6,7 +6,6 @@ import {
mdiArrowDown, mdiArrowDown,
mdiArrowUp, mdiArrowUp,
mdiClose, mdiClose,
mdiCog,
mdiFilterVariant, mdiFilterVariant,
mdiFilterVariantRemove, mdiFilterVariantRemove,
mdiFormatListChecks, mdiFormatListChecks,
@@ -43,7 +42,6 @@ import "../components/search-input-outlined";
import type { HomeAssistant, Route } from "../types"; import type { HomeAssistant, Route } from "../types";
import "./hass-tabs-subpage"; import "./hass-tabs-subpage";
import type { PageNavigation } from "./hass-tabs-subpage"; import type { PageNavigation } from "./hass-tabs-subpage";
import { showDataTableSettingsDialog } from "../components/data-table/show-dialog-data-table-settings";
@customElement("hass-tabs-subpage-data-table") @customElement("hass-tabs-subpage-data-table")
export class HaTabsSubpageDataTable extends LitElement { export class HaTabsSubpageDataTable extends LitElement {
@@ -173,10 +171,6 @@ export class HaTabsSubpageDataTable extends LitElement {
@property({ attribute: false }) public groupOrder?: string[]; @property({ attribute: false }) public groupOrder?: string[];
@property({ attribute: false }) public columnOrder?: string[];
@property({ attribute: false }) public hiddenColumns?: string[];
@state() private _sortColumn?: string; @state() private _sortColumn?: string;
@state() private _sortDirection: SortingDirection = null; @state() private _sortDirection: SortingDirection = null;
@@ -296,14 +290,6 @@ export class HaTabsSubpageDataTable extends LitElement {
` `
: nothing; : nothing;
const settingsButton = html`<ha-assist-chip
class="has-dropdown select-mode-chip"
@click=${this._openSettings}
.title=${localize("ui.components.subpage-data-table.settings")}
>
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
</ha-assist-chip>`;
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
.hass=${this.hass} .hass=${this.hass}
@@ -430,7 +416,6 @@ export class HaTabsSubpageDataTable extends LitElement {
: ""} : ""}
<ha-data-table <ha-data-table
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow}
.columns=${this.columns} .columns=${this.columns}
.data=${this.data} .data=${this.data}
.noDataText=${this.noDataText} .noDataText=${this.noDataText}
@@ -445,8 +430,6 @@ export class HaTabsSubpageDataTable extends LitElement {
.groupColumn=${this._groupColumn} .groupColumn=${this._groupColumn}
.groupOrder=${this.groupOrder} .groupOrder=${this.groupOrder}
.initialCollapsedGroups=${this.initialCollapsedGroups} .initialCollapsedGroups=${this.initialCollapsedGroups}
.columnOrder=${this.columnOrder}
.hiddenColumns=${this.hiddenColumns}
> >
${!this.narrow ${!this.narrow
? html` ? html`
@@ -455,7 +438,7 @@ export class HaTabsSubpageDataTable extends LitElement {
<div class="table-header"> <div class="table-header">
${this.hasFilters && !this.showFilters ${this.hasFilters && !this.showFilters
? html`${filterButton}` ? html`${filterButton}`
: nothing}${selectModeBtn}${searchBar}${groupByMenu}${sortByMenu}${settingsButton} : nothing}${selectModeBtn}${searchBar}${groupByMenu}${sortByMenu}
</div> </div>
</slot> </slot>
</div> </div>
@@ -465,7 +448,7 @@ export class HaTabsSubpageDataTable extends LitElement {
${this.hasFilters && !this.showFilters ${this.hasFilters && !this.showFilters
? html`${filterButton}` ? html`${filterButton}`
: nothing} : nothing}
${selectModeBtn}${groupByMenu}${sortByMenu}${settingsButton} ${selectModeBtn}${groupByMenu}${sortByMenu}
</div>`} </div>`}
</ha-data-table>`} </ha-data-table>`}
<div slot="fab"><slot name="fab"></slot></div> <div slot="fab"><slot name="fab"></slot></div>
@@ -625,22 +608,6 @@ export class HaTabsSubpageDataTable extends LitElement {
fireEvent(this, "grouping-changed", { value: columnId }); fireEvent(this, "grouping-changed", { value: columnId });
} }
private _openSettings() {
showDataTableSettingsDialog(this, {
columns: this.columns,
hiddenColumns: this.hiddenColumns,
columnOrder: this.columnOrder,
onUpdate: (
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) => {
this.columnOrder = columnOrder;
this.hiddenColumns = hiddenColumns;
fireEvent(this, "columns-changed", { columnOrder, hiddenColumns });
},
});
}
private _collapseAllGroups() { private _collapseAllGroups() {
this._dataTable.collapseAllGroups(); this._dataTable.collapseAllGroups();
} }
@@ -907,10 +874,6 @@ declare global {
interface HASSDomEvents { interface HASSDomEvents {
"search-changed": { value: string }; "search-changed": { value: string };
"grouping-changed": { value: string }; "grouping-changed": { value: string };
"columns-changed": {
columnOrder: string[] | undefined;
hiddenColumns: string[] | undefined;
};
"clear-filter": undefined; "clear-filter": undefined;
} }
} }

View File

@@ -72,11 +72,11 @@ class DialogCommunity extends LitElement {
<a <a
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
href="https://x.com/home_assistant" href="https://twitter.com/home_assistant"
> >
<ha-list-item hasMeta graphic="icon"> <ha-list-item hasMeta graphic="icon">
<img class="x" src="/static/images/logo_x.svg" slot="graphic" /> <img src="/static/images/logo_twitter.png" slot="graphic" />
${this.localize("ui.panel.page-onboarding.welcome.x")} ${this.localize("ui.panel.page-onboarding.welcome.twitter")}
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon> <ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item> </ha-list-item>
</a> </a>
@@ -96,12 +96,6 @@ class DialogCommunity extends LitElement {
a { a {
text-decoration: none; text-decoration: none;
} }
@media (prefers-color-scheme: light) {
img.x {
filter: invert(1) hue-rotate(180deg);
}
}
`; `;
} }

View File

@@ -11,7 +11,6 @@ import {
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-help-tooltip"; import "../../../components/ha-help-tooltip";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import "../../../components/ha-icon-overflow-menu";
import { import {
ApplicationCredential, ApplicationCredential,
deleteApplicationCredential, deleteApplicationCredential,
@@ -71,26 +70,6 @@ export class HaConfigApplicationCredentials extends LitElement {
width: "30%", width: "30%",
direction: "asc", direction: "asc",
}, },
actions: {
title: "",
width: "64px",
type: "overflow-menu",
template: (credential) => html`
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
path: mdiDelete,
warning: true,
label: this.hass.localize("ui.common.delete"),
action: () => this._removeCredential(credential),
},
]}
>
</ha-icon-overflow-menu>
`,
},
}; };
return columns; return columns;
@@ -174,24 +153,6 @@ export class HaConfigApplicationCredentials extends LitElement {
this._selected = ev.detail.value; this._selected = ev.detail.value;
} }
private _removeCredential = async (credential) => {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
`ui.panel.config.application_credentials.picker.remove.confirm_title`
),
text: this.hass.localize(
"ui.panel.config.application_credentials.picker.remove_selected.confirm_text"
),
confirmText: this.hass.localize("ui.common.delete"),
dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
});
if (!confirm) {
return;
}
await deleteApplicationCredential(this.hass, credential.id);
};
private _removeSelected() { private _removeSelected() {
showConfirmationDialog(this, { showConfirmationDialog(this, {
title: this.hass.localize( title: this.hass.localize(
@@ -201,9 +162,8 @@ export class HaConfigApplicationCredentials extends LitElement {
text: this.hass.localize( text: this.hass.localize(
"ui.panel.config.application_credentials.picker.remove_selected.confirm_text" "ui.panel.config.application_credentials.picker.remove_selected.confirm_text"
), ),
confirmText: this.hass.localize("ui.common.delete"), confirmText: this.hass.localize("ui.common.remove"),
dismissText: this.hass.localize("ui.common.cancel"), dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
confirm: async () => { confirm: async () => {
try { try {
await Promise.all( await Promise.all(

View File

@@ -54,7 +54,6 @@ import {
AddAutomationElementDialogParams, AddAutomationElementDialogParams,
PASTE_VALUE, PASTE_VALUE,
} from "./show-add-automation-element-dialog"; } from "./show-add-automation-element-dialog";
import { stripDiacritics } from "../../../common/string/strip-diacritics";
const TYPES = { const TYPES = {
trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS }, trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS },
@@ -209,10 +208,9 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: Math.min(filter.length, 2), minMatchCharLength: Math.min(filter.length, 2),
threshold: 0.2, threshold: 0.2,
getFn: (obj, path) => stripDiacritics(Fuse.config.getFn(obj, path)),
}; };
const fuse = new Fuse(items, options); const fuse = new Fuse(items, options);
return fuse.search(stripDiacritics(filter)).map((result) => result.item); return fuse.search(filter).map((result) => result.item);
} }
); );

View File

@@ -3,10 +3,10 @@ import { HassEntity } from "home-assistant-js-websocket";
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-markdown";
import { BlueprintAutomationConfig } from "../../../data/automation"; import { BlueprintAutomationConfig } from "../../../data/automation";
import { fetchBlueprints } from "../../../data/blueprint"; import { fetchBlueprints } from "../../../data/blueprint";
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor"; import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
import "../../../components/ha-markdown";
@customElement("blueprint-automation-editor") @customElement("blueprint-automation-editor")
export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor { export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {

View File

@@ -6,7 +6,6 @@ import {
mdiDebugStepOver, mdiDebugStepOver,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiFileEdit,
mdiInformationOutline, mdiInformationOutline,
mdiPlay, mdiPlay,
mdiPlayCircleOutline, mdiPlayCircleOutline,
@@ -41,12 +40,10 @@ import "../../../components/ha-yaml-editor";
import { import {
AutomationConfig, AutomationConfig,
AutomationEntity, AutomationEntity,
BlueprintAutomationConfig,
deleteAutomation, deleteAutomation,
fetchAutomationFileConfig, fetchAutomationFileConfig,
getAutomationEditorInitData, getAutomationEditorInitData,
getAutomationStateConfig, getAutomationStateConfig,
normalizeAutomationConfig,
saveAutomationConfig, saveAutomationConfig,
showAutomationEditor, showAutomationEditor,
triggerAutomationActions, triggerAutomationActions,
@@ -68,7 +65,6 @@ import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-a
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename"; import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
import "./blueprint-automation-editor"; import "./blueprint-automation-editor";
import "./manual-automation-editor"; import "./manual-automation-editor";
import { substituteBlueprint } from "../../../data/blueprint";
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@@ -239,24 +235,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
></ha-svg-icon> ></ha-svg-icon>
</ha-list-item> </ha-list-item>
${useBlueprint
? html`
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly || this._mode === "yaml"}
>
${this.hass.localize(
"ui.panel.config.automation.editor.take_control"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<li divider role="separator"></li> <li divider role="separator"></li>
<ha-list-item graphic="icon" @click=${this._switchUiMode}> <ha-list-item graphic="icon" @click=${this._switchUiMode}>
@@ -454,7 +432,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
} }
this._config = { this._config = {
...baseConfig, ...baseConfig,
...(initData ? normalizeAutomationConfig(initData) : initData), ...initData,
} as AutomationConfig; } as AutomationConfig;
this._entityId = undefined; this._entityId = undefined;
this._readOnly = false; this._readOnly = false;
@@ -463,7 +441,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
if (changedProps.has("entityId") && this.entityId) { if (changedProps.has("entityId") && this.entityId) {
getAutomationStateConfig(this.hass, this.entityId).then((c) => { getAutomationStateConfig(this.hass, this.entityId).then((c) => {
this._config = normalizeAutomationConfig(c.config); this._config = this._normalizeConfig(c.config);
this._checkValidation(); this._checkValidation();
}); });
this._entityId = this.entityId; this._entityId = this.entityId;
@@ -519,6 +497,18 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
); );
} }
private _normalizeConfig(config: AutomationConfig): AutomationConfig {
// Normalize data: ensure trigger, action and condition are lists
// Happens when people copy paste their automations into the config
for (const key of ["trigger", "condition", "action"]) {
const value = config[key];
if (value && !Array.isArray(value)) {
config[key] = [value];
}
}
return config;
}
private async _loadConfig() { private async _loadConfig() {
try { try {
const config = await fetchAutomationFileConfig( const config = await fetchAutomationFileConfig(
@@ -527,7 +517,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
); );
this._dirty = false; this._dirty = false;
this._readOnly = false; this._readOnly = false;
this._config = normalizeAutomationConfig(config); this._config = this._normalizeConfig(config);
this._checkValidation(); this._checkValidation();
} catch (err: any) { } catch (err: any) {
const entityRegistry = await fetchEntityRegistry(this.hass.connection); const entityRegistry = await fetchEntityRegistry(this.hass.connection);
@@ -648,45 +638,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
} }
}; };
private async _takeControl() {
const config = this._config as BlueprintAutomationConfig;
const confirmation = await showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.automation.editor.take_control_confirmation.title"
),
text: this.hass!.localize(
"ui.panel.config.automation.editor.take_control_confirmation.text"
),
confirmText: this.hass!.localize(
"ui.panel.config.automation.editor.take_control_confirmation.action"
),
});
if (!confirmation) return;
try {
const result = await substituteBlueprint(
this.hass,
"automation",
config.use_blueprint.path,
config.use_blueprint.input || {}
);
const newConfig = {
...normalizeAutomationConfig(result.substituted_config),
alias: config.alias,
description: config.description,
};
this._config = newConfig;
this._dirty = true;
this._errors = undefined;
} catch (err: any) {
this._errors = err.message;
}
}
private async _duplicate() { private async _duplicate() {
const result = this._readOnly const result = this._readOnly
? await showConfirmationDialog(this, { ? await showConfirmationDialog(this, {

View File

@@ -192,20 +192,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "automation-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "automation-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@query("#overflow-menu") private _overflowMenu!: HaMenu; @query("#overflow-menu") private _overflowMenu!: HaMenu;
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
@@ -267,8 +253,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
title: "", title: "",
label: localize("ui.panel.config.automation.picker.headers.state"), label: localize("ui.panel.config.automation.picker.headers.state"),
type: "icon", type: "icon",
moveable: false,
showNarrow: true,
template: (automation) => template: (automation) =>
html`<ha-state-icon html`<ha-state-icon
.hass=${this.hass} .hass=${this.hass}
@@ -288,13 +272,30 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
extraTemplate: (automation) => template: (automation) => {
automation.labels.length const date = new Date(automation.attributes.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
<div style="font-size: 14px;">${automation.name}</div>
${narrow
? html`<div class="secondary">
${this.hass.localize("ui.card.automation.last_triggered")}:
${automation.attributes.last_triggered
? dayDifference > 3
? formatShortDateTime(date, locale, this.hass.config)
: relativeTime(date, locale)
: localize("ui.components.relative_time.never")}
</div>`
: nothing}
${automation.labels.length
? html`<ha-data-table-labels ? html`<ha-data-table-labels
@label-clicked=${this._labelClicked} @label-clicked=${this._labelClicked}
.labels=${automation.labels} .labels=${automation.labels}
></ha-data-table-labels>` ></ha-data-table-labels>`
: nothing, : nothing}
`;
},
}, },
area: { area: {
title: localize("ui.panel.config.automation.picker.headers.area"), title: localize("ui.panel.config.automation.picker.headers.area"),
@@ -321,6 +322,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
width: "130px", width: "130px",
title: localize("ui.card.automation.last_triggered"), title: localize("ui.card.automation.last_triggered"),
hidden: narrow,
template: (automation) => { template: (automation) => {
if (!automation.last_triggered) { if (!automation.last_triggered) {
return this.hass.localize("ui.components.relative_time.never"); return this.hass.localize("ui.components.relative_time.never");
@@ -339,9 +341,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
width: "82px", width: "82px",
sortable: true, sortable: true,
groupable: true, groupable: true,
hidden: narrow,
title: "", title: "",
type: "overflow", type: "overflow",
hidden: narrow,
label: this.hass.localize("ui.panel.config.automation.picker.state"), label: this.hass.localize("ui.panel.config.automation.picker.state"),
template: (automation) => html` template: (automation) => html`
<ha-entity-toggle <ha-entity-toggle
@@ -354,9 +356,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
title: "", title: "",
width: "64px", width: "64px",
type: "icon-button", type: "icon-button",
showNarrow: true,
moveable: false,
hideable: false,
template: (automation) => html` template: (automation) => html`
<ha-icon-button <ha-icon-button
.automation=${automation} .automation=${automation}
@@ -546,9 +545,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@@ -1419,11 +1415,6 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -60,19 +60,14 @@ class HaConfigBackup extends LitElement {
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: narrow template: (backup) =>
? undefined
: (backup) =>
html`${backup.name} html`${backup.name}
<div class="secondary">${backup.path}</div>`, <div class="secondary">${backup.path}</div>`,
}, },
path: {
title: localize("ui.panel.config.backup.path"),
hidden: !narrow,
},
size: { size: {
title: localize("ui.panel.config.backup.size"), title: localize("ui.panel.config.backup.size"),
width: "15%", width: "15%",
hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB", template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
@@ -81,6 +76,7 @@ class HaConfigBackup extends LitElement {
title: localize("ui.panel.config.backup.created"), title: localize("ui.panel.config.backup.created"),
width: "15%", width: "15%",
direction: "desc", direction: "desc",
hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (backup) => template: (backup) =>
@@ -91,9 +87,6 @@ class HaConfigBackup extends LitElement {
title: "", title: "",
width: "15%", width: "15%",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
hideable: false,
moveable: false,
template: (backup) => template: (backup) =>
html`<ha-icon-overflow-menu html`<ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}

View File

@@ -107,20 +107,6 @@ class HaBlueprintOverview extends LitElement {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "blueprint-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "blueprint-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@storage({ @storage({
storage: "sessionStorage", storage: "sessionStorage",
key: "blueprint-table-search", key: "blueprint-table-search",
@@ -168,6 +154,8 @@ class HaBlueprintOverview extends LitElement {
private _columns = memoizeOne( private _columns = memoizeOne(
( (
narrow,
_language,
localize: LocalizeFunc localize: LocalizeFunc
): DataTableColumnContainer<BlueprintMetaDataPath> => ({ ): DataTableColumnContainer<BlueprintMetaDataPath> => ({
name: { name: {
@@ -177,12 +165,19 @@ class HaBlueprintOverview extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: narrow
? (blueprint) => html`
${blueprint.name}<br />
<div class="secondary">${blueprint.path}</div>
`
: undefined,
}, },
translated_type: { translated_type: {
title: localize("ui.panel.config.blueprint.overview.headers.type"), title: localize("ui.panel.config.blueprint.overview.headers.type"),
sortable: true, sortable: true,
filterable: true, filterable: true,
groupable: true, groupable: true,
hidden: narrow,
direction: "asc", direction: "asc",
width: "10%", width: "10%",
}, },
@@ -190,6 +185,7 @@ class HaBlueprintOverview extends LitElement {
title: localize("ui.panel.config.blueprint.overview.headers.file_name"), title: localize("ui.panel.config.blueprint.overview.headers.file_name"),
sortable: true, sortable: true,
filterable: true, filterable: true,
hidden: narrow,
direction: "asc", direction: "asc",
width: "25%", width: "25%",
}, },
@@ -201,9 +197,6 @@ class HaBlueprintOverview extends LitElement {
title: "", title: "",
width: this.narrow ? undefined : "10%", width: this.narrow ? undefined : "10%",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
moveable: false,
hideable: false,
template: (blueprint) => template: (blueprint) =>
blueprint.error blueprint.error
? html`<ha-svg-icon ? html`<ha-svg-icon
@@ -287,7 +280,11 @@ class HaBlueprintOverview extends LitElement {
back-path="/config" back-path="/config"
.route=${this.route} .route=${this.route}
.tabs=${configSections.automations} .tabs=${configSections.automations}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(
this.narrow,
this.hass.language,
this.hass.localize
)}
.data=${this._processedBlueprints(this.blueprints, this.hass.localize)} .data=${this._processedBlueprints(this.blueprints, this.hass.localize)}
id="fullpath" id="fullpath"
.noDataText=${this.hass.localize( .noDataText=${this.hass.localize(
@@ -316,9 +313,6 @@ class HaBlueprintOverview extends LitElement {
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@@ -562,11 +556,6 @@ class HaBlueprintOverview extends LitElement {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return haStyle; return haStyle;
} }

View File

@@ -61,32 +61,32 @@ const randomTip = (hass: HomeAssistant, narrow: boolean) => {
href="https://community.home-assistant.io" href="https://community.home-assistant.io"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_forums")}</a >Forums</a
>`, >`,
twitter: html`<a twitter: html`<a
href=${documentationUrl(hass, `/twitter`)} href=${documentationUrl(hass, `/twitter`)}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_x")}</a >Twitter</a
>`, >`,
discord: html`<a discord: html`<a
href=${documentationUrl(hass, `/join-chat`)} href=${documentationUrl(hass, `/join-chat`)}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_chat")}</a >Chat</a
>`, >`,
blog: html`<a blog: html`<a
href=${documentationUrl(hass, `/blog`)} href=${documentationUrl(hass, `/blog`)}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_blog")}</a >Blog</a
>`, >`,
newsletter: html`<span class="keep-together" newsletter: html`<span class="keep-together"
><a ><a
href="https://newsletter.openhomefoundation.org/" href="https://newsletter.openhomefoundation.org/"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_newsletter")}</a >Newsletter</a
> >
</span>`, </span>`,
}), }),

View File

@@ -154,20 +154,6 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
@storage({ key: "devices-table-collapsed", state: false, subscribe: false }) @storage({ key: "devices-table-collapsed", state: false, subscribe: false })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "devices-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "devices-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width, callback: (entries) => entries[0]?.contentRect.width,
}); });
@@ -448,13 +434,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
typeof this._devicesAndFilterDomains typeof this._devicesAndFilterDomains
>["devicesOutput"][number]; >["devicesOutput"][number];
return { const columns: DataTableColumnContainer<DeviceItem> = {
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.devices.data_table.icon"),
type: "icon", type: "icon",
moveable: false,
showNarrow: true,
template: (device) => template: (device) =>
device.domains.length device.domains.length
? html`<img ? html`<img
@@ -469,14 +452,19 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
/>` />`
: "", : "",
}, },
name: { };
if (narrow) {
columns.name = {
title: localize("ui.panel.config.devices.data_table.device"), title: localize("ui.panel.config.devices.data_table.device"),
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
extraTemplate: (device) => html` template: (device) => html`
<div style="font-size: 14px;">${device.name}</div>
<div class="secondary">${device.area} | ${device.integration}</div>
${device.label_entries.length ${device.label_entries.length
? html` ? html`
<ha-data-table-labels <ha-data-table-labels
@@ -485,37 +473,61 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
` `
: nothing} : nothing}
`, `,
}, };
manufacturer: { } else {
columns.name = {
title: localize("ui.panel.config.devices.data_table.device"),
main: true,
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (device) => html`
<div style="font-size: 14px;">${device.name}</div>
${device.label_entries.length
? html`
<ha-data-table-labels
.labels=${device.label_entries}
></ha-data-table-labels>
`
: nothing}
`,
};
}
columns.manufacturer = {
title: localize("ui.panel.config.devices.data_table.manufacturer"), title: localize("ui.panel.config.devices.data_table.manufacturer"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
}, };
model: { columns.model = {
title: localize("ui.panel.config.devices.data_table.model"), title: localize("ui.panel.config.devices.data_table.model"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
width: "15%", width: "15%",
}, };
area: { columns.area = {
title: localize("ui.panel.config.devices.data_table.area"), title: localize("ui.panel.config.devices.data_table.area"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
}, };
integration: { columns.integration = {
title: localize("ui.panel.config.devices.data_table.integration"), title: localize("ui.panel.config.devices.data_table.integration"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
}, };
battery_entity: { columns.battery_entity = {
title: localize("ui.panel.config.devices.data_table.battery"), title: localize("ui.panel.config.devices.data_table.battery"),
showNarrow: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
type: "numeric", type: "numeric",
@@ -528,9 +540,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
batteryEntityPair && batteryEntityPair[0] batteryEntityPair && batteryEntityPair[0]
? this.hass.states[batteryEntityPair[0]] ? this.hass.states[batteryEntityPair[0]]
: undefined; : undefined;
const batteryDomain = battery const batteryDomain = battery ? computeStateDomain(battery) : undefined;
? computeStateDomain(battery)
: undefined;
const batteryCharging = const batteryCharging =
batteryEntityPair && batteryEntityPair[1] batteryEntityPair && batteryEntityPair[1]
? this.hass.states[batteryEntityPair[1]] ? this.hass.states[batteryEntityPair[1]]
@@ -550,8 +560,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
` `
: html``; : html``;
}, },
}, };
disabled_by: { columns.disabled_by = {
title: "", title: "",
label: localize("ui.panel.config.devices.data_table.disabled_by"), label: localize("ui.panel.config.devices.data_table.disabled_by"),
hidden: true, hidden: true,
@@ -559,15 +569,16 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
device.disabled_by device.disabled_by
? this.hass.localize("ui.panel.config.devices.disabled") ? this.hass.localize("ui.panel.config.devices.disabled")
: "", : "",
}, };
labels: { columns.labels = {
title: "", title: "",
hidden: true, hidden: true,
filterable: true, filterable: true,
template: (device) => template: (device) =>
device.label_entries.map((lbl) => lbl.name).join(" "), device.label_entries.map((lbl) => lbl.name).join(" "),
}, };
} as DataTableColumnContainer<DeviceItem>;
return columns;
}); });
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] { protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
@@ -693,9 +704,6 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@clear-filter=${this._clearFilter} @clear-filter=${this._clearFilter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@@ -1035,11 +1043,6 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
css` css`

View File

@@ -186,20 +186,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "entities-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "entities-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
@@ -265,13 +251,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
]); ]);
private _columns = memoize( private _columns = memoize(
(localize: LocalizeFunc): DataTableColumnContainer<EntityRow> => ({ (
localize: LocalizeFunc,
narrow,
_language
): DataTableColumnContainer<EntityRow> => ({
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.entities.picker.headers.state_icon"), label: localize("ui.panel.config.entities.picker.headers.state_icon"),
type: "icon", type: "icon",
showNarrow: true,
moveable: false,
template: (entry) => template: (entry) =>
entry.icon entry.icon
? html`<ha-icon .icon=${entry.icon}></ha-icon>` ? html`<ha-icon .icon=${entry.icon}></ha-icon>`
@@ -295,23 +283,32 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
extraTemplate: (entry) => template: (entry) => html`
entry.label_entries.length <div style="font-size: 14px;">${entry.name}</div>
${narrow
? html`<div class="secondary">
${entry.entity_id} | ${entry.localized_platform}
</div>`
: nothing}
${entry.label_entries.length
? html` ? html`
<ha-data-table-labels <ha-data-table-labels
.labels=${entry.label_entries} .labels=${entry.label_entries}
></ha-data-table-labels> ></ha-data-table-labels>
` `
: nothing, : nothing}
`,
}, },
entity_id: { entity_id: {
title: localize("ui.panel.config.entities.picker.headers.entity_id"), title: localize("ui.panel.config.entities.picker.headers.entity_id"),
hidden: narrow,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "25%", width: "25%",
}, },
localized_platform: { localized_platform: {
title: localize("ui.panel.config.entities.picker.headers.integration"), title: localize("ui.panel.config.entities.picker.headers.integration"),
hidden: narrow,
sortable: true, sortable: true,
groupable: true, groupable: true,
filterable: true, filterable: true,
@@ -327,6 +324,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
area: { area: {
title: localize("ui.panel.config.entities.picker.headers.area"), title: localize("ui.panel.config.entities.picker.headers.area"),
sortable: true, sortable: true,
hidden: narrow,
filterable: true, filterable: true,
groupable: true, groupable: true,
width: "15%", width: "15%",
@@ -345,7 +343,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
status: { status: {
title: localize("ui.panel.config.entities.picker.headers.status"), title: localize("ui.panel.config.entities.picker.headers.status"),
type: "icon", type: "icon",
showNarrow: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "68px", width: "68px",
@@ -691,7 +688,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
} }
.route=${this.route} .route=${this.route}
.tabs=${configSections.devices} .tabs=${configSections.devices}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(
this.hass.localize,
this.narrow,
this.hass.language
)}
.data=${filteredEntities} .data=${filteredEntities}
.searchLabel=${this.hass.localize( .searchLabel=${this.hass.localize(
"ui.panel.config.entities.picker.search", "ui.panel.config.entities.picker.search",
@@ -713,9 +714,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@@ -1337,11 +1335,6 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -167,20 +167,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
}) })
private _filter = ""; private _filter = "";
@storage({
key: "helpers-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "helpers-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@state() private _stateItems: HassEntity[] = []; @state() private _stateItems: HassEntity[] = [];
@state() private _entityEntries?: Record<string, EntityRegistryEntry>; @state() private _entityEntries?: Record<string, EntityRegistryEntry>;
@@ -257,13 +243,14 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
} }
private _columns = memoizeOne( private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer<HelperItem> => ({ (
narrow: boolean,
localize: LocalizeFunc
): DataTableColumnContainer<HelperItem> => ({
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.helpers.picker.headers.icon"), label: localize("ui.panel.config.helpers.picker.headers.icon"),
type: "icon", type: "icon",
showNarrow: true,
moveable: false,
template: (helper) => template: (helper) =>
helper.entity helper.entity
? html`<ha-state-icon ? html`<ha-state-icon
@@ -282,17 +269,23 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
grows: true, grows: true,
direction: "asc", direction: "asc",
extraTemplate: (helper) => template: (helper) => html`
helper.label_entries.length <div style="font-size: 14px;">${helper.name}</div>
${narrow
? html`<div class="secondary">${helper.entity_id}</div> `
: nothing}
${helper.label_entries.length
? html` ? html`
<ha-data-table-labels <ha-data-table-labels
.labels=${helper.label_entries} .labels=${helper.label_entries}
></ha-data-table-labels> ></ha-data-table-labels>
` `
: nothing, : nothing}
`,
}, },
entity_id: { entity_id: {
title: localize("ui.panel.config.helpers.picker.headers.entity_id"), title: localize("ui.panel.config.helpers.picker.headers.entity_id"),
hidden: this.narrow,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "25%", width: "25%",
@@ -320,9 +313,10 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
}, },
editable: { editable: {
title: "", title: "",
label: localize("ui.panel.config.helpers.picker.headers.editable"), label: this.hass.localize(
"ui.panel.config.helpers.picker.headers.editable"
),
type: "icon", type: "icon",
showNarrow: true,
template: (helper) => html` template: (helper) => html`
${!helper.editable ${!helper.editable
? html` ? html`
@@ -343,12 +337,8 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
}, },
actions: { actions: {
title: "", title: "",
label: "Actions",
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
hideable: false,
moveable: false,
showNarrow: true,
template: (helper) => html` template: (helper) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@@ -566,14 +556,11 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
Array.isArray(val) ? val.length : val Array.isArray(val) ? val.length : val
) )
).length} ).length}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(this.narrow, this.hass.localize)}
.data=${helpers} .data=${helpers}
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@@ -1097,11 +1084,6 @@ ${rejected
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -55,7 +55,6 @@ import {
showYamlIntegrationDialog, showYamlIntegrationDialog,
} from "./show-add-integration-dialog"; } from "./show-add-integration-dialog";
import { getConfigEntries } from "../../../data/config_entries"; import { getConfigEntries } from "../../../data/config_entries";
import { stripDiacritics } from "../../../common/string/strip-diacritics";
export interface IntegrationListItem { export interface IntegrationListItem {
name: string; name: string;
@@ -256,7 +255,6 @@ class AddIntegrationDialog extends LitElement {
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: Math.min(filter.length, 2), minMatchCharLength: Math.min(filter.length, 2),
threshold: 0.2, threshold: 0.2,
getFn: (obj, path) => stripDiacritics(Fuse.config.getFn(obj, path)),
}; };
const helpers = Object.entries(h).map(([domain, integration]) => ({ const helpers = Object.entries(h).map(([domain, integration]) => ({
domain, domain,
@@ -266,16 +264,15 @@ class AddIntegrationDialog extends LitElement {
is_built_in: integration.is_built_in !== false, is_built_in: integration.is_built_in !== false,
cloud: integration.iot_class?.startsWith("cloud_"), cloud: integration.iot_class?.startsWith("cloud_"),
})); }));
const normalizedFilter = stripDiacritics(filter);
return [ return [
...new Fuse(integrations, options) ...new Fuse(integrations, options)
.search(normalizedFilter) .search(filter)
.map((result) => result.item), .map((result) => result.item),
...new Fuse(yamlIntegrations, options) ...new Fuse(yamlIntegrations, options)
.search(normalizedFilter) .search(filter)
.map((result) => result.item), .map((result) => result.item),
...new Fuse(helpers, options) ...new Fuse(helpers, options)
.search(normalizedFilter) .search(filter)
.map((result) => result.item), .map((result) => result.item),
]; ];
} }

View File

@@ -71,7 +71,6 @@ import { showAddIntegrationDialog } from "./show-add-integration-dialog";
import "./ha-disabled-config-entry-card"; import "./ha-disabled-config-entry-card";
import { caseInsensitiveStringCompare } from "../../../common/string/compare"; import { caseInsensitiveStringCompare } from "../../../common/string/compare";
import "../../../components/search-input-outlined"; import "../../../components/search-input-outlined";
import { stripDiacritics } from "../../../common/string/strip-diacritics";
export interface ConfigEntryExtended extends ConfigEntry { export interface ConfigEntryExtended extends ConfigEntry {
localized_domain_name?: string; localized_domain_name?: string;
@@ -209,12 +208,9 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: Math.min(filter.length, 2), minMatchCharLength: Math.min(filter.length, 2),
threshold: 0.2, threshold: 0.2,
getFn: (obj, path) => stripDiacritics(Fuse.config.getFn(obj, path)),
}; };
const fuse = new Fuse(configEntriesInProgress, options); const fuse = new Fuse(configEntriesInProgress, options);
filteredEntries = fuse filteredEntries = fuse.search(filter).map((result) => result.item);
.search(stripDiacritics(filter))
.map((result) => result.item);
} else { } else {
filteredEntries = configEntriesInProgress; filteredEntries = configEntriesInProgress;
} }

View File

@@ -66,26 +66,10 @@ export class HaConfigLabels extends LitElement {
}) })
private _activeSorting?: SortingChangedEvent; private _activeSorting?: SortingChangedEvent;
@storage({
key: "labels-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "labels-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _columns = memoizeOne((localize: LocalizeFunc) => { private _columns = memoizeOne((localize: LocalizeFunc) => {
const columns: DataTableColumnContainer<LabelRegistryEntry> = { const columns: DataTableColumnContainer<LabelRegistryEntry> = {
icon: { icon: {
title: "", title: "",
moveable: false,
showNarrow: true,
label: localize("ui.panel.config.labels.headers.icon"), label: localize("ui.panel.config.labels.headers.icon"),
type: "icon", type: "icon",
template: (label) => template: (label) =>
@@ -93,7 +77,6 @@ export class HaConfigLabels extends LitElement {
}, },
color: { color: {
title: "", title: "",
showNarrow: true,
label: localize("ui.panel.config.labels.headers.color"), label: localize("ui.panel.config.labels.headers.color"),
type: "icon", type: "icon",
template: (label) => template: (label) =>
@@ -122,9 +105,6 @@ export class HaConfigLabels extends LitElement {
}, },
actions: { actions: {
title: "", title: "",
showNarrow: true,
moveable: false,
hideable: false,
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
template: (label) => html` template: (label) => html`
@@ -187,9 +167,6 @@ export class HaConfigLabels extends LitElement {
.noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")} .noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")}
hasFab hasFab
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
.filter=${this._filter} .filter=${this._filter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@@ -320,11 +297,6 @@ export class HaConfigLabels extends LitElement {
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
} }
declare global { declare global {

View File

@@ -2,17 +2,21 @@ import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { import {
mdiCheck, mdiCheck,
mdiCheckCircleOutline, mdiCheckCircleOutline,
mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiOpenInNew, mdiPencil,
mdiPlus, mdiPlus,
mdiStar,
} from "@mdi/js"; } from "@mdi/js";
import { LitElement, PropertyValues, html, nothing } from "lit"; import { LitElement, PropertyValues, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import memoize from "memoize-one"; import memoize from "memoize-one";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { storage } from "../../../../common/decorators/storage";
import { navigate } from "../../../../common/navigate"; import { navigate } from "../../../../common/navigate";
import { stringCompare } from "../../../../common/string/compare"; import { stringCompare } from "../../../../common/string/compare";
import { LocalizeFunc } from "../../../../common/translations/localize";
import { import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
@@ -22,6 +26,9 @@ import "../../../../components/ha-clickable-list-item";
import "../../../../components/ha-fab"; import "../../../../components/ha-fab";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-button";
import "../../../../components/ha-menu";
import type { HaMenu } from "../../../../components/ha-menu";
import "../../../../components/ha-menu-item";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import { LovelacePanelConfig } from "../../../../data/lovelace"; import { LovelacePanelConfig } from "../../../../data/lovelace";
import { import {
@@ -41,13 +48,11 @@ import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-
import "../../../../layouts/hass-loading-screen"; import "../../../../layouts/hass-loading-screen";
import "../../../../layouts/hass-tabs-subpage-data-table"; import "../../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../../types"; import { HomeAssistant, Route } from "../../../../types";
import { LocalizeFunc } from "../../../../common/translations/localize";
import { getLovelaceStrategy } from "../../../lovelace/strategies/get-strategy"; import { getLovelaceStrategy } from "../../../lovelace/strategies/get-strategy";
import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboard"; import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboard";
import { lovelaceTabs } from "../ha-config-lovelace"; import { lovelaceTabs } from "../ha-config-lovelace";
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy"; import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail"; import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
import { storage } from "../../../../common/decorators/storage";
type DataTableItem = Pick< type DataTableItem = Pick<
LovelaceDashboard, LovelaceDashboard,
@@ -85,19 +90,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
}) })
private _activeSorting?: SortingChangedEvent; private _activeSorting?: SortingChangedEvent;
@storage({ @state() private _overflowDashboard?: LovelaceDashboard;
key: "lovelace-dashboards-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({ @query("#overflow-menu") private _overflowMenu!: HaMenu;
key: "lovelace-dashboards-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
public willUpdate() { public willUpdate() {
if (!this.hasUpdated) { if (!this.hasUpdated) {
@@ -115,8 +110,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
const columns: DataTableColumnContainer<DataTableItem> = { const columns: DataTableColumnContainer<DataTableItem> = {
icon: { icon: {
title: "", title: "",
moveable: false,
showNarrow: true,
label: localize( label: localize(
"ui.panel.config.lovelace.dashboards.picker.headers.icon" "ui.panel.config.lovelace.dashboards.picker.headers.icon"
), ),
@@ -144,9 +137,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: narrow template: (dashboard) => {
? undefined const titleTemplate = html`
: (dashboard) => html`
${dashboard.title} ${dashboard.title}
${dashboard.default ${dashboard.default
? html` ? html`
@@ -161,10 +153,24 @@ export class HaConfigLovelaceDashboards extends LitElement {
</simple-tooltip> </simple-tooltip>
` `
: ""} : ""}
`, `;
return narrow
? html`
${titleTemplate}
<div class="secondary">
${this.hass.localize(
`ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}`
)}${dashboard.filename
? html` ${dashboard.filename} `
: ""}
</div>
`
: titleTemplate;
},
}, },
}; };
if (!narrow) {
columns.mode = { columns.mode = {
title: localize( title: localize(
"ui.panel.config.lovelace.dashboards.picker.headers.conf_mode" "ui.panel.config.lovelace.dashboards.picker.headers.conf_mode"
@@ -194,7 +200,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
), ),
sortable: true, sortable: true,
type: "icon", type: "icon",
hidden: narrow,
width: "100px", width: "100px",
template: (dashboard) => template: (dashboard) =>
dashboard.require_admin dashboard.require_admin
@@ -206,49 +211,44 @@ export class HaConfigLovelaceDashboards extends LitElement {
"ui.panel.config.lovelace.dashboards.picker.headers.sidebar" "ui.panel.config.lovelace.dashboards.picker.headers.sidebar"
), ),
type: "icon", type: "icon",
hidden: narrow,
width: "121px", width: "121px",
template: (dashboard) => template: (dashboard) =>
dashboard.show_in_sidebar dashboard.show_in_sidebar
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` ? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
: html``, : html``,
}; };
}
columns.url_path = { columns.actions = {
title: "", title: "",
label: localize( width: "64px",
"ui.panel.config.lovelace.dashboards.picker.headers.url" type: "icon-button",
), template: (dashboard) => html`
filterable: true,
showNarrow: true,
width: "100px",
template: (dashboard) =>
narrow
? html`
<ha-icon-button <ha-icon-button
.path=${mdiOpenInNew} .dashboard=${dashboard}
.urlPath=${dashboard.url_path} .label=${this.hass.localize("ui.common.overflow_menu")}
@click=${this._navigate} .path=${mdiDotsVertical}
.label=${this.hass.localize( @click=${this._showOverflowMenu}
"ui.panel.config.lovelace.dashboards.picker.open"
)}
></ha-icon-button> ></ha-icon-button>
`
: html`
<mwc-button
.urlPath=${dashboard.url_path}
@click=${this._navigate}
>${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.open"
)}</mwc-button
>
`, `,
}; };
return columns; return columns;
} }
); );
private _showOverflowMenu = (ev) => {
if (
this._overflowMenu.open &&
ev.target === this._overflowMenu.anchorElement
) {
this._overflowMenu.close();
return;
}
this._overflowDashboard = ev.target.dashboard;
this._overflowMenu.anchorElement = ev.target;
this._overflowMenu.show();
};
private _getItems = memoize((dashboards: LovelaceDashboard[]) => { private _getItems = memoize((dashboards: LovelaceDashboard[]) => {
const defaultMode = ( const defaultMode = (
this.hass.panels?.lovelace?.config as LovelacePanelConfig this.hass.panels?.lovelace?.config as LovelacePanelConfig
@@ -316,13 +316,10 @@ export class HaConfigLovelaceDashboards extends LitElement {
)} )}
.data=${this._getItems(this._dashboards)} .data=${this._getItems(this._dashboards)}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
.filter=${this._filter} .filter=${this._filter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@row-click=${this._editDashboard} @row-click=${this._navigate}
id="url_path" id="url_path"
hasFab hasFab
clickable clickable
@@ -354,6 +351,22 @@ export class HaConfigLovelaceDashboards extends LitElement {
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab> </ha-fab>
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<ha-menu id="overflow-menu" positioning="fixed">
<ha-menu-item @click=${this._editDashboard}>
<ha-svg-icon .path=${mdiPencil} slot="start"></ha-svg-icon>
<div slot="headline">Edit</div>
</ha-menu-item>
<ha-menu-item>
<ha-svg-icon .path=${mdiStar} slot="start"></ha-svg-icon>
<div slot="headline">Set to default</div>
</ha-menu-item>
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item class="warning">
<ha-svg-icon .path=${mdiDelete} slot="start"></ha-svg-icon>
<div slot="headline">Delete</div>
</ha-menu-item>
</ha-menu>
`; `;
} }
@@ -366,21 +379,23 @@ export class HaConfigLovelaceDashboards extends LitElement {
this._dashboards = await fetchDashboards(this.hass); this._dashboards = await fetchDashboards(this.hass);
} }
private _navigate(ev: Event) { private _navigate(ev: CustomEvent) {
ev.stopPropagation(); const urlPath = (ev.detail as RowClickedEvent).id;
navigate(`/${(ev.target as any).urlPath}`); navigate(`/${urlPath}`);
} }
private _editDashboard(ev: CustomEvent) { private _editDashboard = (ev) => {
const urlPath = (ev.detail as RowClickedEvent).id; ev.stopPropagation();
const dashboard = ev.currentTarget.parentElement.anchorElement.automation;
const urlPath = (ev.currentTarget as any).urlPath;
if (urlPath === "energy") { if (urlPath === "energy") {
navigate("/config/energy"); navigate("/config/energy");
return; return;
} }
const dashboard = this._dashboards.find((res) => res.url_path === urlPath);
this._openDetailDialog(dashboard, urlPath); this._openDetailDialog(dashboard, urlPath);
} };
private async _addDashboard() { private async _addDashboard() {
showNewDashboardDialog(this, { showNewDashboardDialog(this, {
@@ -475,11 +490,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
} }
declare global { declare global {

View File

@@ -67,27 +67,12 @@ export class HaConfigLovelaceRescources extends LitElement {
}) })
private _activeSorting?: SortingChangedEvent; private _activeSorting?: SortingChangedEvent;
@storage({
key: "lovelace-resources-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "lovelace-resources-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _columns = memoize( private _columns = memoize(
( (
_language, _language,
localize: LocalizeFunc localize: LocalizeFunc
): DataTableColumnContainer<LovelaceResource> => ({ ): DataTableColumnContainer<LovelaceResource> => ({
url: { url: {
main: true,
title: localize( title: localize(
"ui.panel.config.lovelace.resources.picker.headers.url" "ui.panel.config.lovelace.resources.picker.headers.url"
), ),
@@ -160,9 +145,6 @@ export class HaConfigLovelaceRescources extends LitElement {
"ui.panel.config.lovelace.resources.picker.no_resources" "ui.panel.config.lovelace.resources.picker.no_resources"
)} )}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
.filter=${this._filter} .filter=${this._filter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@@ -284,11 +266,6 @@ export class HaConfigLovelaceRescources extends LitElement {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -1,15 +1,12 @@
import { mdiPencil } from "@mdi/js"; import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import "../../../components/entity/ha-entities-picker"; import "../../../components/entity/ha-entities-picker";
import "../../../components/ha-button";
import { createCloseHeading } from "../../../components/ha-dialog"; import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield"; import "../../../components/ha-formfield";
import "../../../components/ha-icon-button";
import "../../../components/ha-picture-upload"; import "../../../components/ha-picture-upload";
import type { HaPictureUpload } from "../../../components/ha-picture-upload"; import type { HaPictureUpload } from "../../../components/ha-picture-upload";
import "../../../components/ha-settings-row";
import "../../../components/ha-textfield"; import "../../../components/ha-textfield";
import { adminChangeUsername } from "../../../data/auth"; import { adminChangeUsername } from "../../../data/auth";
import { PersonMutableParams } from "../../../data/person"; import { PersonMutableParams } from "../../../data/person";
@@ -140,17 +137,11 @@ class DialogPersonDetail extends LitElement {
@change=${this._pictureChanged} @change=${this._pictureChanged}
></ha-picture-upload> ></ha-picture-upload>
<ha-settings-row> <ha-formfield
<span slot="heading"> .label=${`${this.hass!.localize(
${this.hass!.localize(
"ui.panel.config.person.detail.allow_login" "ui.panel.config.person.detail.allow_login"
)} )}${this._user ? ` (${this._user.username})` : ""}`}
</span> >
<span slot="description">
${this.hass!.localize(
"ui.panel.config.person.detail.allow_login_description"
)}
</span>
<ha-switch <ha-switch
@change=${this._allowLoginChanged} @change=${this._allowLoginChanged}
.disabled=${this._user && .disabled=${this._user &&
@@ -159,9 +150,34 @@ class DialogPersonDetail extends LitElement {
this._user.is_owner)} this._user.is_owner)}
.checked=${this._userId} .checked=${this._userId}
></ha-switch> ></ha-switch>
</ha-settings-row> </ha-formfield>
${this._renderUserFields()} ${this._user
? html`<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.person.detail.local_only"
)}
>
<ha-switch
.checked=${this._localOnly}
@change=${this._localOnlyChanged}
>
</ha-switch>
</ha-formfield>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.person.detail.admin"
)}
>
<ha-switch
.disabled=${this._user.system_generated ||
this._user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
</ha-switch>
</ha-formfield>`
: ""}
${this._deviceTrackersAvailable(this.hass) ${this._deviceTrackersAvailable(this.hass)
? html` ? html`
<p> <p>
@@ -219,7 +235,7 @@ class DialogPersonDetail extends LitElement {
</div> </div>
${this._params.entry ${this._params.entry
? html` ? html`
<ha-button <mwc-button
slot="secondaryAction" slot="secondaryAction"
class="warning" class="warning"
@click=${this._deleteEntry} @click=${this._deleteEntry}
@@ -227,10 +243,28 @@ class DialogPersonDetail extends LitElement {
this._submitting} this._submitting}
> >
${this.hass!.localize("ui.panel.config.person.detail.delete")} ${this.hass!.localize("ui.panel.config.person.detail.delete")}
</ha-button> </mwc-button>
${this._user && this.hass.user?.is_owner
? html`<mwc-button
slot="secondaryAction"
@click=${this._changeUsername}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_username"
)}
</mwc-button>
<mwc-button
slot="secondaryAction"
@click=${this._changePassword}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
: ""}
` `
: nothing} : nothing}
<ha-button <mwc-button
slot="primaryAction" slot="primaryAction"
@click=${this._updateEntry} @click=${this._updateEntry}
.disabled=${nameInvalid || this._submitting} .disabled=${nameInvalid || this._submitting}
@@ -238,96 +272,11 @@ class DialogPersonDetail extends LitElement {
${this._params.entry ${this._params.entry
? this.hass!.localize("ui.panel.config.person.detail.update") ? this.hass!.localize("ui.panel.config.person.detail.update")
: this.hass!.localize("ui.panel.config.person.detail.create")} : this.hass!.localize("ui.panel.config.person.detail.create")}
</ha-button> </mwc-button>
</ha-dialog> </ha-dialog>
`; `;
} }
private _renderUserFields() {
const user = this._user;
if (!user) return nothing;
return html`
${!user.system_generated
? html`
<ha-settings-row>
<span slot="heading">
${this.hass.localize("ui.panel.config.person.detail.username")}
</span>
<span slot="description">${user.username}</span>
${this.hass.user?.is_owner
? html`
<ha-icon-button
.path=${mdiPencil}
@click=${this._changeUsername}
.label=${this.hass.localize(
"ui.panel.config.person.detail.change_username"
)}
>
</ha-icon-button>
`
: nothing}
</ha-settings-row>
`
: nothing}
${!user.system_generated && this.hass.user?.is_owner
? html`
<ha-settings-row>
<span slot="heading">
${this.hass.localize("ui.panel.config.person.detail.password")}
</span>
<span slot="description">************</span>
${this.hass.user?.is_owner
? html`
<ha-icon-button
.path=${mdiPencil}
@click=${this._changePassword}
.label=${this.hass.localize(
"ui.panel.config.person.detail.change_password"
)}
>
</ha-icon-button>
`
: nothing}
</ha-settings-row>
`
: nothing}
<ha-settings-row>
<span slot="heading">
${this.hass.localize(
"ui.panel.config.person.detail.local_access_only"
)}
</span>
<span slot="description">
${this.hass.localize(
"ui.panel.config.person.detail.local_access_only_description"
)}
</span>
<ha-switch
.disabled=${user.system_generated}
.checked=${this._localOnly}
@change=${this._localOnlyChanged}
>
</ha-switch>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
${this.hass.localize("ui.panel.config.person.detail.admin")}
</span>
<span slot="description">
${this.hass.localize(
"ui.panel.config.person.detail.admin_description"
)}
</span>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
</ha-switch>
</ha-settings-row>
`;
}
private _closeDialog() { private _closeDialog() {
this._params = undefined; this._params = undefined;
} }
@@ -368,16 +317,14 @@ class DialogPersonDetail extends LitElement {
} else if (this._userId) { } else if (this._userId) {
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.person.detail.confirm_delete_user_title"
),
text: this.hass!.localize( text: this.hass!.localize(
"ui.panel.config.person.detail.confirm_delete_user_text", "ui.panel.config.person.detail.confirm_delete_user",
{ name: this._name } { name: this._name }
), ),
confirmText: this.hass!.localize("ui.common.delete"), confirmText: this.hass!.localize(
"ui.panel.config.person.detail.delete"
),
dismissText: this.hass!.localize("ui.common.cancel"), dismissText: this.hass!.localize("ui.common.cancel"),
destructive: true,
})) }))
) { ) {
target.checked = true; target.checked = true;
@@ -541,8 +488,9 @@ class DialogPersonDetail extends LitElement {
margin-bottom: 16px; margin-bottom: 16px;
--file-upload-image-border-radius: 50%; --file-upload-image-border-radius: 50%;
} }
ha-settings-row { ha-formfield {
padding: 0; display: block;
padding: 16px 0;
} }
a { a {
color: var(--primary-color); color: var(--primary-color);

View File

@@ -180,20 +180,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "scene-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "scene-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width, callback: (entries) => entries[0]?.contentRect.width,
}); });
@@ -239,13 +225,11 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
); );
private _columns = memoizeOne( private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer => { (narrow, localize: LocalizeFunc): DataTableColumnContainer => {
const columns: DataTableColumnContainer<SceneItem> = { const columns: DataTableColumnContainer<SceneItem> = {
icon: { icon: {
title: "", title: "",
label: localize("ui.panel.config.scene.picker.headers.state"), label: localize("ui.panel.config.scene.picker.headers.state"),
moveable: false,
showNarrow: true,
type: "icon", type: "icon",
template: (scene) => html` template: (scene) => html`
<ha-state-icon <ha-state-icon
@@ -261,13 +245,15 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
extraTemplate: (scene) => template: (scene) => html`
scene.labels.length <div style="font-size: 14px;">${scene.name}</div>
${scene.labels.length
? html`<ha-data-table-labels ? html`<ha-data-table-labels
@label-clicked=${this._labelClicked} @label-clicked=${this._labelClicked}
.labels=${scene.labels} .labels=${scene.labels}
></ha-data-table-labels>` ></ha-data-table-labels>`
: nothing, : nothing}
`,
}, },
area: { area: {
title: localize("ui.panel.config.scene.picker.headers.area"), title: localize("ui.panel.config.scene.picker.headers.area"),
@@ -295,6 +281,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
), ),
sortable: true, sortable: true,
width: "30%", width: "30%",
hidden: narrow,
template: (scene) => { template: (scene) => {
const lastActivated = scene.state; const lastActivated = scene.state;
if (!lastActivated || isUnavailableState(lastActivated)) { if (!lastActivated || isUnavailableState(lastActivated)) {
@@ -313,7 +300,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
only_editable: { only_editable: {
title: "", title: "",
width: "56px", width: "56px",
showNarrow: true,
template: (scene) => template: (scene) =>
!scene.attributes.id !scene.attributes.id
? html` ? html`
@@ -333,9 +319,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
title: "", title: "",
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
moveable: false,
hideable: false,
template: (scene) => html` template: (scene) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@@ -553,14 +536,11 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
Array.isArray(val) ? val.length : val Array.isArray(val) ? val.length : val
) )
).length} ).length}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(this.narrow, this.hass.localize)}
id="entity_id" id="entity_id"
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@@ -1175,11 +1155,6 @@ ${rejected
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -2,10 +2,10 @@ import "@material/mwc-button/mwc-button";
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-markdown";
import { fetchBlueprints } from "../../../data/blueprint";
import { BlueprintScriptConfig } from "../../../data/script"; import { BlueprintScriptConfig } from "../../../data/script";
import { fetchBlueprints } from "../../../data/blueprint";
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor"; import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
import "../../../components/ha-markdown";
@customElement("blueprint-script-editor") @customElement("blueprint-script-editor")
export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor { export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {

View File

@@ -6,7 +6,6 @@ import {
mdiDebugStepOver, mdiDebugStepOver,
mdiDelete, mdiDelete,
mdiDotsVertical, mdiDotsVertical,
mdiFileEdit,
mdiFormTextbox, mdiFormTextbox,
mdiInformationOutline, mdiInformationOutline,
mdiPlay, mdiPlay,
@@ -41,7 +40,6 @@ import { validateConfig } from "../../../data/config";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import { EntityRegistryEntry } from "../../../data/entity_registry";
import { import {
BlueprintScriptConfig,
ScriptConfig, ScriptConfig,
deleteScript, deleteScript,
fetchScriptFileConfig, fetchScriptFileConfig,
@@ -63,7 +61,6 @@ import { showAutomationRenameDialog } from "../automation/automation-rename-dial
import "./blueprint-script-editor"; import "./blueprint-script-editor";
import "./manual-script-editor"; import "./manual-script-editor";
import type { HaManualScriptEditor } from "./manual-script-editor"; import type { HaManualScriptEditor } from "./manual-script-editor";
import { substituteBlueprint } from "../../../data/blueprint";
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -231,24 +228,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
></ha-svg-icon> ></ha-svg-icon>
</ha-list-item> </ha-list-item>
${useBlueprint
? html`
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly || this._mode === "yaml"}
>
${this.hass.localize(
"ui.panel.config.script.editor.take_control"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<li divider role="separator"></li> <li divider role="separator"></li>
<ha-list-item graphic="icon" @click=${this._switchUiMode}> <ha-list-item graphic="icon" @click=${this._switchUiMode}>
@@ -622,45 +601,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
} }
}; };
private async _takeControl() {
const config = this._config as BlueprintScriptConfig;
const confirmation = await showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.script.editor.take_control_confirmation.title"
),
text: this.hass!.localize(
"ui.panel.config.script.editor.take_control_confirmation.text"
),
confirmText: this.hass!.localize(
"ui.panel.config.script.editor.take_control_confirmation.action"
),
});
if (!confirmation) return;
try {
const result = await substituteBlueprint(
this.hass,
"script",
config.use_blueprint.path,
config.use_blueprint.input || {}
);
const newConfig = {
...this._normalizeConfig(result.substituted_config),
alias: config.alias,
description: config.description,
};
this._config = newConfig;
this._dirty = true;
this._errors = undefined;
} catch (err: any) {
this._errors = err.message;
}
}
private async _duplicate() { private async _duplicate() {
const result = this._readOnly const result = this._readOnly
? await showConfirmationDialog(this, { ? await showConfirmationDialog(this, {

View File

@@ -184,20 +184,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "script-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "script-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
private _sizeController = new ResizeController(this, { private _sizeController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width, callback: (entries) => entries[0]?.contentRect.width,
}); });
@@ -246,12 +232,14 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
); );
private _columns = memoizeOne( private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer<ScriptItem> => { (
narrow,
localize: LocalizeFunc,
locale: HomeAssistant["locale"]
): DataTableColumnContainer<ScriptItem> => {
const columns: DataTableColumnContainer = { const columns: DataTableColumnContainer = {
icon: { icon: {
title: "", title: "",
showNarrow: true,
moveable: false,
label: localize("ui.panel.config.script.picker.headers.state"), label: localize("ui.panel.config.script.picker.headers.state"),
type: "icon", type: "icon",
template: (script) => template: (script) =>
@@ -271,13 +259,30 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
extraTemplate: (script) => template: (script) => {
script.labels.length const date = new Date(script.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
<div style="font-size: 14px;">${script.name}</div>
${narrow
? html`<div class="secondary">
${this.hass.localize("ui.card.automation.last_triggered")}:
${script.attributes.last_triggered
? dayDifference > 3
? formatShortDateTime(date, locale, this.hass.config)
: relativeTime(date, locale)
: localize("ui.components.relative_time.never")}
</div>`
: nothing}
${script.labels.length
? html`<ha-data-table-labels ? html`<ha-data-table-labels
@label-clicked=${this._labelClicked} @label-clicked=${this._labelClicked}
.labels=${script.labels} .labels=${script.labels}
></ha-data-table-labels>` ></ha-data-table-labels>`
: nothing, : nothing}
`;
},
}, },
area: { area: {
title: localize("ui.panel.config.script.picker.headers.area"), title: localize("ui.panel.config.script.picker.headers.area"),
@@ -300,6 +305,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
template: (script) => script.labels.map((lbl) => lbl.name).join(" "), template: (script) => script.labels.map((lbl) => lbl.name).join(" "),
}, },
last_triggered: { last_triggered: {
hidden: narrow,
sortable: true, sortable: true,
width: "40%", width: "40%",
title: localize("ui.card.automation.last_triggered"), title: localize("ui.card.automation.last_triggered"),
@@ -324,9 +330,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
title: "", title: "",
width: "64px", width: "64px",
type: "overflow-menu", type: "overflow-menu",
showNarrow: true,
moveable: false,
hideable: false,
template: (script) => html` template: (script) => html`
<ha-icon-overflow-menu <ha-icon-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@@ -536,9 +539,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
.initialGroupColumn=${this._activeGrouping || "category"} .initialGroupColumn=${this._activeGrouping || "category"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged} @collapsed-changed=${this._handleCollapseChanged}
@@ -553,7 +553,11 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
Array.isArray(val) ? val.length : val Array.isArray(val) ? val.length : val
) )
).length} ).length}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(
this.narrow,
this.hass.localize,
this.hass.locale
)}
.data=${scripts} .data=${scripts}
.empty=${!this.scripts.length} .empty=${!this.scripts.length}
.activeFilters=${this._activeFilters} .activeFilters=${this._activeFilters}
@@ -1266,11 +1270,6 @@ ${rejected
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -66,12 +66,11 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
}) })
private _filter = ""; private _filter = "";
private _columns = memoizeOne((localize: LocalizeFunc) => { private _columns = memoizeOne(
(narrow: boolean, _language, localize: LocalizeFunc) => {
const columns: DataTableColumnContainer<TagRowData> = { const columns: DataTableColumnContainer<TagRowData> = {
icon: { icon: {
title: "", title: "",
moveable: false,
showNarrow: true,
label: localize("ui.panel.config.tag.headers.icon"), label: localize("ui.panel.config.tag.headers.icon"),
type: "icon", type: "icon",
template: (tag) => html`<tag-image .tag=${tag}></tag-image>`, template: (tag) => html`<tag-image .tag=${tag}></tag-image>`,
@@ -82,10 +81,24 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (tag) =>
html`${tag.display_name}
${narrow
? html`<div class="secondary">
${tag.last_scanned_datetime
? html`<ha-relative-time
.hass=${this.hass}
.datetime=${tag.last_scanned_datetime}
capitalize
></ha-relative-time>`
: this.hass.localize("ui.panel.config.tag.never_scanned")}
</div>`
: ""}`,
}, },
last_scanned_datetime: { last_scanned_datetime: {
title: localize("ui.panel.config.tag.headers.last_scanned"), title: localize("ui.panel.config.tag.headers.last_scanned"),
sortable: true, sortable: true,
hidden: narrow,
direction: "desc", direction: "desc",
width: "20%", width: "20%",
template: (tag) => html` template: (tag) => html`
@@ -104,9 +117,8 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
title: "", title: "",
label: localize("ui.panel.config.tag.headers.write"), label: localize("ui.panel.config.tag.headers.write"),
type: "icon-button", type: "icon-button",
showNarrow: true,
template: (tag) => template: (tag) =>
html`<ha-icon-button html` <ha-icon-button
.tag=${tag} .tag=${tag}
@click=${this._handleWriteClick} @click=${this._handleWriteClick}
.label=${this.hass.localize("ui.panel.config.tag.write")} .label=${this.hass.localize("ui.panel.config.tag.write")}
@@ -117,23 +129,21 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
columns.automation = { columns.automation = {
title: "", title: "",
type: "icon-button", type: "icon-button",
showNarrow: true,
template: (tag) => template: (tag) =>
html`<ha-icon-button html` <ha-icon-button
.tag=${tag} .tag=${tag}
@click=${this._handleAutomationClick} @click=${this._handleAutomationClick}
.label=${this.hass.localize("ui.panel.config.tag.create_automation")} .label=${this.hass.localize(
"ui.panel.config.tag.create_automation"
)}
.path=${mdiRobot} .path=${mdiRobot}
></ha-icon-button>`, ></ha-icon-button>`,
}; };
columns.edit = { columns.edit = {
title: "", title: "",
type: "icon-button", type: "icon-button",
showNarrow: true,
hideable: false,
moveable: false,
template: (tag) => template: (tag) =>
html`<ha-icon-button html` <ha-icon-button
.tag=${tag} .tag=${tag}
@click=${this._handleEditClick} @click=${this._handleEditClick}
.label=${this.hass.localize("ui.panel.config.tag.edit")} .label=${this.hass.localize("ui.panel.config.tag.edit")}
@@ -141,7 +151,8 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
></ha-icon-button>`, ></ha-icon-button>`,
}; };
return columns; return columns;
}); }
);
private _data = memoizeOne((tags: Tag[]): TagRowData[] => private _data = memoizeOne((tags: Tag[]): TagRowData[] =>
tags.map((tag) => ({ tags.map((tag) => ({
@@ -180,7 +191,11 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
back-path="/config" back-path="/config"
.route=${this.route} .route=${this.route}
.tabs=${configSections.tags} .tabs=${configSections.tags}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(
this.narrow,
this.hass.language,
this.hass.localize
)}
.data=${this._data(this._tags)} .data=${this._data(this._tags)}
.noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")} .noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")}
.filter=${this._filter} .filter=${this._filter}

View File

@@ -1,34 +1,31 @@
import "@material/mwc-button";
import { import {
css,
CSSResultGroup, CSSResultGroup,
html,
LitElement, LitElement,
PropertyValues, PropertyValues,
css,
html,
nothing, nothing,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-circular-progress"; import "../../../components/ha-circular-progress";
import { createCloseHeading } from "../../../components/ha-dialog"; import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield"; import "../../../components/ha-formfield";
import "../../../components/ha-icon-button";
import "../../../components/ha-settings-row";
import "../../../components/ha-switch"; import "../../../components/ha-switch";
import type { HaSwitch } from "../../../components/ha-switch"; import type { HaSwitch } from "../../../components/ha-switch";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import { createAuthForUser } from "../../../data/auth"; import { createAuthForUser } from "../../../data/auth";
import { import {
createUser,
deleteUser,
SYSTEM_GROUP_ID_ADMIN, SYSTEM_GROUP_ID_ADMIN,
SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_USER,
User, User,
createUser,
deleteUser,
} from "../../../data/user"; } from "../../../data/user";
import { ValueChangedEvent, HomeAssistant } from "../../../types";
import { haStyleDialog } from "../../../resources/styles"; import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant, ValueChangedEvent } from "../../../types";
import { AddUserDialogParams } from "./show-dialog-add-user"; import { AddUserDialogParams } from "./show-dialog-add-user";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
@customElement("dialog-add-user") @customElement("dialog-add-user")
export class DialogAddUser extends LitElement { export class DialogAddUser extends LitElement {
@@ -158,44 +155,38 @@ export class DialogAddUser extends LitElement {
"ui.panel.config.users.add_user.password_not_match" "ui.panel.config.users.add_user.password_not_match"
)} )}
></ha-textfield> ></ha-textfield>
<ha-settings-row> <div class="row">
<span slot="heading"> <ha-formfield
${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.users.editor.local_access_only" "ui.panel.config.users.editor.local_only"
)} )}
</span> >
<span slot="description">
${this.hass.localize(
"ui.panel.config.users.editor.local_access_only_description"
)}
</span>
<ha-switch <ha-switch
.checked=${this._localOnly} .checked=${this._localOnly}
@change=${this._localOnlyChanged} @change=${this._localOnlyChanged}
> >
</ha-switch> </ha-switch>
</ha-settings-row> </ha-formfield>
<ha-settings-row> </div>
<span slot="heading"> <div class="row">
${this.hass.localize("ui.panel.config.users.editor.admin")} <ha-formfield
</span> .label=${this.hass.localize("ui.panel.config.users.editor.admin")}
<span slot="description"> >
${this.hass.localize( <ha-switch
"ui.panel.config.users.editor.admin_description" .checked=${this._isAdmin}
)} @change=${this._adminChanged}
</span> >
<ha-switch .checked=${this._isAdmin} @change=${this._adminChanged}>
</ha-switch> </ha-switch>
</ha-settings-row> </ha-formfield>
</div>
${!this._isAdmin ${!this._isAdmin
? html` ? html`
<ha-alert alert-type="info"> <br />
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.users.users_privileges_note" "ui.panel.config.users.users_privileges_note"
)} )}
</ha-alert>
` `
: nothing} : ""}
</div> </div>
${this._loading ${this._loading
? html` ? html`
@@ -204,7 +195,7 @@ export class DialogAddUser extends LitElement {
</div> </div>
` `
: html` : html`
<ha-button <mwc-button
slot="primaryAction" slot="primaryAction"
.disabled=${!this._name || .disabled=${!this._name ||
!this._username || !this._username ||
@@ -213,7 +204,7 @@ export class DialogAddUser extends LitElement {
@click=${this._createUser} @click=${this._createUser}
> >
${this.hass.localize("ui.panel.config.users.add_user.create")} ${this.hass.localize("ui.panel.config.users.add_user.create")}
</ha-button> </mwc-button>
`} `}
</ha-dialog> </ha-dialog>
`; `;
@@ -308,10 +299,7 @@ export class DialogAddUser extends LitElement {
} }
ha-textfield { ha-textfield {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 16px;
}
ha-settings-row {
padding: 0;
} }
`, `,
]; ];

View File

@@ -132,7 +132,7 @@ class DialogAdminChangePassword extends LitElement {
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
.disabled=${this._submitting} .disabled=${this._submitting}
></ha-form> ></ha-form>
<mwc-button slot="secondaryAction" @click=${this.closeDialog}> <mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.common.cancel")} ${this.hass.localize("ui.common.cancel")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button

View File

@@ -1,13 +1,12 @@
import { mdiPencil } from "@mdi/js"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import "@material/mwc-button";
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 "../../../components/ha-alert";
import "../../../components/ha-button";
import { createCloseHeading } from "../../../components/ha-dialog"; import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield"; import "../../../components/ha-formfield";
import "../../../components/ha-icon-button"; import "../../../components/ha-help-tooltip";
import "../../../components/ha-label"; import "../../../components/ha-label";
import "../../../components/ha-settings-row";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import "../../../components/ha-switch"; import "../../../components/ha-switch";
import "../../../components/ha-textfield"; import "../../../components/ha-textfield";
@@ -69,15 +68,15 @@ class DialogUserDetail extends LitElement {
.heading=${createCloseHeading(this.hass, user.name)} .heading=${createCloseHeading(this.hass, user.name)}
> >
<div> <div>
${this._error ${this._error ? html` <div class="error">${this._error}</div> ` : ""}
? html`<div class="error">${this._error}</div>`
: nothing}
<div class="secondary"> <div class="secondary">
${this.hass.localize("ui.panel.config.users.editor.id")}: ${this.hass.localize("ui.panel.config.users.editor.id")}:
${user.id}<br /> ${user.id}<br />
${this.hass.localize("ui.panel.config.users.editor.username")}:
${user.username}
</div> </div>
${badges.length === 0 ${badges.length === 0
? nothing ? ""
: html` : html`
<div class="badge-container"> <div class="badge-container">
${badges.map( ${badges.map(
@@ -91,136 +90,74 @@ class DialogUserDetail extends LitElement {
</div> </div>
`} `}
<div class="form"> <div class="form">
${!user.system_generated
? html`
<ha-textfield <ha-textfield
dialogInitialFocus dialogInitialFocus
.value=${this._name} .value=${this._name}
.disabled=${user.system_generated}
@input=${this._nameChanged} @input=${this._nameChanged}
.label=${this.hass!.localize( .label=${this.hass!.localize("ui.panel.config.users.editor.name")}
"ui.panel.config.users.editor.name"
)}
></ha-textfield> ></ha-textfield>
<ha-settings-row> <div class="row">
<span slot="heading"> <ha-formfield
${this.hass.localize(
"ui.panel.config.users.editor.username"
)}
</span>
<span slot="description">${user.username}</span>
${this.hass.user?.is_owner
? html`
<ha-icon-button
.path=${mdiPencil}
@click=${this._changeUsername}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.users.editor.change_username" "ui.panel.config.users.editor.local_only"
)} )}
> >
</ha-icon-button>
`
: nothing}
</ha-settings-row>
`
: nothing}
${!user.system_generated && this.hass.user?.is_owner
? html`
<ha-settings-row>
<span slot="heading">
${this.hass.localize(
"ui.panel.config.users.editor.password"
)}
</span>
<span slot="description">************</span>
${this.hass.user?.is_owner
? html`
<ha-icon-button
.path=${mdiPencil}
@click=${this._changePassword}
.label=${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
>
</ha-icon-button>
`
: nothing}
</ha-settings-row>
`
: nothing}
<ha-settings-row>
<span slot="heading">
${this.hass.localize("ui.panel.config.users.editor.active")}
</span>
<span slot="description">
${this.hass.localize(
"ui.panel.config.users.editor.active_description"
)}
</span>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isActive}
@change=${this._activeChanged}
>
</ha-switch>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
${this.hass.localize(
"ui.panel.config.users.editor.local_access_only"
)}
</span>
<span slot="description">
${this.hass.localize(
"ui.panel.config.users.editor.local_access_only_description"
)}
</span>
<ha-switch <ha-switch
.disabled=${user.system_generated} .disabled=${user.system_generated}
.checked=${this._localOnly} .checked=${this._localOnly}
@change=${this._localOnlyChanged} @change=${this._localOnlyChanged}
> >
</ha-switch> </ha-switch>
</ha-settings-row> </ha-formfield>
<ha-settings-row> </div>
<span slot="heading"> <div class="row">
${this.hass.localize("ui.panel.config.users.editor.admin")} <ha-formfield
</span> .label=${this.hass.localize(
<span slot="description"> "ui.panel.config.users.editor.admin"
${this.hass.localize(
"ui.panel.config.users.editor.admin_description"
)} )}
</span> >
<ha-switch <ha-switch
.disabled=${user.system_generated || user.is_owner} .disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin} .checked=${this._isAdmin}
@change=${this._adminChanged} @change=${this._adminChanged}
> >
</ha-switch> </ha-switch>
</ha-settings-row> </ha-formfield>
${!this._isAdmin && !user.system_generated </div>
${!this._isAdmin
? html` ? html`
<ha-alert alert-type="info"> <br />
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.users.users_privileges_note" "ui.panel.config.users.users_privileges_note"
)} )}
</ha-alert>
` `
: nothing} : ""}
</div> <div class="row">
${user.system_generated <ha-formfield
? html` .label=${this.hass.localize(
<ha-alert alert-type="info"> "ui.panel.config.users.editor.active"
${this.hass.localize(
"ui.panel.config.users.editor.system_generated_read_only_users"
)} )}
</ha-alert> >
` <ha-switch
: nothing} .disabled=${user.system_generated || user.is_owner}
.checked=${this._isActive}
@change=${this._activeChanged}
>
</ha-switch>
</ha-formfield>
<ha-help-tooltip
.label=${this.hass.localize(
"ui.panel.config.users.editor.active_tooltip"
)}
>
</ha-help-tooltip>
</div>
</div>
</div> </div>
<div slot="secondaryAction"> <div slot="secondaryAction">
<ha-button <mwc-button
class="warning" class="warning"
@click=${this._deleteEntry} @click=${this._deleteEntry}
.disabled=${this._submitting || .disabled=${this._submitting ||
@@ -228,18 +165,47 @@ class DialogUserDetail extends LitElement {
user.is_owner} user.is_owner}
> >
${this.hass!.localize("ui.panel.config.users.editor.delete_user")} ${this.hass!.localize("ui.panel.config.users.editor.delete_user")}
</ha-button> </mwc-button>
${user.system_generated
? html`
<simple-tooltip animation-delay="0" position="right">
${this.hass.localize(
"ui.panel.config.users.editor.system_generated_users_not_removable"
)}
</simple-tooltip>
`
: ""}
${!user.system_generated && this.hass.user?.is_owner
? html`<mwc-button @click=${this._changeUsername}>
${this.hass.localize(
"ui.panel.config.users.editor.change_username"
)} </mwc-button
><mwc-button @click=${this._changePassword}>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
: ""}
</div> </div>
<div slot="primaryAction"> <div slot="primaryAction">
<ha-button <mwc-button
@click=${this._updateEntry} @click=${this._updateEntry}
.disabled=${!this._name || .disabled=${!this._name ||
this._submitting || this._submitting ||
user.system_generated} user.system_generated}
> >
${this.hass!.localize("ui.panel.config.users.editor.update_user")} ${this.hass!.localize("ui.panel.config.users.editor.update_user")}
</ha-button> </mwc-button>
${user.system_generated
? html`
<simple-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.users.editor.system_generated_users_not_editable"
)}
</simple-tooltip>
`
: ""}
</div> </div>
</ha-dialog> </ha-dialog>
`; `;
@@ -387,8 +353,27 @@ class DialogUserDetail extends LitElement {
margin-inline-end: 4px; margin-inline-end: 4px;
margin-inline-start: 0; margin-inline-start: 0;
} }
ha-settings-row { .state {
padding: 0; background-color: rgba(var(--rgb-primary-text-color), 0.15);
border-radius: 16px;
padding: 4px 8px;
margin-top: 8px;
display: inline-block;
}
.state:not(:first-child) {
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
}
.row {
display: flex;
padding: 8px 0;
}
ha-help-tooltip {
margin-left: 4px;
margin-inline-start: 4px;
margin-inline-end: initial;
position: relative;
} }
`, `,
]; ];

View File

@@ -46,20 +46,6 @@ export class HaConfigUsers extends LitElement {
@storage({ key: "users-table-grouping", state: false, subscribe: false }) @storage({ key: "users-table-grouping", state: false, subscribe: false })
private _activeGrouping?: string; private _activeGrouping?: string;
@storage({
key: "users-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "users-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@storage({ @storage({
storage: "sessionStorage", storage: "sessionStorage",
key: "users-table-search", key: "users-table-search",
@@ -86,6 +72,17 @@ export class HaConfigUsers extends LitElement {
width: "25%", width: "25%",
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (user) =>
narrow
? html` ${user.name}<br />
<div class="secondary">
${user.username ? `${user.username} |` : ""}
${localize(`groups.${user.group_ids[0]}`)}
</div>`
: html` ${user.name ||
this.hass!.localize(
"ui.panel.config.users.editor.unnamed_user"
)}`,
}, },
username: { username: {
title: localize("ui.panel.config.users.picker.headers.username"), title: localize("ui.panel.config.users.picker.headers.username"),
@@ -93,6 +90,7 @@ export class HaConfigUsers extends LitElement {
filterable: true, filterable: true,
width: "20%", width: "20%",
direction: "asc", direction: "asc",
hidden: narrow,
template: (user) => html`${user.username || "—"}`, template: (user) => html`${user.username || "—"}`,
}, },
group: { group: {
@@ -102,6 +100,7 @@ export class HaConfigUsers extends LitElement {
groupable: true, groupable: true,
width: "20%", width: "20%",
direction: "asc", direction: "asc",
hidden: narrow,
}, },
is_active: { is_active: {
title: this.hass.localize( title: this.hass.localize(
@@ -155,7 +154,6 @@ export class HaConfigUsers extends LitElement {
filterable: false, filterable: false,
width: "104px", width: "104px",
hidden: !narrow, hidden: !narrow,
showNarrow: true,
template: (user) => { template: (user) => {
const badges = computeUserBadges(this.hass, user, false); const badges = computeUserBadges(this.hass, user, false);
return html`${badges.map( return html`${badges.map(
@@ -188,9 +186,6 @@ export class HaConfigUsers extends LitElement {
.tabs=${configSections.persons} .tabs=${configSections.persons}
.columns=${this._columns(this.narrow, this.hass.localize)} .columns=${this._columns(this.narrow, this.hass.localize)}
.data=${this._userData(this._users, this.hass.localize)} .data=${this._userData(this._users, this.hass.localize)}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
@@ -218,7 +213,6 @@ export class HaConfigUsers extends LitElement {
private _userData = memoizeOne((users: User[], localize: LocalizeFunc) => private _userData = memoizeOne((users: User[], localize: LocalizeFunc) =>
users.map((user) => ({ users.map((user) => ({
...user, ...user,
name: user.name || localize("ui.panel.config.users.editor.unnamed_user"),
group: localize(`groups.${user.group_ids[0]}`), group: localize(`groups.${user.group_ids[0]}`),
})) }))
); );
@@ -308,11 +302,6 @@ export class HaConfigUsers extends LitElement {
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
} }
declare global { declare global {

View File

@@ -118,20 +118,6 @@ export class VoiceAssistantsExpose extends LitElement {
}) })
private _activeCollapsed?: string; private _activeCollapsed?: string;
@storage({
key: "voice-expose-table-column-order",
state: false,
subscribe: false,
})
private _activeColumnOrder?: string[];
@storage({
key: "voice-expose-table-hidden-columns",
state: false,
subscribe: false,
})
private _activeHiddenColumns?: string[];
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
@@ -151,7 +137,6 @@ export class VoiceAssistantsExpose extends LitElement {
icon: { icon: {
title: "", title: "",
type: "icon", type: "icon",
moveable: false,
hidden: narrow, hidden: narrow,
template: (entry) => html` template: (entry) => html`
<ha-state-icon <ha-state-icon
@@ -168,21 +153,11 @@ export class VoiceAssistantsExpose extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: narrow template: (entry) => html`
? undefined
: (entry) => html`
${entry.name}<br /> ${entry.name}<br />
<div class="secondary">${entry.entity_id}</div> <div class="secondary">${entry.entity_id}</div>
`, `,
}, },
// For search & narrow
entity_id: {
title: localize(
"ui.panel.config.voice_assistants.expose.headers.entity_id"
),
hidden: !narrow,
filterable: true,
},
domain: { domain: {
title: localize( title: localize(
"ui.panel.config.voice_assistants.expose.headers.domain" "ui.panel.config.voice_assistants.expose.headers.domain"
@@ -196,6 +171,7 @@ export class VoiceAssistantsExpose extends LitElement {
title: localize("ui.panel.config.voice_assistants.expose.headers.area"), title: localize("ui.panel.config.voice_assistants.expose.headers.area"),
sortable: true, sortable: true,
groupable: true, groupable: true,
hidden: narrow,
filterable: true, filterable: true,
width: "15%", width: "15%",
}, },
@@ -203,7 +179,6 @@ export class VoiceAssistantsExpose extends LitElement {
title: localize( title: localize(
"ui.panel.config.voice_assistants.expose.headers.assistants" "ui.panel.config.voice_assistants.expose.headers.assistants"
), ),
showNarrow: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "160px", width: "160px",
@@ -233,6 +208,7 @@ export class VoiceAssistantsExpose extends LitElement {
), ),
sortable: true, sortable: true,
filterable: true, filterable: true,
hidden: narrow,
width: "15%", width: "15%",
template: (entry) => template: (entry) =>
entry.aliases.length === 0 entry.aliases.length === 0
@@ -254,6 +230,12 @@ export class VoiceAssistantsExpose extends LitElement {
.path=${mdiCloseCircleOutline} .path=${mdiCloseCircleOutline}
></ha-icon-button>`, ></ha-icon-button>`,
}, },
// For search
entity_id: {
title: "",
hidden: true,
filterable: true,
},
}) })
); );
@@ -570,9 +552,6 @@ export class VoiceAssistantsExpose extends LitElement {
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.columnOrder=${this._activeColumnOrder}
.hiddenColumns=${this._activeHiddenColumns}
@columns-changed=${this._handleColumnsChanged}
@sorting-changed=${this._handleSortingChanged} @sorting-changed=${this._handleSortingChanged}
@selection-changed=${this._handleSelectionChanged} @selection-changed=${this._handleSelectionChanged}
@grouping-changed=${this._handleGroupingChanged} @grouping-changed=${this._handleGroupingChanged}
@@ -778,11 +757,6 @@ export class VoiceAssistantsExpose extends LitElement {
this._activeCollapsed = ev.detail.value; this._activeCollapsed = ev.detail.value;
} }
private _handleColumnsChanged(ev: CustomEvent) {
this._activeColumnOrder = ev.detail.columnOrder;
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -1,4 +1,3 @@
import { ResizeController } from "@lit-labs/observers/resize-controller";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { import {
CSSResultGroup, CSSResultGroup,
@@ -27,15 +26,6 @@ import { HumidifierCardConfig } from "./types";
@customElement("hui-humidifier-card") @customElement("hui-humidifier-card")
export class HuiHumidifierCard extends LitElement implements LovelaceCard { export class HuiHumidifierCard extends LitElement implements LovelaceCard {
private _resizeController = new ResizeController(this, {
callback: (entries) => {
const container = entries[0]?.target.shadowRoot?.querySelector(
".container"
) as HTMLElement | undefined;
return container?.clientHeight;
},
});
public static async getConfigElement(): Promise<LovelaceCardEditor> { public static async getConfigElement(): Promise<LovelaceCardEditor> {
await import("../editor/config-elements/hui-humidifier-card-editor"); await import("../editor/config-elements/hui-humidifier-card-editor");
return document.createElement("hui-humidifier-card-editor"); return document.createElement("hui-humidifier-card-editor");
@@ -133,25 +123,16 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
const color = stateColorCss(stateObj); const color = stateColorCss(stateObj);
const controlMaxWidth = this._resizeController.value
? `${this._resizeController.value}px`
: undefined;
return html` return html`
<ha-card> <ha-card>
<p class="title">${name}</p> <p class="title">${name}</p>
<div class="container">
<ha-state-control-humidifier-humidity <ha-state-control-humidifier-humidity
style=${styleMap({
maxWidth: controlMaxWidth,
})}
prevent-interaction-on-scroll prevent-interaction-on-scroll
.showCurrentAsPrimary=${this._config.show_current_as_primary} .showCurrentAsPrimary=${this._config.show_current_as_primary}
show-secondary show-secondary
.hass=${this.hass} .hass=${this.hass}
.stateObj=${stateObj} .stateObj=${stateObj}
></ha-state-control-humidifier-humidity> ></ha-state-control-humidifier-humidity>
</div>
<ha-icon-button <ha-icon-button
class="more-info" class="more-info"
.label=${this.hass!.localize( .label=${this.hass!.localize(
@@ -175,15 +156,10 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host {
position: relative;
display: block;
height: 100%;
}
ha-card { ha-card {
position: relative;
height: 100%; height: 100%;
width: 100%; position: relative;
overflow: hidden;
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -202,28 +178,13 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
flex: none;
} }
.container { ha-state-control-humidifier-humidity {
display: flex; width: 100%;
align-items: center; max-width: 344px; /* 12px + 12px + 320px */
justify-content: center; padding: 0 12px 12px 12px;
position: relative;
overflow: hidden;
max-width: 100%;
box-sizing: border-box; box-sizing: border-box;
flex: 1;
}
.container:before {
content: "";
display: block;
padding-top: 100%;
}
.container > * {
padding: 8px;
} }
.more-info { .more-info {
@@ -240,7 +201,6 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
hui-card-features { hui-card-features {
width: 100%; width: 100%;
flex: none;
} }
`; `;
} }

View File

@@ -1,4 +1,3 @@
import { ResizeController } from "@lit-labs/observers/resize-controller";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { import {
CSSResultGroup, CSSResultGroup,
@@ -19,23 +18,14 @@ import "../../../components/ha-icon-button";
import { ClimateEntity } from "../../../data/climate"; import { ClimateEntity } from "../../../data/climate";
import "../../../state-control/climate/ha-state-control-climate-temperature"; import "../../../state-control/climate/ha-state-control-climate-temperature";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "../card-features/hui-card-features";
import { findEntities } from "../common/find-entities"; import { findEntities } from "../common/find-entities";
import { createEntityNotFoundWarning } from "../components/hui-warning"; import { createEntityNotFoundWarning } from "../components/hui-warning";
import "../card-features/hui-card-features";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { ThermostatCardConfig } from "./types"; import { ThermostatCardConfig } from "./types";
@customElement("hui-thermostat-card") @customElement("hui-thermostat-card")
export class HuiThermostatCard extends LitElement implements LovelaceCard { export class HuiThermostatCard extends LitElement implements LovelaceCard {
private _resizeController = new ResizeController(this, {
callback: (entries) => {
const container = entries[0]?.target.shadowRoot?.querySelector(
".container"
) as HTMLElement | undefined;
return container?.clientHeight;
},
});
public static async getConfigElement(): Promise<LovelaceCardEditor> { public static async getConfigElement(): Promise<LovelaceCardEditor> {
await import("../editor/config-elements/hui-thermostat-card-editor"); await import("../editor/config-elements/hui-thermostat-card-editor");
return document.createElement("hui-thermostat-card-editor"); return document.createElement("hui-thermostat-card-editor");
@@ -125,25 +115,16 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
const color = stateColorCss(stateObj); const color = stateColorCss(stateObj);
const controlMaxWidth = this._resizeController.value
? `${this._resizeController.value}px`
: undefined;
return html` return html`
<ha-card> <ha-card>
<p class="title">${name}</p> <p class="title">${name}</p>
<div class="container">
<ha-state-control-climate-temperature <ha-state-control-climate-temperature
style=${styleMap({
maxWidth: controlMaxWidth,
})}
prevent-interaction-on-scroll prevent-interaction-on-scroll
.showCurrentAsPrimary=${this._config.show_current_as_primary} .showCurrentAsPrimary=${this._config.show_current_as_primary}
show-secondary show-secondary
.hass=${this.hass} .hass=${this.hass}
.stateObj=${stateObj} .stateObj=${stateObj}
></ha-state-control-climate-temperature> ></ha-state-control-climate-temperature>
</div>
<ha-icon-button <ha-icon-button
class="more-info" class="more-info"
.label=${this.hass!.localize( .label=${this.hass!.localize(
@@ -167,15 +148,10 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host {
position: relative;
display: block;
height: 100%;
}
ha-card { ha-card {
position: relative;
height: 100%; height: 100%;
width: 100%; position: relative;
overflow: hidden;
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -194,28 +170,13 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
flex: none;
} }
.container { ha-state-control-climate-temperature {
display: flex; width: 100%;
align-items: center; max-width: 344px; /* 12px + 12px + 320px */
justify-content: center; padding: 0 12px 12px 12px;
position: relative;
overflow: hidden;
max-width: 100%;
box-sizing: border-box; box-sizing: border-box;
flex: 1;
}
.container:before {
content: "";
display: block;
padding-top: 100%;
}
.container > * {
padding: 8px;
} }
.more-info { .more-info {
@@ -232,7 +193,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
hui-card-features { hui-card-features {
width: 100%; width: 100%;
flex: none;
} }
`; `;
} }

View File

@@ -1,4 +1,3 @@
import { ResizeController } from "@lit-labs/observers/resize-controller";
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,
@@ -15,6 +14,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { formatNumber } from "../../../common/number/format_number"; import { formatNumber } from "../../../common/number/format_number";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
@@ -74,21 +74,10 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
@state() private _subscribed?: Promise<() => void>; @state() private _subscribed?: Promise<() => void>;
private _sizeController = new ResizeController(this, { // @todo Consider reworking to eliminate need for attribute since it is manipulated internally
callback: (entries) => { @property({ type: Boolean, reflect: true }) public veryVeryNarrow = false;
const width = entries[0]?.contentRect.width;
if (width < 245) { private _resizeObserver?: ResizeObserver;
return "very-very-narrow";
}
if (width < 300) {
return "very-narrow";
}
if (width < 375) {
return "narrow";
}
return "regular";
},
});
private _needForecastSubscription() { private _needForecastSubscription() {
return ( return (
@@ -129,10 +118,14 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
if (this.hasUpdated && this._config && this.hass) { if (this.hasUpdated && this._config && this.hass) {
this._subscribeForecastEvents(); this._subscribeForecastEvents();
} }
this.updateComplete.then(() => this._attachObserver());
} }
public disconnectedCallback(): void { public disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
this._unsubscribeForecastEvents(); this._unsubscribeForecastEvents();
} }
@@ -166,6 +159,16 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
); );
} }
public willUpdate(): void {
if (!this.hasUpdated) {
this._measureCard();
}
}
protected firstUpdated(): void {
this._attachObserver();
}
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
super.updated(changedProps); super.updated(changedProps);
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
@@ -223,10 +226,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
); );
const forecast = const forecast =
this._config?.show_forecast !== false && forecastData?.forecast?.length this._config?.show_forecast !== false && forecastData?.forecast?.length
? forecastData.forecast.slice( ? forecastData.forecast.slice(0, this.veryVeryNarrow ? 3 : 5)
0,
this._sizeController.value === "very-very-narrow" ? 3 : 5
)
: undefined; : undefined;
const weather = !forecast || this._config?.show_current !== false; const weather = !forecast || this._config?.show_current !== false;
@@ -238,7 +238,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
return html` return html`
<ha-card <ha-card
class=${ifDefined(this._sizeController.value)}
@action=${this._handleAction} @action=${this._handleAction}
.actionHandler=${actionHandler({ .actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
@@ -417,6 +416,44 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
handleAction(this, this.hass!, this._config!, ev.detail.action!); handleAction(this, this.hass!, this._config!, ev.detail.action!);
} }
private async _attachObserver(): Promise<void> {
if (!this._resizeObserver) {
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
}
const card = this.shadowRoot!.querySelector("ha-card");
// If we show an error or warning there is no ha-card
if (!card) {
return;
}
this._resizeObserver.observe(card);
}
private _measureCard() {
if (!this.isConnected) {
return;
}
const card = this.shadowRoot!.querySelector("ha-card");
// If we show an error or warning there is no ha-card
if (!card) {
return;
}
if (card.offsetWidth < 375) {
this.setAttribute("narrow", "");
} else {
this.removeAttribute("narrow");
}
if (card.offsetWidth < 300) {
this.setAttribute("verynarrow", "");
} else {
this.removeAttribute("verynarrow");
}
this.veryVeryNarrow = card.offsetWidth < 245;
}
private _showValue(item?: any): boolean { private _showValue(item?: any): boolean {
return typeof item !== "undefined" && item !== null; return typeof item !== "undefined" && item !== null;
} }
@@ -425,11 +462,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
return [ return [
weatherSVGStyles, weatherSVGStyles,
css` css`
:host {
position: relative;
display: block;
height: 100%;
}
ha-card { ha-card {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
@@ -580,48 +612,48 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
/* ============= NARROW ============= */ /* ============= NARROW ============= */
[class*="narrow"] .icon-image { :host([narrow]) .icon-image {
min-width: 52px; min-width: 52px;
} }
[class*="narrow"] .weather-image { :host([narrow]) .weather-image {
flex: 0 0 52px; flex: 0 0 52px;
width: 52px; width: 52px;
} }
[class*="narrow"] .icon-image .weather-icon { :host([narrow]) .icon-image .weather-icon {
--mdc-icon-size: 52px; --mdc-icon-size: 52px;
} }
[class*="narrow"] .state, :host([narrow]) .state,
[class*="narrow"] .temp-attribute .temp { :host([narrow]) .temp-attribute .temp {
font-size: 22px; font-size: 22px;
} }
[class*="narrow"] .temp-attribute .temp { :host([narrow]) .temp-attribute .temp {
margin-right: 16px; margin-right: 16px;
margin-inline-end: 16px; margin-inline-end: 16px;
margin-inline-start: initial; margin-inline-start: initial;
} }
[class*="narrow"] .temp span { :host([narrow]) .temp span {
top: 1px; top: 1px;
font-size: 16px; font-size: 16px;
} }
/* ============= VERY NARROW ============= */ /* ============= VERY NARROW ============= */
[class*="very-narrow"] .name, :host([veryNarrow]) .name,
[class*="very-narrow"] .attribute { :host([veryNarrow]) .attribute {
display: none; display: none;
} }
[class*="very-narrow"] .info { :host([veryNarrow]) .info {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
} }
[class*="very-narrow"] .name-state { :host([veryNarrow]) .name-state {
padding-right: 0; padding-right: 0;
padding-inline-end: 0; padding-inline-end: 0;
padding-inline-start: initial; padding-inline-start: initial;
@@ -629,18 +661,18 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
/* ============= VERY VERY NARROW ============= */ /* ============= VERY VERY NARROW ============= */
[class*="very-very-narrow"] .info { :host([veryVeryNarrow]) .info {
padding-top: 4px; padding-top: 4px;
align-items: center; align-items: center;
} }
[class*="very-very-narrow"] .content { :host([veryVeryNarrow]) .content {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
} }
[class*="very-very-narrow"] .icon-image { :host([veryVeryNarrow]) .icon-image {
margin-right: 0; margin-right: 0;
margin-inline-end: 0; margin-inline-end: 0;
margin-inline-start: initial; margin-inline-start: initial;

View File

@@ -16,7 +16,6 @@ import memoizeOne from "memoize-one";
import { storage } from "../../../../common/decorators/storage"; import { storage } from "../../../../common/decorators/storage";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { stringCompare } from "../../../../common/string/compare"; import { stringCompare } from "../../../../common/string/compare";
import { stripDiacritics } from "../../../../common/string/strip-diacritics";
import "../../../../components/ha-circular-progress"; import "../../../../components/ha-circular-progress";
import "../../../../components/search-input"; import "../../../../components/search-input";
import { isUnavailableState } from "../../../../data/entity"; import { isUnavailableState } from "../../../../data/entity";
@@ -87,10 +86,9 @@ export class HuiCardPicker extends LitElement {
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: Math.min(filter.length, 2), minMatchCharLength: Math.min(filter.length, 2),
threshold: 0.2, threshold: 0.2,
getFn: (obj, path) => stripDiacritics(Fuse.config.getFn(obj, path)),
}; };
const fuse = new Fuse(cards, options); const fuse = new Fuse(cards, options);
cards = fuse.search(stripDiacritics(filter)).map((result) => result.item); cards = fuse.search(filter).map((result) => result.item);
return cardElements.filter((cardElement: CardElement) => return cardElements.filter((cardElement: CardElement) =>
cards.includes(cardElement.card) cards.includes(cardElement.card)
); );

View File

@@ -419,6 +419,11 @@
"manual": "Manual Entry" "manual": "Manual Entry"
} }
}, },
"image": {
"select_image": "Select image",
"upload": "Upload picture",
"url": "Local path or web URL"
},
"text": { "text": {
"show_password": "Show password", "show_password": "Show password",
"hide_password": "Hide password" "hide_password": "Hide password"
@@ -530,8 +535,7 @@
"selected": "Selected {selected}", "selected": "Selected {selected}",
"close_select_mode": "Close selection mode", "close_select_mode": "Close selection mode",
"select_all": "Select all", "select_all": "Select all",
"select_none": "Select none", "select_none": "Select none"
"settings": "Customize table"
}, },
"config-entry-picker": { "config-entry-picker": {
"config_entry": "Integration" "config_entry": "Integration"
@@ -800,14 +804,7 @@
"filtering_by": "Filtering by", "filtering_by": "Filtering by",
"hidden": "{number} hidden", "hidden": "{number} hidden",
"clear": "Clear", "clear": "Clear",
"ungrouped": "Ungrouped", "ungrouped": "Ungrouped"
"settings": {
"header": "Customize",
"hide": "Hide column {title}",
"show": "Show column {title}",
"done": "Done",
"restore": "Restore defaults"
}
}, },
"media-browser": { "media-browser": {
"tts": { "tts": {
@@ -2079,7 +2076,6 @@
"download_backup": "[%key:supervisor::backup::download_backup%]", "download_backup": "[%key:supervisor::backup::download_backup%]",
"remove_backup": "[%key:supervisor::backup::delete_backup_title%]", "remove_backup": "[%key:supervisor::backup::delete_backup_title%]",
"name": "[%key:supervisor::backup::name%]", "name": "[%key:supervisor::backup::name%]",
"path": "Path",
"size": "[%key:supervisor::backup::size%]", "size": "[%key:supervisor::backup::size%]",
"created": "[%key:supervisor::backup::created%]", "created": "[%key:supervisor::backup::created%]",
"no_backups": "[%key:supervisor::backup::no_backups%]", "no_backups": "[%key:supervisor::backup::no_backups%]",
@@ -2674,7 +2670,6 @@
"caption": "Expose", "caption": "Expose",
"headers": { "headers": {
"name": "Name", "name": "Name",
"entity_id": "Entity ID",
"area": "Area", "area": "Area",
"domain": "Domain", "domain": "Domain",
"assistants": "Assistants", "assistants": "Assistants",
@@ -2769,12 +2764,6 @@
"unavailable": "Automation is unavailable", "unavailable": "Automation is unavailable",
"migrate": "Migrate", "migrate": "Migrate",
"duplicate": "[%key:ui::common::duplicate%]", "duplicate": "[%key:ui::common::duplicate%]",
"take_control": "Take control",
"take_control_confirmation": {
"title": "Take control of automation?",
"text": "This automation is using a blueprint. By taking control, your automation will be converted into a regular automation using triggers, conditions and actions. You will be able to edit it directly and you won't be able to convert it back to a blueprint.",
"action": "Take control"
},
"run": "[%key:ui::panel::config::automation::editor::actions::run%]", "run": "[%key:ui::panel::config::automation::editor::actions::run%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"show_trace": "Traces", "show_trace": "Traces",
@@ -3645,12 +3634,6 @@
"show_info": "[%key:ui::panel::config::automation::editor::show_info%]", "show_info": "[%key:ui::panel::config::automation::editor::show_info%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"change_mode": "[%key:ui::panel::config::automation::editor::change_mode%]", "change_mode": "[%key:ui::panel::config::automation::editor::change_mode%]",
"take_control": "[%key:ui::panel::config::automation::editor::take_control%]",
"take_control_confirmation": {
"title": "Take control of script?",
"text": "This script is using a blueprint. By taking control, your script will be converted into a regular automation using actions. You will be able to edit it directly and you won't be able to convert it back to a blueprint.",
"action": "[%key:ui::panel::config::automation::editor::take_control_confirmation::action%]"
},
"read_only": "This script cannot be edited from the UI, because it is not stored in the ''scripts.yaml'' file.", "read_only": "This script cannot be edited from the UI, because it is not stored in the ''scripts.yaml'' file.",
"unavailable": "Script is unavailable", "unavailable": "Script is unavailable",
"migrate": "Migrate", "migrate": "Migrate",
@@ -4050,7 +4033,6 @@
"update_device_error": "Updating the device failed", "update_device_error": "Updating the device failed",
"disabled": "Disabled", "disabled": "Disabled",
"data_table": { "data_table": {
"icon": "Icon",
"device": "Device", "device": "Device",
"manufacturer": "Manufacturer", "manufacturer": "Manufacturer",
"model": "Model", "model": "Model",
@@ -4161,18 +4143,10 @@
"delete": "Delete", "delete": "Delete",
"create": "Create", "create": "Create",
"update": "Update", "update": "Update",
"confirm_delete_user_title": "Delete user account", "confirm_delete_user": "Are you sure you want to delete the user account for {name}? You can still track the user, but the person will no longer be able to login.",
"confirm_delete_user_text": "The user account for ''{name}'' will be permanently deleted. You can still track the user, but the person will no longer be able to login.",
"allow_login": "Allow login",
"allow_login_description": "Allow access using username and password.",
"username": "[%key:ui::panel::config::users::editor::username%]",
"password": "[%key:ui::panel::config::users::editor::password%]",
"admin": "[%key:ui::panel::config::users::editor::admin%]", "admin": "[%key:ui::panel::config::users::editor::admin%]",
"admin_description": "[%key:ui::panel::config::users::editor::admin_description%]", "local_only": "[%key:ui::panel::config::users::editor::local_only%]",
"local_access_only": "[%key:ui::panel::config::users::editor::local_access_only%]", "allow_login": "Allow person to login"
"local_access_only_description": "[%key:ui::panel::config::users::editor::local_access_only_description%]",
"change_username": "[%key:ui::panel::config::users::editor::change_username%]",
"change_password": "[%key:ui::panel::config::users::editor::change_password%]"
} }
}, },
"zone": { "zone": {
@@ -4279,9 +4253,9 @@
"config_entry": { "config_entry": {
"application_credentials": { "application_credentials": {
"delete_title": "Application credentials", "delete_title": "Application credentials",
"delete_prompt": "Would you like to also delete Application Credentials for this integration?", "delete_prompt": "Would you like to also remove Application Credentials for this integration?",
"delete_detail": "If you delete them, you will need to enter credentials when setting up the integration again. If you keep them, they will be used automatically when setting up the integration again or may be acccessed from the Application Credentials menu.", "delete_detail": "If you remove them, you will need to enter credentials when setting up the integration again. If you keep them, they will be used automatically when setting up the integration again or may be acccessed from the Application Credentials menu.",
"delete_error_title": "Deleting application credentials failed", "delete_error_title": "Removing application credentials failed",
"dismiss": "Keep", "dismiss": "Keep",
"learn_more": "Learn more about application credentials" "learn_more": "Learn more about application credentials"
}, },
@@ -4412,7 +4386,6 @@
"caption": "View user", "caption": "View user",
"name": "Display name", "name": "Display name",
"username": "Username", "username": "Username",
"password": "Password",
"change_password": "Change password", "change_password": "Change password",
"change_username": "Change username", "change_username": "Change username",
"activate_user": "Activate user", "activate_user": "Activate user",
@@ -4422,17 +4395,16 @@
"id": "ID", "id": "ID",
"owner": "Owner", "owner": "Owner",
"admin": "Administrator", "admin": "Administrator",
"admin_description": "Administrators can manage users, devices, automations and dashboards.",
"group": "Group", "group": "Group",
"active": "Active", "active": "Active",
"active_description": "Controls if user can login", "local_only": "Can only log in from the local network",
"local_access_only": "Local access only",
"local_access_only_description": "Can only log in from the local network",
"system_generated": "System user", "system_generated": "System user",
"system_generated_read_only_users": "System users can not be updated.", "system_generated_users_not_removable": "Unable to remove system users.",
"system_generated_users_not_editable": "Unable to update system users.",
"unnamed_user": "Unnamed User", "unnamed_user": "Unnamed User",
"confirm_user_deletion_title": "Delete {name}?", "confirm_user_deletion_title": "Delete {name}?",
"confirm_user_deletion_text": "This user will be permanently deleted." "confirm_user_deletion_text": "This user will be permanently deleted.",
"active_tooltip": "Controls if user can login"
}, },
"add_user": { "add_user": {
"caption": "Add user", "caption": "Add user",
@@ -4482,15 +4454,11 @@
"client_id": "OAuth client ID", "client_id": "OAuth client ID",
"application": "Integration" "application": "Integration"
}, },
"remove": {
"button": "Delete application credential",
"confirm_title": "Delete application credential?"
},
"remove_selected": { "remove_selected": {
"button": "Delete selected", "button": "Remove selected",
"confirm_title": "Do you want to delete {number} {number, plural,\n one {credential}\n other {credentials}\n}?", "confirm_title": "Do you want to remove {number} {number, plural,\n one {credential}\n other {credentials}\n}?",
"confirm_text": "Application credentials in use by an integration may not be deleted.", "confirm_text": "Application credentials in use by an integration may not be removed.",
"error_title": "Deleting application credentials failed" "error_title": "Removing application credentials failed"
}, },
"selected": "{number} selected" "selected": "{number} selected"
} }
@@ -5093,11 +5061,6 @@
"tips": { "tips": {
"tip": "Tip!", "tip": "Tip!",
"join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}", "join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}",
"join_x": "X (formerly Twitter)",
"join_forums": "Forums",
"join_chat": "Chat",
"join_blog": "Blog",
"join_newsletter": "Newsletter",
"media_storage": "You can add network storage to your Home Assistant instance in the {storage} panel." "media_storage": "You can add network storage to your Home Assistant instance in the {storage} panel."
}, },
"analytics": { "analytics": {
@@ -6901,7 +6864,7 @@
"forums": "Home Assistant forums", "forums": "Home Assistant forums",
"open_home_newsletter": "Building the Open Home newsletter", "open_home_newsletter": "Building the Open Home newsletter",
"discord": "Discord chat", "discord": "Discord chat",
"x": "[%key:ui::panel::config::tips::join_x%]", "twitter": "Twitter",
"playstore": "Get it on Google Play", "playstore": "Get it on Google Play",
"appstore": "Download on the App Store" "appstore": "Download on the App Store"
}, },

View File

@@ -4356,10 +4356,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/mocha@npm:10.0.7": "@types/mocha@npm:10.0.6":
version: 10.0.7 version: 10.0.6
resolution: "@types/mocha@npm:10.0.7" resolution: "@types/mocha@npm:10.0.6"
checksum: 10/4494871e8a867633d818b00d6f29d47379f9e23655b89ca728166ff2f0a406b97d376fcc3e7a570a3840f72abb03c886c5e66f50ae0f018376e4dc10ed179564 checksum: 10/fc73626e81e89c32d06b7ff9b72c4177b46d579cdd932f796614adc026852d84cb849d743473ba572cb4d9ea6d8c04e3749552d326c26495ec1c4b46e6e0a0c0
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8996,7 +8996,7 @@ __metadata:
"@types/leaflet-draw": "npm:1.0.11" "@types/leaflet-draw": "npm:1.0.11"
"@types/lodash.merge": "npm:4.6.9" "@types/lodash.merge": "npm:4.6.9"
"@types/luxon": "npm:3.4.2" "@types/luxon": "npm:3.4.2"
"@types/mocha": "npm:10.0.7" "@types/mocha": "npm:10.0.6"
"@types/qrcode": "npm:1.5.5" "@types/qrcode": "npm:1.5.5"
"@types/serve-handler": "npm:6.1.4" "@types/serve-handler": "npm:6.1.4"
"@types/sortablejs": "npm:1.15.8" "@types/sortablejs": "npm:1.15.8"
@@ -9100,7 +9100,7 @@ __metadata:
ts-lit-plugin: "npm:2.0.2" ts-lit-plugin: "npm:2.0.2"
tsparticles-engine: "npm:2.12.0" tsparticles-engine: "npm:2.12.0"
tsparticles-preset-links: "npm:2.12.0" tsparticles-preset-links: "npm:2.12.0"
typescript: "npm:5.5.2" typescript: "npm:5.4.5"
ua-parser-js: "npm:1.0.38" ua-parser-js: "npm:1.0.38"
unfetch: "npm:5.0.0" unfetch: "npm:5.0.0"
vis-data: "npm:7.1.9" vis-data: "npm:7.1.9"
@@ -14226,13 +14226,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@npm:5.5.2": "typescript@npm:5.4.5":
version: 5.5.2 version: 5.4.5
resolution: "typescript@npm:5.5.2" resolution: "typescript@npm:5.4.5"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: 10/9118b20f248e76b0dbff8737fef65dfa89d02668d4e633d2c5ceac99033a0ca5e8a1c1a53bc94da68e8f67677a88f318663dde859c9e9a09c1e116415daec2ba checksum: 10/d04a9e27e6d83861f2126665aa8d84847e8ebabcea9125b9ebc30370b98cb38b5dff2508d74e2326a744938191a83a69aa9fddab41f193ffa43eabfdf3f190a5
languageName: node languageName: node
linkType: hard linkType: hard
@@ -14246,13 +14246,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@patch:typescript@npm%3A5.5.2#optional!builtin<compat/typescript>": "typescript@patch:typescript@npm%3A5.4.5#optional!builtin<compat/typescript>":
version: 5.5.2 version: 5.4.5
resolution: "typescript@patch:typescript@npm%3A5.5.2#optional!builtin<compat/typescript>::version=5.5.2&hash=379a07" resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin<compat/typescript>::version=5.4.5&hash=5adc0c"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: 10/ac3145f65cf9e72ab29f2196e05d5816b355dc1a9195b9f010d285182a12457cfacd068be2dd22c877f88ebc966ac6e0e83f51c8586412b16499a27e3670ff4b checksum: 10/760f7d92fb383dbf7dee2443bf902f4365db2117f96f875cf809167f6103d55064de973db9f78fe8f31ec08fff52b2c969aee0d310939c0a3798ec75d0bca2e1
languageName: node languageName: node
linkType: hard linkType: hard