perf: lazy-load js-yaml (~107KB) as needed (#2551)

- Closes: #2532
This commit is contained in:
Dermot Duffy
2026-06-30 17:45:13 -07:00
committed by dermotduffy
parent 3fe5e4004a
commit d833edb65b
18 changed files with 413 additions and 134 deletions
+2 -3
View File
@@ -16,7 +16,6 @@ import type { IssueTriggerEventData } from '../card-controller/issues/types.js';
import { CachedValueController } from '../components-lib/cached-value-controller.js';
import { MediaLoadedInfoSourceController } from '../components-lib/media-loaded-info-source-controller.js';
import { UpdatingImageMediaPlayerController } from '../components-lib/media-player/updating-image.js';
import { dataToContext } from '../components-lib/notification/data-to-context.js';
import { SignedURLController } from '../components-lib/signed-url-controller.js';
import type { Notification } from '../config/schema/actions/types.js';
import type { CameraConfig } from '../config/schema/cameras.js';
@@ -418,7 +417,7 @@ export class AdvancedCameraCardImageUpdatingPlayer
icon: 'mdi:alert-circle',
},
link: { url: TROUBLESHOOTING_URL, title: localize('error.troubleshooting') },
context: this.proxyConfig ? dataToContext(this.proxyConfig) : undefined,
context: this.proxyConfig ? [this.proxyConfig] : undefined,
};
}
if (this._imageLoadError) {
@@ -428,7 +427,7 @@ export class AdvancedCameraCardImageUpdatingPlayer
icon: 'mdi:alert-circle',
},
link: { url: TROUBLESHOOTING_URL, title: localize('error.troubleshooting') },
context: this.imageConfig ? dataToContext(this.imageConfig) : undefined,
context: this.imageConfig ? [this.imageConfig] : undefined,
};
}
return null;
+9 -1
View File
@@ -12,6 +12,7 @@ import {
createNotificationFromText,
type NotificationOptions,
} from '../../components-lib/notification/factory.js';
import { NotificationContextController } from '../../components-lib/notification/notification-context-controller.js';
import type { Notification } from '../../config/schema/actions/types.js';
import { localize } from '../../localize/localize.js';
import notificationBlockStyle from '../../scss/notification-block.scss';
@@ -41,11 +42,14 @@ export class AdvancedCameraCardNotificationBlock extends LitElement {
@property({ attribute: false })
public notification: Notification | null = null;
private _contextController = new NotificationContextController(this);
protected render(): TemplateResult | void {
if (!this.notification) {
return;
}
const context = this._contextController.getContext(this.notification);
const { heading, in_progress } = this.notification;
const controls = this.notification.controls ?? [];
@@ -78,7 +82,11 @@ export class AdvancedCameraCardNotificationBlock extends LitElement {
: ''}
</div>`
: ''}
${renderNotificationBody(this.notification, spinnerInBody ? spinner : undefined)}
${renderNotificationBody(
this.notification,
context,
spinnerInBody ? spinner : undefined,
)}
</div>
`;
}
@@ -67,11 +67,11 @@ export function renderControl(
}
export function renderNotificationBody(
notification: Notification,
notification: Omit<Notification, 'context'>,
context: string[],
bodyIconOverride?: TemplateResult,
): TemplateResult {
const { body, link } = notification;
const context = notification.context ?? [];
const metadata = notification.metadata ?? [];
return html`
${metadata.map((detail) => renderDetail(detail, 'metadata'))}
+13 -53
View File
@@ -9,11 +9,11 @@ import { customElement, property } from 'lit/decorators.js';
import { createRef, ref, type Ref } from 'lit/directives/ref.js';
import { handleControlAction } from '../../components-lib/notification/action.js';
import { NotificationContextController } from '../../components-lib/notification/notification-context-controller.js';
import { NotificationPopupController } from '../../components-lib/notification/notification-popup-controller.js';
import type { Notification } from '../../config/schema/actions/types.js';
import { localize } from '../../localize/localize.js';
import notificationPopupStyle from '../../scss/notification-popup.scss';
import { hasPopOutAnimationEnded } from '../../utils/animation.js';
import { dispatchDismissNotificationEvent } from '../../utils/notification.js';
import {
renderControl,
renderDetail,
@@ -26,39 +26,27 @@ export class AdvancedCameraCardNotification extends LitElement {
public notification: Notification | null = null;
private _refNotification: Ref<HTMLElement> = createRef();
public connectedCallback(): void {
super.connectedCallback();
window.addEventListener('click', this._handleOutsideInteraction);
window.addEventListener('focusin', this._handleOutsideInteraction);
// Escape is claimed in the capture phase: the popup is a modal surface and
// must consume Escape before non-modal background controls (e.g. the call
// controls) that also listen on `window`.
window.addEventListener('keydown', this._handleKeyDown, { capture: true });
}
public disconnectedCallback(): void {
window.removeEventListener('click', this._handleOutsideInteraction);
window.removeEventListener('focusin', this._handleOutsideInteraction);
window.removeEventListener('keydown', this._handleKeyDown, { capture: true });
super.disconnectedCallback();
}
private _popupController = new NotificationPopupController(
this,
() => this._refNotification.value ?? null,
);
private _contextController = new NotificationContextController(this);
protected render(): TemplateResult | void {
if (!this.notification) {
return;
}
const context = this._contextController.getContext(this.notification);
const { heading, in_progress } = this.notification;
const controls = this.notification.controls ?? [];
return html`
<div class="backdrop" @click=${this._dismiss}></div>
<div class="backdrop" @click=${this._popupController.dismiss}></div>
<div
class="notification"
${ref(this._refNotification)}
@animationend=${this._handleAnimationEnd}
@animationend=${this._popupController.handleAnimationEnd}
>
${controls.length || in_progress
? html`<div class="controls">
@@ -69,52 +57,24 @@ export class AdvancedCameraCardNotification extends LitElement {
: ''}
${controls.map((control) =>
renderControl(control, (ev, c) =>
handleControlAction(ev, c, this, this._dismiss),
handleControlAction(ev, c, this, this._popupController.dismiss),
),
)}
</div>`
: ''}
<div class="close" @click=${this._dismiss}>
<div class="close" @click=${this._popupController.dismiss}>
<advanced-camera-card-icon
.icon=${{ icon: 'mdi:close' }}
></advanced-camera-card-icon>
</div>
<div class="details">
${heading ? renderDetail(heading, 'heading') : ''}
${renderNotificationBody(this.notification)}
${renderNotificationBody(this.notification, context)}
</div>
</div>
`;
}
private _dismiss = (): void => {
this._refNotification.value?.classList.add('exiting');
};
private _handleAnimationEnd = (ev: AnimationEvent): void => {
if (hasPopOutAnimationEnded(ev)) {
dispatchDismissNotificationEvent(this);
}
};
private _handleOutsideInteraction = (ev: Event): void => {
if (!ev.composedPath().includes(this)) {
this._dismiss();
}
};
private _handleKeyDown = (ev: KeyboardEvent): void => {
if (ev.key === 'Escape') {
this._dismiss();
// `stopImmediatePropagation()` (not `stopPropagation()`) is required to
// block sibling `window` listeners -- `stopPropagation()` only stops
// propagation to other targets, not other listeners on `window` itself.
ev.stopImmediatePropagation();
ev.preventDefault();
}
};
static get styles(): CSSResultGroup {
return unsafeCSS(notificationPopupStyle);
}