chore: Standardize code, comments and docs on US-English spelling (#2707)
This commit is contained in:
@@ -420,7 +420,7 @@ cameras:
|
||||
| ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `event_type` | | The Home Assistant event type to subscribe to (e.g. `zha_event`, `deconz_event`, or a custom event name fired by one of your automations). Same field name and meaning as in HA automation YAML. May be a single string or a list of strings to match any of them. |
|
||||
| `event_data` | | Optional dictionary of key/value pairs the event's payload must contain for this entry to trigger. Mirrors Home Assistant's `event_data` matching exactly: the top-level keys you list must be present in the payload (extra payload keys are ignored), and nested objects are matched the same way -- list only the keys you care about and extra nested keys are ignored. Omit entirely to trigger on every fire of this `event_type`. |
|
||||
| `context` | | Optional filter on the event's `context` object. Recognised fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. |
|
||||
| `context` | | Optional filter on the event's `context` object. Recognized fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. |
|
||||
|
||||
> [!TIP] Shared `event_type` values like `zha_event` and `deconz_event` fire for **every** device on that integration. Without an `event_data` filter the camera would trigger on any Zigbee/deCONZ device press in your home. Use `event_data` to narrow down to the specific device you care about; you can copy values straight out of **Developer tools → Events** in Home Assistant.
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ triggers:
|
||||
| `trigger` | Must be `event`. |
|
||||
| `event_type` | The Home Assistant event type to subscribe to (e.g. `zha_event`, `deconz_event`, or a custom event fired by one of your automations). May be a single string or a list of strings to match any of them. |
|
||||
| `event_data` | Optional dictionary of key/value pairs the event's payload must contain for this entry to match. Mirrors Home Assistant's `event_data` matching exactly: the top-level keys you list must be present in the payload (extra payload keys are ignored), and nested objects are matched the same way -- list only the keys you care about and extra nested keys are ignored. Omit entirely to match every fire of `event_type`. |
|
||||
| `context` | Optional filter on the event's `context` object. Recognised fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. |
|
||||
| `context` | Optional filter on the event's `context` object. Recognized fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. |
|
||||
|
||||
The fired event is exposed to action templates as `trigger.event.*`, matching HA's event trigger template surface (`trigger.event.event_type`, `trigger.event.data`, `trigger.event.context`, `trigger.event.origin`, `trigger.event.time_fired`).
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ folders:
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> To match everything at a given level whilst parsing nothing would simply be
|
||||
> To match everything at a given level while parsing nothing would simply be
|
||||
> represented by an empty object `{}`
|
||||
|
||||
#### Matchers
|
||||
|
||||
@@ -61,7 +61,7 @@ live:
|
||||
| `enabled` | `true` | Whether the on-screen call controls are shown during a call. Set to `false` to hide them and drive calls from the [`menu`](./menu.md) instead. When hiding the controls, be sure to also remove `call` from `menu.auto_hide` so the menu stays visible allowing the call to be controlled. See [Driving calls from the menu](../examples.md?id=driving-calls-from-the-menu) for a complete example. |
|
||||
| `lock` | `true` | Whether to lock the rest of the card controls/actions while a call is in progress. Prevents an accidental tap, swipe or action mid-call. Set to `false` to allow interactions regardless of call state. |
|
||||
| `ringtone` | | The audible chime played while an inbound call (e.g. one started by [`view.triggers.actions.trigger: call`](./view.md?id=trigger-action-configuration)) is ringing. Stops as soon as the call is answered or ended. Manual calls never ring. See [`ringtone`](#ringtone). |
|
||||
| `unanswered_timeout_seconds` | `60` | The number of seconds an inbound call may ring unanswered before it is automatically ended. The timer is cancelled the moment the call is answered. Set to `0` to disable the timeout. |
|
||||
| `unanswered_timeout_seconds` | `60` | The number of seconds an inbound call may ring unanswered before it is automatically ended. The timer is canceled the moment the call is answered. Set to `0` to disable the timeout. |
|
||||
|
||||
> [!NOTE] Browser autoplay restrictions may prevent the ringtone from playing
|
||||
> until the page has received a user gesture (e.g. a tap or click). When that
|
||||
|
||||
@@ -64,7 +64,7 @@ menu:
|
||||
| `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. |
|
||||
| `enabled` | `true` for `call`, `camera_ui`, `cameras`, `display_mode`, `download`, `folders`, `fullscreen`, `gallery`, `info`, `iris`, `live`, `media_player`, `set_review`, `substreams` and `timeline`. `false` for `clips`, `expand`, `image`, `microphone`, `mute`, `pip`, `play`, `ptz_controls`, `ptz_home`, `recordings`, `reviews`, `screenshot` and `snapshots`. | Whether or not to show the button. |
|
||||
| `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. See also [custom icons](../usage/custom-icons.md). |
|
||||
| `inert` | `false` | If `true` the button is shown but rendered as inert (greyed out, non-interactive). Differs from `enabled: false`, which removes the button entirely. |
|
||||
| `inert` | `false` | If `true` the button is shown but rendered as inert (grayed out, non-interactive). Differs from `enabled: false`, which removes the button entirely. |
|
||||
| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). |
|
||||
| `priority` | `50` | The menu item priority. Higher priority items are ordered closer to the start of the menu alignment (i.e. a button with priority `70` will order further to the left than a button with priority `60`). Priority applies separately to `matching` and `opposing` groups (see `alignment` above). Minimum `0`, maximum `100`. |
|
||||
| `state_color` | `true` | Whether to colorize the button based on the state of a related entity (where applicable). |
|
||||
|
||||
@@ -11,7 +11,10 @@ overrides:
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Whilst all configuration parameters are theoretically overridable, in some instances a configuration variable may only be consulted on startup or changing its value may negatively impact behavior -- override results may vary!
|
||||
> While all configuration parameters are theoretically overridable, in some
|
||||
> instances a configuration variable may only be consulted on startup or
|
||||
> changing its value may negatively impact behavior -- override results may
|
||||
> vary!
|
||||
|
||||
> [!WARNING]
|
||||
> Avoid an override whose `conditions` depend on a value that the override itself
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
The behavior of the timeline during seeking/dragging can be controlled by means of the icon on the bottom-right of the timeline, or by using the `pan_mode` configuration variable for the relevant timeline controls (e.g. `live.controls.timeline.pan_mode`).
|
||||
|
||||
| Configuration name | UI Icon | Behavior |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `pan` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pan-horizontal</title><path d="M7,8L2.5,12L7,16V8M17,8V16L21.5,12L17,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></svg> | Dragging the timeline will pan only without selected or seeking any media. |
|
||||
| `seek-in-camera` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>camera-lock</title><path d="M4 4H7L9 2H15L17 4H20C21.11 4 22 4.89 22 6V12C21.16 11.37 20.13 11 19 11C18.21 11 17.46 11.18 16.79 11.5C16.18 9.22 14.27 7 12 7C9.24 7 7 9.24 7 12C7 14.76 9.24 17 12 17C12.42 17 12.84 16.95 13.23 16.85C13.08 17.2 13 17.59 13 18V20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.9 4 4 4M12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9M23 18.3V21.8C23 22.4 22.4 23 21.7 23H16.2C15.6 23 15 22.4 15 21.7V18.2C15 17.6 15.6 17 16.2 17V15.5C16.2 14.1 17.6 13 19 13C20.4 13 21.8 14.1 21.8 15.5V17C22.4 17 23 17.6 23 18.3M20.5 15.5C20.5 14.7 19.8 14.2 19 14.2C18.2 14.2 17.5 14.7 17.5 15.5V17H20.5V15.5Z" /></svg> | Dragging the timeline will seek / select across all available media from the selected camera only, selecting the media item with the longest duration. |
|
||||
| `seek-in-media` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>play-box-lock</title><path d="M23 17.3V20.8C23 21.4 22.4 22 21.7 22H16.2C15.6 22 15 21.4 15 20.7V17.2C15 16.6 15.6 16 16.2 16V14.5C16.2 13.1 17.6 12 19 12C20.4 12 21.8 13.1 21.8 14.5V16C22.4 16 23 16.6 23 17.3M13 19V21H4C2.89 21 2 20.1 2 19V5C2 3.89 2.89 3 4 3H18C19.1 3 20 3.89 20 5V10.1L19 10L18 10.1C15.79 10.55 14.12 12.45 14 14.76C13.39 15.31 13 16.11 13 17V19M20.5 14.5C20.5 13.7 19.8 13.2 19 13.2C18.2 13.2 17.5 13.7 17.5 14.5V16H20.5V14.5M9 8V16L14 12L9 8Z" /></svg> | Dragging the timeline will seek within the selected media item only. |
|
||||
| `seek` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>filmstrip-box-multiple</title><path d="M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M10,15H8V13H10V15M10,11H8V9H10V11M10,7H8V5H10V7M20,15H18V13H20V15M20,11H18V9H20V11M20,7H18V5H20V7Z" /></svg> | Dragging the timeline will seek / select across all available media from all cameras, selecting the media item with the longest duration whilst favoring (but not limited to) the currently selected camera. |
|
||||
| Configuration name | UI Icon | Behavior |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `pan` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pan-horizontal</title><path d="M7,8L2.5,12L7,16V8M17,8V16L21.5,12L17,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></svg> | Dragging the timeline will pan only without selected or seeking any media. |
|
||||
| `seek-in-camera` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>camera-lock</title><path d="M4 4H7L9 2H15L17 4H20C21.11 4 22 4.89 22 6V12C21.16 11.37 20.13 11 19 11C18.21 11 17.46 11.18 16.79 11.5C16.18 9.22 14.27 7 12 7C9.24 7 7 9.24 7 12C7 14.76 9.24 17 12 17C12.42 17 12.84 16.95 13.23 16.85C13.08 17.2 13 17.59 13 18V20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.9 4 4 4M12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9M23 18.3V21.8C23 22.4 22.4 23 21.7 23H16.2C15.6 23 15 22.4 15 21.7V18.2C15 17.6 15.6 17 16.2 17V15.5C16.2 14.1 17.6 13 19 13C20.4 13 21.8 14.1 21.8 15.5V17C22.4 17 23 17.6 23 18.3M20.5 15.5C20.5 14.7 19.8 14.2 19 14.2C18.2 14.2 17.5 14.7 17.5 15.5V17H20.5V15.5Z" /></svg> | Dragging the timeline will seek / select across all available media from the selected camera only, selecting the media item with the longest duration. |
|
||||
| `seek-in-media` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>play-box-lock</title><path d="M23 17.3V20.8C23 21.4 22.4 22 21.7 22H16.2C15.6 22 15 21.4 15 20.7V17.2C15 16.6 15.6 16 16.2 16V14.5C16.2 13.1 17.6 12 19 12C20.4 12 21.8 13.1 21.8 14.5V16C22.4 16 23 16.6 23 17.3M13 19V21H4C2.89 21 2 20.1 2 19V5C2 3.89 2.89 3 4 3H18C19.1 3 20 3.89 20 5V10.1L19 10L18 10.1C15.79 10.55 14.12 12.45 14 14.76C13.39 15.31 13 16.11 13 17V19M20.5 14.5C20.5 13.7 19.8 13.2 19 13.2C18.2 13.2 17.5 13.7 17.5 14.5V16H20.5V14.5M9 8V16L14 12L9 8Z" /></svg> | Dragging the timeline will seek within the selected media item only. |
|
||||
| `seek` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>filmstrip-box-multiple</title><path d="M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M10,15H8V13H10V15M10,11H8V9H10V11M10,7H8V5H10V7M20,15H18V13H20V15M20,11H18V9H20V11M20,7H18V5H20V7Z" /></svg> | Dragging the timeline will seek / select across all available media from all cameras, selecting the media item with the longest duration while favoring (but not limited to) the currently selected camera. |
|
||||
|
||||
+2
-2
@@ -376,7 +376,7 @@ elements:
|
||||
|
||||
The [`doorbell` profile](configuration/profiles.md?id=doorbell) turns a dashboard into a phone-like ringer when somebody presses the doorbell, by setting [`view.triggers.actions.trigger: call`](configuration/view.md?id=triggers) and auto-discovering [HA `event.*` entities](https://www.home-assistant.io/integrations/event/#device-class) with `device_class: doorbell` on the camera's device (Ring, UniFi Protect, Nest, DoorBird, Reolink, etc.). The intended deployment is a wall-mounted tablet sitting on the dashboard.
|
||||
|
||||
A doorbell press is instantaneous, so the card synthesises a ring window from [`view.triggers.event_hold_seconds`](configuration/view.md?id=triggers) (default `30`s) -- long enough for a typical phone-style answer window. `untrigger_delay_seconds` then lingers past that, same as for any stateful trigger.
|
||||
A doorbell press is instantaneous, so the card synthesizes a ring window from [`view.triggers.event_hold_seconds`](configuration/view.md?id=triggers) (default `30`s) -- long enough for a typical phone-style answer window. `untrigger_delay_seconds` then lingers past that, same as for any stateful trigger.
|
||||
|
||||
`triggers.motion`, `triggers.occupancy`, and `triggers.media_events` are off by default -- only the explicit doorbell press triggers the call, so casual motion won't make the card ring.
|
||||
|
||||
@@ -506,7 +506,7 @@ automations:
|
||||
|
||||
> [!WARNING]
|
||||
> This example is only relevant to those doorbells with a design flaw
|
||||
> that prevents the doorbell being rung whilst a 2-way channel is enabled. This
|
||||
> that prevents the doorbell being rung while a 2-way channel is enabled. This
|
||||
> means simply watching the stream, when you might _in future_ hold a 2-way
|
||||
> conversation on that stream, can disable the actual whole point of a doorbell --
|
||||
> to ring!
|
||||
|
||||
@@ -55,7 +55,7 @@ To resolve it:
|
||||
1. Re-create each entry under it in the current format (see the relevant
|
||||
configuration documentation in the sidebar). An upgrade usually fails this
|
||||
way when a feature has been retired, or changed enough that the migration
|
||||
needs human judgement.
|
||||
needs human judgment.
|
||||
1. Once you're happy with the result, delete the `__UPGRADE_FAILURE__` key.
|
||||
|
||||
The notification clears once the `__UPGRADE_FAILURE__` key is gone.
|
||||
|
||||
@@ -12,7 +12,7 @@ The Advanced Camera Card will execute these actions in the following circumstanc
|
||||
- On 'tab' change in a dashboard.
|
||||
- When a `navigate` [action](https://www.home-assistant.io/dashboards/actions/)
|
||||
is called on the dashboard (e.g. a button click requests navigation).
|
||||
- When the user uses the `back` / `forward` browser buttons whilst viewing a
|
||||
- When the user uses the `back` / `forward` browser buttons while viewing a
|
||||
dashboard.
|
||||
|
||||
## Instructions
|
||||
|
||||
@@ -96,7 +96,7 @@ export class ActionsManager implements ActionsExecutor {
|
||||
ev: Event | CustomEvent<ActionConfig>,
|
||||
): Promise<void> => {
|
||||
if (!('detail' in ev)) {
|
||||
// The event may or may not be a CustomEvent object. For example, whilst
|
||||
// The event may or may not be a CustomEvent object. For example, while
|
||||
// this card doesn't use custom-card-helpers, embedded elements may:
|
||||
// https://github.com/custom-cards/custom-card-helpers/blob/master/src/fire-event.ts#L70
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdvancedCameraCardConfig } from '../../config/schema/types';
|
||||
|
||||
// The card state exposed via the `acc` namespace AND as the
|
||||
// `trigger.from_acc`/`to_acc` before/after snapshots (the card analogue of HA's
|
||||
// `trigger.from_acc`/`to_acc` before/after snapshots (the card analog of HA's
|
||||
// full `trigger.from_state`/`to_state`).
|
||||
export interface TemplateAdvancedCameraCardState {
|
||||
camera?: string;
|
||||
|
||||
@@ -25,7 +25,7 @@ export class SubstreamViewModifier implements ViewModifier {
|
||||
return;
|
||||
}
|
||||
// A stream equal to the camera itself is semantically "no substream";
|
||||
// normalise it to a cleared override so the map doesn't carry self-
|
||||
// normalize it to a cleared override so the map doesn't carry self-
|
||||
// referential entries.
|
||||
if (!this._options.stream || this._options.stream === cameraID) {
|
||||
view.context?.live?.overrides?.delete(cameraID);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const getLocalizationKeyForPath = (path: ConfigPath): string =>
|
||||
* Compute the label for a form field. Intended for use as an `ha-form`
|
||||
* `computeLabel` callback. A field is named for the setting it edits, which for
|
||||
* a bound field is wherever that setting is stored rather than where the field
|
||||
* sits in the form. Container nodes are labelled by their explicit title, never
|
||||
* sits in the form. Container nodes are labeled by their explicit title, never
|
||||
* by configuration path (their headers already render the title).
|
||||
* @param form The form the field belongs to.
|
||||
* @param schema The field's schema.
|
||||
@@ -37,7 +37,7 @@ export const computeFormLabel = (
|
||||
return schema.label;
|
||||
}
|
||||
if (!isFormFieldSchema(schema)) {
|
||||
// Only an expandable has a title: a grid lays out fields that are labelled
|
||||
// Only an expandable has a title: a grid lays out fields that are labeled
|
||||
// individually, and shows nothing of its own.
|
||||
return (schema.type === 'expandable' ? schema.title : null) ?? schema.name ?? '';
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const getShownPaths = (forms: EditorForm[]): Set<string> =>
|
||||
|
||||
// What the full editor shows and the simple editor does not. Configuration
|
||||
// outside this is either something the simple editor shows too, or something
|
||||
// neither shows (`elements`, `debug`, anything the card does not recognise at
|
||||
// neither shows (`elements`, `debug`, anything the card does not recognize at
|
||||
// all), which the full editor would be no better at showing.
|
||||
const getFullOnlyPaths = (): Set<string> => {
|
||||
const simplePaths = getShownPaths([
|
||||
|
||||
@@ -34,7 +34,7 @@ import { isWebKitUserAgent } from '../utils/user-agent';
|
||||
// MSE Tuning
|
||||
// ===========================================================================
|
||||
|
||||
// These are judgement-based budgets (time, buffered seconds, staged bytes)
|
||||
// These are judgment-based budgets (time, buffered seconds, staged bytes)
|
||||
// balancing latency and resilience, not derived values. The retained-buffer
|
||||
// window and the staged-bytes cap follow go2rtc's reference web client; the
|
||||
// negotiation timeout has no counterpart there and is added by this
|
||||
|
||||
@@ -89,7 +89,7 @@ export class MediaDimensionsContainerController implements ReactiveController {
|
||||
private _mediaLoadedHandler = (ev: CustomEvent<MediaLoadedInfoEventDetail>): void => {
|
||||
const info = ev.detail.info;
|
||||
// Only resize if the media dimensions have changed (otherwise the loading
|
||||
// image whilst waiting for the stream, will trigger aresize every second).
|
||||
// image while waiting for the stream, will trigger aresize every second).
|
||||
if (
|
||||
this._mediaDimensions?.width === info.width &&
|
||||
this._mediaDimensions.height === info.height
|
||||
|
||||
@@ -186,7 +186,7 @@ export class AdvancedCameraCardImageUpdatingPlayer
|
||||
livenessOptions: {
|
||||
// Frames are only due while the refresh timer runs. A snapshot with
|
||||
// refreshing switched off shows one picture forever, which is the
|
||||
// configured behaviour and never a stall.
|
||||
// configured behavior and never a stall.
|
||||
isFrameExpected: () => this._cachedValueController.hasTimer(),
|
||||
|
||||
// Allow a whole refresh interval to pass, plus the standard window, so
|
||||
|
||||
@@ -154,7 +154,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
|
||||
/**
|
||||
* Determine if a camera image should be shown in lieu of the real stream
|
||||
* whilst loading.
|
||||
* while loading.
|
||||
* @returns`true` if an image should be shown.
|
||||
*/
|
||||
private _shouldShowImageDuringLoading(): boolean {
|
||||
@@ -287,7 +287,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
|
||||
private _getNotification(failure: StreamFailure | null): TemplateResult | null {
|
||||
// If a card *re*-initializes (e.g. was already initialized and then there's
|
||||
// a use of the editor to change the config), cameras will re-initialize in
|
||||
// place, which means they might be asked to render (here) whilst not yet
|
||||
// place, which means they might be asked to render (here) while not yet
|
||||
// being initialized. This can cause spurious errors (e.g. lack of resolved
|
||||
// endpoints). Instead, simply never render uninitialized cameras.
|
||||
if (!this.camera?.isInitialized()) {
|
||||
|
||||
@@ -5,9 +5,10 @@ import type { ConditionsEvaluationResult, ConditionState } from '../types';
|
||||
import type { ConditionEvaluator, ConditionOfType, EvaluatorContext } from './types';
|
||||
|
||||
// Home Assistant resolves an expected value that names an `input_*` helper to
|
||||
// that helper's current state (its Lovelace state-condition behaviour), on both
|
||||
// that helper's current state (its Lovelace state-condition behavior), on both
|
||||
// the state and the attribute path; only these helper domains are resolved.
|
||||
// Regexp directly from: https://github.com/home-assistant/core/blob/dev/homeassistant/helpers/condition.py
|
||||
// Regexp directly from:
|
||||
// https://github.com/home-assistant/core/blob/dev/homeassistant/helpers/condition.py
|
||||
const INPUT_ENTITY_ID =
|
||||
/^input_(?:select|text|number|boolean|datetime)\.(?!.+__)(?!_)[\da-z_]+(?<!_)$/;
|
||||
|
||||
|
||||
@@ -587,7 +587,7 @@ const dropTriggerOnlyConditions = (conditions: unknown[]): unknown[] => {
|
||||
// composite the user wrote empty is kept; only one the recursion emptied
|
||||
// is dropped.
|
||||
if (inner.length || !children.length) {
|
||||
// `arrayify` has already normalised `children`, so rebuilding a
|
||||
// `arrayify` has already normalized `children`, so rebuilding a
|
||||
// composite the recursion left alone would rewrite Home Assistant's
|
||||
// single-condition spelling (`or: <condition>`) into a list the user
|
||||
// never wrote.
|
||||
@@ -672,7 +672,7 @@ const getAutomationList = (
|
||||
plural: string,
|
||||
singular: string,
|
||||
): { key: string; items: unknown[] } => {
|
||||
// Mirroring the schema's rename, the singular key is honoured only when the
|
||||
// Mirroring the schema's rename, the singular key is honored only when the
|
||||
// plural is absent; an automation with neither is written under the plural.
|
||||
const key = singular in automation && !(plural in automation) ? singular : plural;
|
||||
return { key, items: arrayify(automation[key]) };
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
||||
|
||||
import { arrayify } from '../../../utils/basic';
|
||||
|
||||
// Normalise a single item to an array like Home Assistant's `cv.ensure_list` (a
|
||||
// Normalize a single item to an array like Home Assistant's `cv.ensure_list` (a
|
||||
// key written with no value is a list of nothing). The schema output is always
|
||||
// the list, so the rest of the code only ever sees the canonical form. An
|
||||
// absent value is passed through so that an optional field stays absent rather
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface SubscriptionFailure<K> {
|
||||
failureCount?: number;
|
||||
}
|
||||
|
||||
// Recoverable health of a set of keyed subscriptions. A named specialisation
|
||||
// Recoverable health of a set of keyed subscriptions. A named specialization
|
||||
// for readability and the extension point for any subscription-specific health
|
||||
// surface later.
|
||||
export type SubscriptionHealthInterface<K> = RecoverableHealthInterface<
|
||||
|
||||
@@ -88,7 +88,7 @@ const matchesSchemaValue = (expected: unknown, actual: unknown): boolean => {
|
||||
// HA-faithful match for the event `context` object: every defined filter field
|
||||
// must match the event's corresponding field by equality (scalar filter) or
|
||||
// list-membership (array filter). A null event-side field never satisfies an
|
||||
// explicit filter, mirroring HA's behaviour.
|
||||
// explicit filter, mirroring HA's behavior.
|
||||
// https://www.home-assistant.io/docs/automation/trigger/#event-trigger
|
||||
export const matchesEventContext = (
|
||||
filter: HAEventContextFilter,
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
}
|
||||
|
||||
// Each top-level section is an HA expansion panel. An open section draws its
|
||||
// own outline, which would otherwise sit directly against the neighbouring
|
||||
// own outline, which would otherwise sit directly against the neighboring
|
||||
// sections, so every section is spaced whether it is open or not: a gap that
|
||||
// appeared only on opening would move the sections below as the panel grew.
|
||||
// The margins of two adjacent sections collapse together, so the gap is the
|
||||
// same wherever it falls.
|
||||
// appeared only on opening would move the sections below as the panel grew. The
|
||||
// margins of two adjacent sections collapse together, so the gap is the same
|
||||
// wherever it falls.
|
||||
.section {
|
||||
display: block;
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
|
||||
// Triggered and transmitting cameras are highlighted with a soft inset glow,
|
||||
// drawn by an overlay stacked above the camera feed. The overlays come before
|
||||
// the carousel controls in source order, so they stack above the feed but
|
||||
// below the controls; `pointer-events: none` keeps them inert. The glow is an
|
||||
// the carousel controls in source order, so they stack above the feed but below
|
||||
// the controls; `pointer-events: none` keeps them inert. The glow is an
|
||||
// interior effect, so it coexists with the grid selection border (see
|
||||
// media-grid.scss) without competing for the `border` property.
|
||||
//
|
||||
// The two states differ by colour and rhythm: triggered is an amber glow that
|
||||
// The two states differ by color and rhythm: triggered is an amber glow that
|
||||
// pulses fully out and back (a "something happened" beacon), transmitting is a
|
||||
// red glow that breathes gently without ever fully fading ("you are live").
|
||||
// Transmitting wins: while it is active the trigger overlay is faded out.
|
||||
@@ -82,7 +82,7 @@
|
||||
// Hidden by default; activation/deactivation is a plain opacity fade. Each
|
||||
// overlay carries its own static box-shadow (not scoped to the
|
||||
// `[triggered]`/`[transmitting]` selectors) so the glow stays painted in its
|
||||
// own colour throughout the fade-out, after its animation has been removed.
|
||||
// own color throughout the fade-out, after its animation has been removed.
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ img {
|
||||
border-radius: var(--advanced-camera-card-border-radius-final);
|
||||
|
||||
// Not 'contain' as some thumbnails may vary in aspect-ratio slightly and
|
||||
// should be clipped to fill the thumbnail div whilst maintaining
|
||||
// aspect-ratio.
|
||||
// should be clipped to fill the thumbnail div while maintaining aspect-ratio.
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import { errorToConsole } from './basic';
|
||||
* @param host The host object.
|
||||
* @param task The Lit task.
|
||||
* @param completeFunc The function to call with the result.
|
||||
* @param inProgressFunc The function to call whilst in progress.
|
||||
* @param inProgressFunc The function to call while in progress.
|
||||
* @returns A template.
|
||||
*/
|
||||
export const renderTask = <R>(
|
||||
|
||||
@@ -92,7 +92,7 @@ export const tabUntil = async (
|
||||
|
||||
/**
|
||||
* Click an element with a real pointer, which is the only kind that carries the
|
||||
* browser's own behaviour: the press moves focus, and an element that stops the
|
||||
* browser's own behavior: the press moves focus, and an element that stops the
|
||||
* press doing so leaves it where it was.
|
||||
*/
|
||||
export const clickElement = async (element: Element): Promise<void> =>
|
||||
|
||||
@@ -92,12 +92,12 @@ const createRegistryEntry = (entityID: string, overrides?: Partial<Entity>): Ent
|
||||
* A driveable stand-in for the `hass` object the card is handed by Home
|
||||
* Assistant.
|
||||
*
|
||||
* Home Assistant updates immutably, and that is the behaviour reproduced here:
|
||||
* a change hands consumers a new top-level object and a new `states` map
|
||||
* holding a new state object for the entity that changed, while every other
|
||||
* entity keeps the object it already had. Anything the card can observe about
|
||||
* a real `hass` it must be able to observe about this one, so a divergence here
|
||||
* is a bug in the fake rather than a shortcut worth taking.
|
||||
* Home Assistant updates immutably, and that is the behavior reproduced here: a
|
||||
* change hands consumers a new top-level object and a new `states` map holding
|
||||
* a new state object for the entity that changed, while every other entity
|
||||
* keeps the object it already had. Anything the card can observe about a real
|
||||
* `hass` it must be able to observe about this one, so a divergence here is a
|
||||
* bug in the fake rather than a shortcut worth taking.
|
||||
*/
|
||||
export class FakeHASS {
|
||||
private _hass: HomeAssistant;
|
||||
|
||||
@@ -40,8 +40,8 @@ class HAPlayerStandIn extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* A stand-in that renders its children and nothing else. Behaviour is added
|
||||
* only for elements the card is observed to depend on.
|
||||
* A stand-in that renders its children and nothing else. Behavior is added only
|
||||
* for elements the card is observed to depend on.
|
||||
*/
|
||||
class HAElementStub extends LitElement {
|
||||
static styles = css`
|
||||
|
||||
@@ -158,11 +158,11 @@ interface EventEntry {
|
||||
target: EventTarget | null;
|
||||
}
|
||||
|
||||
interface LabelledElement extends Element {
|
||||
interface LabeledElement extends Element {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const hasLabel = (element: Element): element is LabelledElement => 'label' in element;
|
||||
const hasLabel = (element: Element): element is LabeledElement => 'label' in element;
|
||||
|
||||
/**
|
||||
* What a control calls itself to the user. The card titles the controls it
|
||||
|
||||
@@ -36,7 +36,7 @@ const HOME_ASSISTANT_THEME = `
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
/* A dashboard sits the card on the background colour with room around it. */
|
||||
/* A dashboard sits the card on the background color with room around it. */
|
||||
body {
|
||||
background: var(--primary-background-color);
|
||||
margin: 0;
|
||||
|
||||
@@ -87,13 +87,13 @@ export const getTestMediaRequestCount = (url: string): number => {
|
||||
/**
|
||||
* Serves a fixture at `/test-media/<file>`, behaving as the query asks:
|
||||
*
|
||||
* token Which counter the request belongs to, so that a test's
|
||||
* behaviour does not depend on what ran before it.
|
||||
* responses The status to answer each request with, in order: `200` serves
|
||||
* the file and anything else is sent as an empty error.
|
||||
* repeat What to do once the `responses` list is exhausted: answer every
|
||||
* request after it as the last one was, or never answer again
|
||||
* (i.e. camera going quiet).
|
||||
* token Which counter the request belongs to, so that a test's behavior
|
||||
* does not depend on what ran before it. responses The status to
|
||||
* answer each request with, in order: `200` serves the file and
|
||||
* anything else is sent as an empty error. repeat What to do
|
||||
* once the `responses` list is exhausted: answer every request
|
||||
* after it as the last one was, or never answer again (i.e. camera
|
||||
* going quiet).
|
||||
*
|
||||
* Answered from within the page rather than by a server, because a request the
|
||||
* page is still waiting on holds one of the handful of connections a browser
|
||||
|
||||
@@ -1373,8 +1373,8 @@ describe('answer', () => {
|
||||
|
||||
expect(getRingtone().stop).toHaveBeenCalled();
|
||||
|
||||
// Timer was armed and should now be cancelled: advancing past the
|
||||
// timeout must not end the (now-answered) call.
|
||||
// Timer was armed and should now be canceled: advancing past the timeout
|
||||
// must not end the (now-answered) call.
|
||||
vi.advanceTimersByTime(60_000);
|
||||
expect(manager.isActive()).toBe(true);
|
||||
} finally {
|
||||
|
||||
@@ -12,8 +12,8 @@ interface AudioMocks {
|
||||
}
|
||||
|
||||
// Uses real jsdom HTMLAudioElement instances and only stubs the parts jsdom
|
||||
// can't fulfil (`play()` / `pause()` -- no audio backend). Tests then exercise
|
||||
// observable behaviour: registered listeners fire via `dispatchEvent`, property
|
||||
// can't fulfill (`play()` / `pause()` -- no audio backend). Tests then exercise
|
||||
// observable behavior: registered listeners fire via `dispatchEvent`, property
|
||||
// writes round-trip on the element, etc.
|
||||
//
|
||||
// Called once at module load. The `beforeEach`/`afterEach` calls inside this
|
||||
|
||||
@@ -538,7 +538,8 @@ describe('CameraTriggersManager', () => {
|
||||
vi.runOnlyPendingTimers();
|
||||
await flushPromises();
|
||||
|
||||
// Should still be triggered because the second 'new' event should have cancelled the first timer.
|
||||
// Should still be triggered because the second 'new' event should have
|
||||
// canceled the first timer.
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -245,7 +245,7 @@ describe('SessionManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// The user-facing behaviour the machine exists for, driven through the real
|
||||
// The user-facing behavior the machine exists for, driven through the real
|
||||
// schema, trigger factory and evaluator rather than hand-written state.
|
||||
describe('should drive the initialized trigger', () => {
|
||||
it('should fire once per session and not when a session ends', () => {
|
||||
|
||||
@@ -873,7 +873,7 @@ describe('IssueManager', () => {
|
||||
|
||||
// The attempt is now in flight: the problem is still unresolved
|
||||
// (needsRetry) but cannot be retried right now (canRetryNow). The running
|
||||
// timer is cancelled and no further attempt fires, however long we wait.
|
||||
// timer is canceled and no further attempt fires, however long we wait.
|
||||
canRetryNow.mockReturnValue(false);
|
||||
manager.evaluate();
|
||||
vi.advanceTimersByTime(RETRY_EXPONENTIAL_MAX_SECONDS * 1000);
|
||||
|
||||
@@ -488,7 +488,7 @@ describe('MediaUnavailableIssue', () => {
|
||||
|
||||
await card.events.waitForFirst('advanced-camera-card:issue:trigger');
|
||||
|
||||
// The status bar only summarises. Everything a user can do about the
|
||||
// The status bar only summarizes. Everything a user can do about the
|
||||
// failure is behind it, which is the point of the issue report being
|
||||
// clickable.
|
||||
await card.clickControl(MEDIA_ISSUE_TITLE);
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
createStillImageCardConfig,
|
||||
} from '../browser/test-utils';
|
||||
|
||||
// A colour the dark theme sets and the card carries no other way. Reading it
|
||||
// back proves the stylesheet reached the card rather than merely compiling.
|
||||
// See src/scss/themes/dark.scss .
|
||||
// A color the dark theme sets and the card carries no other way. Reading it
|
||||
// back proves the stylesheet reached the card rather than merely compiling. See
|
||||
// src/scss/themes/dark.scss .
|
||||
const DARK_PRIMARY_BACKGROUND = '#111111';
|
||||
|
||||
const mountThemed = async (themes: ThemeName[]) =>
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('computeFormLabel', () => {
|
||||
});
|
||||
|
||||
it('should return an empty label for a grid', () => {
|
||||
// The fields a grid lays out are labelled individually; the grid itself
|
||||
// The fields a grid lays out are labeled individually; the grid itself
|
||||
// shows nothing.
|
||||
expect(computeFormLabel(createForm([]), { type: 'grid', schema: [] })).toBe('');
|
||||
});
|
||||
|
||||
@@ -379,7 +379,7 @@ describe('NumericStateTrigger', () => {
|
||||
setHass(stateManager, { [SENSOR]: { state: '25' } });
|
||||
vi.advanceTimersByTime(3000);
|
||||
|
||||
// Leaves the range before the hold elapses: cancelled.
|
||||
// Leaves the range before the hold elapses: canceled.
|
||||
setHass(stateManager, { [SENSOR]: { state: '10' } });
|
||||
vi.advanceTimersByTime(5000);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('preprocessToArray', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should normalise a single item to a list', () => {
|
||||
it('should normalize a single item to a list', () => {
|
||||
expect(schema.parse({ items: { name: 'office' } })).toEqual({
|
||||
items: [{ name: 'office' }],
|
||||
});
|
||||
|
||||
@@ -1389,7 +1389,7 @@ describe('conditions should accept Home Assistant composite shorthand', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should normalise a single shorthand condition to a list', () => {
|
||||
it('should normalize a single shorthand condition to a list', () => {
|
||||
expect(
|
||||
conditionSchema.parse({ or: { condition: 'fullscreen', fullscreen: true } }),
|
||||
).toMatchObject({
|
||||
@@ -1760,7 +1760,7 @@ describe('should lazy evaluate schemas', () => {
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should normalise single if/then/else items to lists', () => {
|
||||
it('should normalize single if/then/else items to lists', () => {
|
||||
const result = actionConfigSchema.parse({
|
||||
if: { condition: 'state', entity_id: 'light.office', state: 'on' },
|
||||
then: {
|
||||
@@ -1912,7 +1912,7 @@ describe('automations should require actions', () => {
|
||||
});
|
||||
|
||||
describe('automations should accept Home Assistant input shorthands', () => {
|
||||
it('should normalise singular keys and single items to lists', () => {
|
||||
it('should normalize singular keys and single items to lists', () => {
|
||||
const result = automationsSchema.parse([
|
||||
{
|
||||
trigger: { trigger: 'state', entity_id: 'binary_sensor.door', to: 'on' },
|
||||
|
||||
Vendored
+1
-1
@@ -227,7 +227,7 @@ describe('the built card', () => {
|
||||
// only through the accessors `@property` installs. Were the decorator
|
||||
// output wrong, writes would land on plain instance fields and none of this
|
||||
// would work. This verifies the build's treatment of decorators rather than
|
||||
// the card's behaviour per se (already well covered in other tests).
|
||||
// the card's behavior per se (already well covered in other tests).
|
||||
await mounted.events.waitForFirst('advanced-camera-card:media:loaded');
|
||||
|
||||
expect(isLiveMediaShowing(mounted.card)).toBe(true);
|
||||
|
||||
+7
-6
@@ -46,12 +46,13 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
|
||||
resolve: {
|
||||
// Same as Vite's default, minus the trailing `jsnext:main` and `jsnext`: an
|
||||
// early convention for naming an ES module build that `module` replaced.
|
||||
// `moment` is the one dependency still shipping it, and honouring it hands
|
||||
// out moment as an ES module, which `vis-timeline`'s CommonJS build cannot
|
||||
// then use -- causing timeline views to throw an error. Requiring an ES
|
||||
// module yields the namespace object rather than moment itself.
|
||||
// Same as Vite's default, minus the trailing `jsnext:main` and `jsnext`:
|
||||
// an early convention for naming an ES module build that `module`
|
||||
// replaced. `moment` is the one dependency still shipping it, and
|
||||
// honoring it hands out moment as an ES module, which `vis-timeline`'s
|
||||
// CommonJS build cannot then use -- causing timeline views to throw an
|
||||
// error. Requiring an ES module yields the namespace object rather than
|
||||
// moment itself.
|
||||
mainFields: ['browser', 'module'],
|
||||
},
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export default defineConfig({
|
||||
|
||||
resolve: {
|
||||
// Several dependencies declare their own Lit. Two copies in one page do not
|
||||
// recognise each other's directives and template results, which surfaces as
|
||||
// recognize each other's directives and template results, which surfaces as
|
||||
// "Multiple versions of Lit loaded" followed by render-time type errors.
|
||||
// The build gets one copy from its own resolver; the tests have to be told.
|
||||
dedupe: ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'],
|
||||
|
||||
Reference in New Issue
Block a user