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