-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Order automation devices in pickers based on automation usage #27477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 9 commits
1e010a1
4381208
697ccc9
1c6b2d5
be52ab5
c686917
d864a47
461a852
394ea46
393be55
81e89d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| import { createContext } from "@lit/context"; | ||
| import type { HomeAssistant } from "../types"; | ||
| import type { AutomationConfig } from "./automation"; | ||
| import { ensureArray } from "../common/array/ensure-array"; | ||
|
|
||
| export type AutomationSection = "triggers" | "conditions" | "actions"; | ||
|
|
||
| export type EntityId = string; | ||
| export type DeviceId = string; | ||
| export type AreaId = string; | ||
| export type Domain = string; | ||
|
|
||
| export interface AutomationLocalContext { | ||
| meta: { | ||
| automationId?: string; | ||
| signature: string; | ||
| }; | ||
|
|
||
| used: { | ||
| entities: EntityId[]; | ||
| devices: DeviceId[]; | ||
| areas: AreaId[]; | ||
| domains: Domain[]; | ||
| }; | ||
|
|
||
| maps: { | ||
| entityArea: Record<EntityId, AreaId | null>; | ||
| entityDevice: Record<EntityId, DeviceId | null>; | ||
| deviceArea: Record<DeviceId, AreaId | null>; | ||
| deviceDomains: Record<DeviceId, Domain[]>; | ||
| }; | ||
|
|
||
| weights?: { | ||
| used: number; | ||
| sameDomain: number; | ||
| sameArea: number; | ||
| }; | ||
|
|
||
| hints?: { | ||
| lastEditedPath?: string[] | number[]; | ||
| labels?: string[]; | ||
| services?: string[]; | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Context key for the automation editor locality context. | ||
| */ | ||
| export const automationEditorContext = createContext< | ||
| AutomationLocalContext | undefined | ||
| >("automationEditorContext"); | ||
|
|
||
| const addIds = (ids: unknown, set: Set<string>) => { | ||
| if (ids === undefined || ids === null) return; | ||
| const list = ensureArray(ids) as unknown[]; | ||
| for (const item of list) { | ||
| if (typeof item === "string" && item) { | ||
| set.add(item); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const getContextSignature = (devices: Set<string>): string => | ||
| Array.from(devices.values()) | ||
| .map((d) => d.slice(0, 6)) | ||
| .sort() | ||
| .join(); | ||
|
|
||
| export const buildAutomationLocalContext = ( | ||
| config: AutomationConfig | undefined, | ||
| _hass: HomeAssistant, | ||
| automationId?: string, | ||
| previous?: AutomationLocalContext | ||
| ): AutomationLocalContext | undefined => { | ||
| if (!config) return undefined; | ||
|
|
||
| const devicesSet = new Set<string>(); | ||
|
|
||
| const scan = (val: any) => { | ||
| if (val === null || val === undefined) return; | ||
| if (Array.isArray(val)) { | ||
| for (const item of val) scan(item); | ||
| return; | ||
| } | ||
| if (typeof val !== "object") return; | ||
|
|
||
| if ("device_id" in val) { | ||
| addIds((val as any).device_id, devicesSet); | ||
| } | ||
| if ("target" in val && val.target && typeof val.target === "object") { | ||
| if ("device_id" in (val.target as any)) { | ||
| addIds((val.target as any).device_id, devicesSet); | ||
| } | ||
| } | ||
|
|
||
| for (const key of Object.keys(val)) { | ||
| const child = (val as any)[key]; | ||
| if (child && typeof child === "object") { | ||
| scan(child); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| scan(config.triggers); | ||
| scan(config.conditions); | ||
| scan(config.actions); | ||
|
|
||
| // Avoid re-rendering if nothing changed | ||
| const signature = getContextSignature(devicesSet); | ||
| if (previous && previous.meta.signature === signature) { | ||
| return previous; | ||
| } | ||
|
|
||
| return { | ||
| meta: { | ||
| automationId, | ||
| signature, | ||
| }, | ||
| used: { | ||
| entities: [], | ||
| devices: Array.from(devicesSet), | ||
| areas: [], | ||
| domains: [], | ||
| }, | ||
| maps: { | ||
| entityArea: {}, | ||
| entityDevice: {}, | ||
| deviceArea: Object.fromEntries( | ||
| Object.entries(_hass.devices || {}).map(([id, dev]) => [ | ||
| id, | ||
| dev.area_id || null, | ||
| ]) | ||
| ), | ||
| deviceDomains: {}, | ||
| }, | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { consume } from "@lit/context"; | ||
| import { consume, ContextProvider } from "@lit/context"; | ||
| import { | ||
| mdiAppleKeyboardCommand, | ||
| mdiCog, | ||
|
|
@@ -89,6 +89,11 @@ import { | |
| import "./blueprint-automation-editor"; | ||
| import "./manual-automation-editor"; | ||
| import type { HaManualAutomationEditor } from "./manual-automation-editor"; | ||
| import { | ||
| automationEditorContext, | ||
| buildAutomationLocalContext, | ||
| } from "../../../data/automation_editor_context"; | ||
| import type { AutomationLocalContext } from "../../../data/automation_editor_context"; | ||
|
|
||
| declare global { | ||
| interface HTMLElementTagNameMap { | ||
|
|
@@ -185,6 +190,13 @@ export class HaAutomationEditor extends PreventUnsavedMixin( | |
| currentConfig: () => this._config!, | ||
| }); | ||
|
|
||
| private _automationContextProvider = new ContextProvider(this, { | ||
| context: automationEditorContext, | ||
| initialValue: undefined, | ||
| }); | ||
|
|
||
| private _automationContextValue?: AutomationLocalContext; | ||
|
|
||
| protected willUpdate(changedProps) { | ||
| super.willUpdate(changedProps); | ||
|
|
||
|
|
@@ -716,6 +728,14 @@ export class HaAutomationEditor extends PreventUnsavedMixin( | |
| Object.values(this._configSubscriptions).forEach((sub) => | ||
| sub(this._config) | ||
| ); | ||
| const ctx = buildAutomationLocalContext( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to prevent this from re-creating everything when the automation changes, as it will trigger the other memoized functions to also recalculate. Can we make sure to only update those parts that actually changed? So if no devices changed, we don't recreate
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a signature to the context that we can compare on change updates to not update the context object. |
||
| this._config, | ||
| this.hass, | ||
| this.automationId ?? this._entityId ?? undefined, | ||
| this._automationContextValue | ||
| ); | ||
| this._automationContextProvider.setValue(ctx); | ||
| this._automationContextValue = ctx; | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bramkragten This is hacky but I didn't want to increase the scope of the PR nor know how you would approach an alternative. In my mind this would be replaced by a proper grouping like “Suggested” and “All devices” or something similar.