perf: Variety of small type and performance fixes (#2367)
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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';
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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(
|
// The config is cloned here because Zod 4 returns shared constant
|
||||||
setProfiles(
|
// defaults by reference. Since setProfiles() mutates the configuration
|
||||||
inputConfig,
|
// in-place, those mutations would "pollute" the global defaults and break
|
||||||
|
// test isolation if we didn't use a fresh clone here.
|
||||||
// The config is cloned here because Zod 4 returns shared constant
|
const config = setProfiles(
|
||||||
// defaults by reference. Since setProfiles() mutates the configuration
|
inputConfig,
|
||||||
// in-place, those mutations would "pollute" the global defaults and break
|
copyConfig(parseResult.data),
|
||||||
// test isolation if we didn't use a fresh clone here.
|
parseResult.data.profiles,
|
||||||
copyConfig(parseResult.data),
|
|
||||||
|
|
||||||
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,14 +100,21 @@ 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();
|
||||||
this._intersectionObserver.disconnect();
|
|
||||||
|
|
||||||
if (sentinel) {
|
// Avoid redundant observer disconnect/reconnect on every Lit update cycle
|
||||||
this._intersectionObserver.observe(sentinel);
|
// 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 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();
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user