perf: Variety of small type and performance fixes (#2367)

This commit is contained in:
Dermot Duffy
2026-02-22 15:02:14 -08:00
committed by GitHub
parent 0f8c758d38
commit 497e6e88a0
10 changed files with 82 additions and 53 deletions
-2
View File
@@ -24,11 +24,9 @@
"@graphiteds/core": "^1.9.21",
"@lit-labs/scoped-registry-mixin": "^1.0.3",
"@lit-labs/task": "^1.1.3",
"@types/bluebird": "^3.5.42",
"any-date-parser": "^2.2.0",
"component-emitter": "^1.3.1",
"compute-scroll-into-view": "^3.1.1",
"crypto": "^1.0.1",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"embla-carousel": "^8.6.0",
+1 -3
View File
@@ -155,11 +155,9 @@ export const getReviewThumbnailURL = (
* Get generic review severity.
*/
export const getReviewSeverity = (severity: FrigateReviewSeverity): Severity => {
// Frigate severities: 'alert' -> 'high', 'detection' -> 'medium'.
if (severity === 'alert') {
return 'high';
}
if (severity === 'detection') {
return 'medium';
}
return 'low';
};
+2 -6
View File
@@ -73,18 +73,14 @@ export class ConfigManager {
(hint ?? localize('error.invalid_configuration_no_hint')),
);
}
const config = advancedCameraCardConfigSchema.parse(
setProfiles(
inputConfig,
// The config is cloned here because Zod 4 returns shared constant
// defaults by reference. Since setProfiles() mutates the configuration
// in-place, those mutations would "pollute" the global defaults and break
// test isolation if we didn't use a fresh clone here.
const config = setProfiles(
inputConfig,
copyConfig(parseResult.data),
parseResult.data.profiles,
),
);
this._rawConfig = inputConfig;
@@ -23,6 +23,7 @@ export class GalleryCoreController implements ReactiveController {
private _options: GalleryCoreOptions | null = null;
private _touchScrollYPosition: number | null = null;
private _observedSentinel: HTMLElement | null = null;
// Wheel / touch events may be voluminous, throttle extension calls.
private _throttledExtendUp = throttle(
@@ -99,16 +100,23 @@ export class GalleryCoreController implements ReactiveController {
this._host.removeEventListener('touchend', this._touchEndHandler);
this._resizeObserver.disconnect();
this._intersectionObserver.disconnect();
this._observedSentinel = null;
}
public hostUpdated(): void {
const sentinel = this._getSentintelBottom();
// Avoid redundant observer disconnect/reconnect on every Lit update cycle
// when the sentinel element hasn't changed.
if (sentinel !== this._observedSentinel) {
this._intersectionObserver.disconnect();
this._observedSentinel = sentinel;
if (sentinel) {
this._intersectionObserver.observe(sentinel);
}
}
}
private _setColumnCount(): void {
if (!this._options?.columnWidth) {
+8 -7
View File
@@ -63,6 +63,8 @@ export class AdvancedCameraCardLiveWebRTCCard extends LitElement implements Medi
private hass?: HomeAssistant;
private _videoRTC: VideoRTC | null = null;
private _mediaPlayerController = new VideoMediaPlayerController(
this,
() => this._getVideo(),
@@ -85,6 +87,7 @@ export class AdvancedCameraCardLiveWebRTCCard extends LitElement implements Medi
}
disconnectedCallback(): void {
this._videoRTC = null;
this._message = null;
super.disconnectedCallback();
}
@@ -97,16 +100,12 @@ export class AdvancedCameraCardLiveWebRTCCard extends LitElement implements Medi
}
}
private _getVideoRTC(): VideoRTC | null {
return (this.renderRoot?.querySelector('#webrtc') ?? null) as VideoRTC | null;
}
/**
* Get the underlying video player.
* @returns The player or `null` if not found.
*/
private _getVideo(): HTMLVideoElement | null {
return this._getVideoRTC()?.video ?? null;
return this._videoRTC?.video ?? null;
}
private async _getWebRTCCardElement(): Promise<CustomElementConstructor | undefined> {
@@ -196,7 +195,7 @@ export class AdvancedCameraCardLiveWebRTCCard extends LitElement implements Medi
// Extract the video component after it has been rendered and generate the
// media load event.
this.updateComplete.then(() => {
const videoRTC = this._getVideoRTC();
this._videoRTC = this.renderRoot?.querySelector('#webrtc') ?? null;
const video = this._getVideo();
if (video) {
setControlsOnVideo(video, this.controls);
@@ -210,7 +209,9 @@ export class AdvancedCameraCardLiveWebRTCCard extends LitElement implements Medi
supportsPause: true,
hasAudio: mayHaveAudio(video),
},
...(videoRTC && { technology: getTechnologyForVideoRTC(videoRTC) }),
...(this._videoRTC && {
technology: getTechnologyForVideoRTC(this._videoRTC),
}),
});
};
video.onplay = () => dispatchMediaPlayEvent(this);
@@ -258,7 +258,4 @@ describe('getReviewSeverity', () => {
it('should get detection severity', () => {
expect(getReviewSeverity('detection')).toBe('medium');
});
it('should get significant_motion severity', () => {
expect(getReviewSeverity('significant_motion')).toBe('low');
});
});
@@ -22,7 +22,7 @@ describe('OverlayMessageManager', () => {
it('should set and get message', () => {
const manager = new OverlayMessageManager(api);
const message = { message: 'foo' };
const message = { text: 'foo' };
manager.setMessage(message);
expect(manager.getMessage()).toBe(message);
@@ -32,7 +32,7 @@ describe('OverlayMessageManager', () => {
it('should reset message', () => {
const manager = new OverlayMessageManager(api);
manager.setMessage({ message: 'foo' });
manager.setMessage({ text: 'foo' });
vi.clearAllMocks();
manager.reset();
@@ -105,7 +105,54 @@ describe('GalleryCoreController', () => {
).toBeCalledWith(sentinel);
});
it('should not observe when sentinel is null', () => {
it('should disconnect when sentinel changes to null', () => {
const sentinel = document.createElement('div');
let currentSentinel: HTMLElement | null = sentinel;
const getSentinelBottom = vi.fn(() => currentSentinel);
const host = createLitElement();
const controller = createController({
host,
getSentinelBottom,
});
// First call with a real sentinel.
controller.hostUpdated();
currentSentinel = null;
controller.hostUpdated();
expect(
vi.mocked(IntersectionObserver).mock.results[0].value.disconnect,
).toBeCalledTimes(2);
expect(
vi.mocked(IntersectionObserver).mock.results[0].value.observe,
).toBeCalledTimes(1);
});
it('should skip disconnect/observe when sentinel is unchanged', () => {
const sentinel = document.createElement('div');
const getSentinelBottom = vi.fn(() => sentinel);
const host = createLitElement();
const controller = createController({
host,
getSentinelBottom,
});
controller.hostUpdated();
controller.hostUpdated();
// Only called once despite two hostUpdated() calls.
expect(
vi.mocked(IntersectionObserver).mock.results[0].value.disconnect,
).toBeCalledTimes(1);
expect(
vi.mocked(IntersectionObserver).mock.results[0].value.observe,
).toBeCalledTimes(1);
});
it('should not observe when sentinel is null from the start', () => {
const getSentinelBottom = vi.fn(() => null);
const host = createLitElement();
@@ -118,7 +165,7 @@ describe('GalleryCoreController', () => {
expect(
vi.mocked(IntersectionObserver).mock.results[0].value.disconnect,
).toBeCalled();
).not.toBeCalled();
expect(
vi.mocked(IntersectionObserver).mock.results[0].value.observe,
).not.toBeCalled();
+2 -2
View File
@@ -311,7 +311,7 @@ describe('getChildrenFromElement', () => {
describe('recursivelyMergeObjectsNotArrays', () => {
it('should recursively merge objects but replace arrays', () => {
expect(
recursivelyMergeObjectsNotArrays(
recursivelyMergeObjectsNotArrays<Record<string, unknown>>(
{},
{
a: {
@@ -358,7 +358,7 @@ describe('recursivelyMergeObjectsNotArrays', () => {
describe('recursivelyMergeObjectsConcatenatingArraysUniquely', () => {
it('should recursively merge objects but uniquely concat arrays', () => {
expect(
recursivelyMergeObjectsConcatenatingArraysUniquely(
recursivelyMergeObjectsConcatenatingArraysUniquely<Record<string, unknown>>(
{},
{
a: {
-16
View File
@@ -2044,13 +2044,6 @@ __metadata:
languageName: node
linkType: hard
"@types/bluebird@npm:^3.5.42":
version: 3.5.42
resolution: "@types/bluebird@npm:3.5.42"
checksum: 10c0/ce752a5e277bbc0cdee3dee9c875ac093d1331905a8a5175665ccb8cb76b7b95a84836f92bc46076c2fd1e384a107866893eeeb20f6fb33427482663879faf93
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:1.0.5, @types/estree@npm:^1.0.0":
version: 1.0.5
resolution: "@types/estree@npm:1.0.5"
@@ -2493,7 +2486,6 @@ __metadata:
"@rollup/plugin-terser": "npm:^0.4.4"
"@rollup/plugin-typescript": "npm:^11.1.6"
"@semantic-release/github": "npm:^10.3.3"
"@types/bluebird": "npm:^3.5.42"
"@types/js-yaml": "npm:^4"
"@types/lodash-es": "npm:^4.17.12"
"@types/masonry-layout": "npm:^4.2.8"
@@ -2504,7 +2496,6 @@ __metadata:
component-emitter: "npm:^1.3.1"
compute-scroll-into-view: "npm:^3.1.1"
conventional-changelog-conventionalcommits: "npm:^8.0.0"
crypto: "npm:^1.0.1"
date-fns: "npm:^3.6.0"
date-fns-tz: "npm:^3.1.3"
docsify-cli: "npm:^4.4.4"
@@ -3802,13 +3793,6 @@ __metadata:
languageName: node
linkType: hard
"crypto@npm:^1.0.1":
version: 1.0.1
resolution: "crypto@npm:1.0.1"
checksum: 10c0/fcf7dbd68ac5415b7fde7d7208fe203038e92e83e8a8fcf6e86ab4771ce3dd026d6967a990ba56b9d1c771378210814d5c90d907d3739fbd1723d552ad6c8ab8
languageName: node
linkType: hard
"css-declaration-sorter@npm:^7.2.0":
version: 7.2.0
resolution: "css-declaration-sorter@npm:7.2.0"