perf: Variety of small type and performance fixes (#2367)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
return 'medium';
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
copyConfig(parseResult.data),
|
||||
|
||||
parseResult.data.profiles,
|
||||
),
|
||||
// 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,14 +100,21 @@ 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();
|
||||
this._intersectionObserver.disconnect();
|
||||
|
||||
if (sentinel) {
|
||||
this._intersectionObserver.observe(sentinel);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user