Compare commits

..
11 changed files with 131 additions and 44 deletions
-3
View File
@@ -64,7 +64,6 @@ const DEVICES: DeviceRegistryEntry[] = [
labels: [],
created_at: 0,
modified_at: 0,
primary_config_entry: null,
},
{
area_id: "backyard",
@@ -87,7 +86,6 @@ const DEVICES: DeviceRegistryEntry[] = [
labels: [],
created_at: 0,
modified_at: 0,
primary_config_entry: null,
},
{
area_id: null,
@@ -110,7 +108,6 @@ const DEVICES: DeviceRegistryEntry[] = [
labels: [],
created_at: 0,
modified_at: 0,
primary_config_entry: null,
},
];
@@ -64,7 +64,6 @@ const DEVICES: DeviceRegistryEntry[] = [
labels: [],
created_at: 0,
modified_at: 0,
primary_config_entry: null,
},
{
area_id: "backyard",
@@ -87,7 +86,6 @@ const DEVICES: DeviceRegistryEntry[] = [
labels: [],
created_at: 0,
modified_at: 0,
primary_config_entry: null,
},
{
area_id: null,
@@ -110,7 +108,6 @@ const DEVICES: DeviceRegistryEntry[] = [
labels: [],
created_at: 0,
modified_at: 0,
primary_config_entry: null,
},
];
@@ -232,7 +232,6 @@ const createDeviceRegistryEntries = (
labels: [],
created_at: 0,
modified_at: 0,
primary_config_entry: null,
},
];
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20240906.0"
version = "20240904.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
+16 -15
View File
@@ -1,6 +1,6 @@
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../types";
import type { IntegrationType } from "./integration";
import type { IntegrationManifest, IntegrationType } from "./integration";
export interface ConfigEntry {
entry_id: string;
@@ -149,19 +149,20 @@ export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
export const sortConfigEntries = (
configEntries: ConfigEntry[],
primaryConfigEntry: string | null
manifestLookup: { [domain: string]: IntegrationManifest }
): ConfigEntry[] => {
if (!primaryConfigEntry) {
return configEntries;
}
const primaryEntry = configEntries.find(
(e) => e.entry_id === primaryConfigEntry
);
if (!primaryEntry) {
return configEntries;
}
const otherEntries = configEntries.filter(
(e) => e.entry_id !== primaryConfigEntry
);
return [primaryEntry, ...otherEntries];
const sortedConfigEntries = [...configEntries];
const getScore = (entry: ConfigEntry) => {
const manifest = manifestLookup[entry.domain] as
| IntegrationManifest
| undefined;
const isHelper = manifest?.integration_type === "helper";
return isHelper ? -1 : 1;
};
const configEntriesCompare = (a: ConfigEntry, b: ConfigEntry) =>
getScore(b) - getScore(a);
return sortedConfigEntries.sort(configEntriesCompare);
};
-1
View File
@@ -33,7 +33,6 @@ export interface DeviceRegistryEntry extends RegistryEntry {
entry_type: "service" | null;
disabled_by: "user" | "integration" | "config_entry" | null;
configuration_url: string | null;
primary_config_entry: string | null;
}
export interface DeviceEntityDisplayLookup {
+2
View File
@@ -25,6 +25,8 @@ export interface LovelaceBaseViewConfig {
// Only used for section view, it should move to a section view config type when the views will have dedicated editor.
max_columns?: number;
dense_section_placement?: boolean;
column_breakpoints?: Record<string, number>;
experimental_breakpoints?: boolean;
}
export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
@@ -153,7 +153,7 @@ export class HaConfigDevicePage extends LitElement {
.filter((entId) => entId in entryLookup)
.map((entry) => entryLookup[entry]);
return sortConfigEntries(deviceEntries, device.primary_config_entry);
return sortConfigEntries(deviceEntries, manifestLookup);
}
);
@@ -388,7 +388,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
device.config_entries
.filter((entId) => entId in entryLookup)
.map((entId) => entryLookup[entId]),
device.primary_config_entry
manifestLookup
);
const labels = labelReg && device?.labels;
@@ -6,18 +6,19 @@ import {
mdiCloseCircle,
mdiProgressClock,
} from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { groupBy } from "../../../../../common/util/group-by";
import memoizeOne from "memoize-one";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-next";
@@ -26,19 +27,25 @@ import "../../../../../components/ha-settings-row";
import "../../../../../components/ha-svg-icon";
import "../../../../../components/ha-switch";
import "../../../../../components/ha-textfield";
import { computeDeviceName } from "../../../../../data/device_registry";
import { groupBy } from "../../../../../common/util/group-by";
import {
computeDeviceName,
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../../../data/device_registry";
import {
ZWaveJSNodeConfigParam,
ZWaveJSNodeConfigParams,
ZWaveJSSetConfigParamResult,
ZwaveJSNodeMetadata,
fetchZwaveNodeConfigParameters,
fetchZwaveNodeMetadata,
setZwaveNodeConfigParameter,
ZWaveJSNodeConfigParam,
ZWaveJSNodeConfigParams,
ZwaveJSNodeMetadata,
ZWaveJSSetConfigParamResult,
} from "../../../../../data/zwave_js";
import "../../../../../layouts/hass-error-screen";
import "../../../../../layouts/hass-loading-screen";
import "../../../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
@@ -50,8 +57,16 @@ const icons = {
error: mdiCloseCircle,
};
const getDevice = memoizeOne(
(
deviceId: string,
entries?: DeviceRegistryEntry[]
): DeviceRegistryEntry | undefined =>
entries?.find((device) => device.id === deviceId)
);
@customElement("zwave_js-node-config")
class ZWaveJSNodeConfig extends LitElement {
class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public route!: Route;
@@ -64,6 +79,8 @@ class ZWaveJSNodeConfig extends LitElement {
@property() public deviceId!: string;
@state() private _deviceRegistryEntries?: DeviceRegistryEntry[];
@state() private _nodeMetadata?: ZwaveJSNodeMetadata;
@state() private _config?: ZWaveJSNodeConfigParams;
@@ -77,8 +94,19 @@ class ZWaveJSNodeConfig extends LitElement {
this.deviceId = this.route.path.substr(1);
}
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._deviceRegistryEntries = entries;
}),
];
}
protected updated(changedProps: PropertyValues): void {
if (!this._config || changedProps.has("deviceId")) {
if (
(!this._config || changedProps.has("deviceId")) &&
changedProps.has("_deviceRegistryEntries")
) {
this._fetchData();
}
}
@@ -97,7 +125,7 @@ class ZWaveJSNodeConfig extends LitElement {
return html`<hass-loading-screen></hass-loading-screen>`;
}
const device = this.hass.devices[this.deviceId];
const device = this._device!;
return html`
<hass-tabs-subpage
@@ -364,7 +392,7 @@ class ZWaveJSNodeConfig extends LitElement {
try {
const result = await setZwaveNodeConfigParameter(
this.hass,
this.deviceId,
this._device!.id,
target.property,
target.endpoint,
value,
@@ -392,12 +420,16 @@ class ZWaveJSNodeConfig extends LitElement {
this._results = { ...this._results, [key]: errorParam };
}
private get _device(): DeviceRegistryEntry | undefined {
return getDevice(this.deviceId, this._deviceRegistryEntries);
}
private async _fetchData() {
if (!this.configEntryId) {
if (!this.configEntryId || !this._deviceRegistryEntries) {
return;
}
const device = this.hass.devices[this.deviceId];
const device = this._device;
if (!device) {
this._error = "device_not_found";
return;
+63 -3
View File
@@ -12,6 +12,7 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import { styleMap } from "lit/directives/style-map";
import { listenMediaQuery } from "../../../common/dom/media_query";
import "../../../components/ha-icon-button";
import "../../../components/ha-sortable";
import "../../../components/ha-svg-icon";
@@ -28,8 +29,29 @@ import { showEditSectionDialog } from "../editor/section-editor/show-edit-sectio
import { HuiSection } from "../sections/hui-section";
import type { Lovelace } from "../types";
type Breakpoints = Record<string, number>;
export const DEFAULT_MAX_COLUMNS = 4;
export const DEFAULT_BREAKPOINTS: Breakpoints = {
"0": 1,
"768": 2,
"1280": 3,
"1600": 4,
"1920": 5,
"2560": 6,
};
const buildMediaQueries = (breakpoints: Breakpoints) =>
Object.keys(breakpoints).map((breakpoint, index, array) => {
const nextBreakpoint = array[index + 1] as string | undefined;
let mediaQuery = `(min-width: ${breakpoint}px)`;
if (nextBreakpoint) {
mediaQuery += ` and (max-width: ${parseInt(nextBreakpoint) - 1}px)`;
}
return mediaQuery;
});
const parsePx = (value: string) => parseInt(value.replace("px", ""));
@customElement("hui-sections-view")
@@ -52,8 +74,15 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
@state() _dragging = false;
private _listeners: Array<() => void> = [];
@state() private _breakpointsColumns: number = 1;
private _columnsController = new ResizeController(this, {
callback: (entries) => {
// Don't do anything if we are using breakpoints
if (this._config?.experimental_breakpoints) return 1;
const totalWidth = entries[0]?.contentRect.width;
const style = getComputedStyle(this);
@@ -76,8 +105,13 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
},
});
private get _sizeColumns() {
return this._columnsController.value ?? 1;
}
public setConfig(config: LovelaceViewConfig): void {
this._config = config;
this._attachMediaQueriesListeners();
}
private _sectionConfigKeys = new WeakMap<HuiSection, string>();
@@ -100,12 +134,35 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
this._computeSectionsCount();
};
private _attachMediaQueriesListeners() {
this._detachMediaQueriesListeners();
if (!this._config?.experimental_breakpoints) return;
const breakpoints = this._config?.column_breakpoints ?? DEFAULT_BREAKPOINTS;
const maxColumns = this._config?.max_columns ?? DEFAULT_MAX_COLUMNS;
const mediaQueries = buildMediaQueries(breakpoints);
this._listeners = mediaQueries.map((mediaQuery, index) =>
listenMediaQuery(mediaQuery, (matches) => {
if (matches) {
const columns = Object.values(breakpoints)[index];
this._breakpointsColumns = Math.min(maxColumns, columns);
}
})
);
}
private _detachMediaQueriesListeners() {
while (this._listeners.length) {
this._listeners.pop()!();
}
}
connectedCallback(): void {
super.connectedCallback();
this.addEventListener(
"section-visibility-changed",
this._sectionVisibilityChanged
);
this._attachMediaQueriesListeners();
}
disconnectedCallback(): void {
@@ -114,6 +171,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
"section-visibility-changed",
this._sectionVisibilityChanged
);
this._detachMediaQueriesListeners();
}
willUpdate(changedProperties: PropertyValues<typeof this>): void {
@@ -130,7 +188,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
this._sectionColumnCount + (this.lovelace?.editMode ? 1 : 0);
const editMode = this.lovelace.editMode;
const maxColumnCount = this._columnsController.value ?? 1;
const maxColumnCount = this._config?.experimental_breakpoints
? this._breakpointsColumns
: this._sizeColumns;
return html`
<hui-view-badges
@@ -321,9 +381,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
:host {
--row-height: var(--ha-view-sections-row-height, 56px);
--row-gap: var(--ha-view-sections-row-gap, 8px);
--column-gap: var(--ha-view-sections-column-gap, 32px);
--column-max-width: var(--ha-view-sections-column-max-width, 500px);
--column-gap: var(--ha-view-sections-column-gap, 24px);
--column-min-width: var(--ha-view-sections-column-min-width, 320px);
--column-max-width: var(--ha-view-sections-column-max-width, 500px);
display: block;
}