@@ -16,6 +16,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cycjimmy/jsmpeg-player": "^6.0.4",
|
"@cycjimmy/jsmpeg-player": "^6.0.4",
|
||||||
|
"@dermotduffy/panzoom": "^4.5.1",
|
||||||
"@egjs/hammerjs": "^2.0.17",
|
"@egjs/hammerjs": "^2.0.17",
|
||||||
"@graphiteds/core": "^1.9.6",
|
"@graphiteds/core": "^1.9.6",
|
||||||
"@lit-labs/scoped-registry-mixin": "^1.0.1",
|
"@lit-labs/scoped-registry-mixin": "^1.0.1",
|
||||||
|
|||||||
+12
-2
@@ -26,6 +26,7 @@ import { View } from '../view/view.js';
|
|||||||
import { dispatchErrorMessageEvent } from './message.js';
|
import { dispatchErrorMessageEvent } from './message.js';
|
||||||
import { contentsChanged } from '../utils/basic.js';
|
import { contentsChanged } from '../utils/basic.js';
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
|
import './zoomer.js';
|
||||||
|
|
||||||
// See TOKEN_CHANGE_INTERVAL in https://github.com/home-assistant/core/blob/dev/homeassistant/components/camera/__init__.py .
|
// See TOKEN_CHANGE_INTERVAL in https://github.com/home-assistant/core/blob/dev/homeassistant/components/camera/__init__.py .
|
||||||
const HASS_REJECTION_CUTOFF_MS = 5 * 60 * 1000;
|
const HASS_REJECTION_CUTOFF_MS = 5 * 60 * 1000;
|
||||||
@@ -41,6 +42,9 @@ export class FrigateCardImage extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public cameraConfig?: CameraConfig;
|
public cameraConfig?: CameraConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public supportZoom = false;
|
||||||
|
|
||||||
// Using contentsChanged to ensure overridden configs (e.g. when the
|
// Using contentsChanged to ensure overridden configs (e.g. when the
|
||||||
// 'show_image_during_load' option is true for live views, an overridden
|
// 'show_image_during_load' option is true for live views, an overridden
|
||||||
// config may be used here).
|
// config may be used here).
|
||||||
@@ -231,12 +235,18 @@ export class FrigateCardImage extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _renderZoom(contents: TemplateResult): TemplateResult {
|
||||||
|
return this.supportZoom
|
||||||
|
? html` <frigate-card-zoomer>${contents}</frigate-card-zoomer>`
|
||||||
|
: contents;
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
const src = this._cachedValueController?.value;
|
const src = this._cachedValueController?.value;
|
||||||
// Note the use of live() below to ensure the update will restore the image
|
// Note the use of live() below to ensure the update will restore the image
|
||||||
// src if it's been changed via _forceSafeImage().
|
// src if it's been changed via _forceSafeImage().
|
||||||
return src
|
return src
|
||||||
? html` <img
|
? this._renderZoom(html` <img
|
||||||
${ref(this._refImage)}
|
${ref(this._refImage)}
|
||||||
src=${live(src)}
|
src=${live(src)}
|
||||||
@load=${(ev: Event) => {
|
@load=${(ev: Event) => {
|
||||||
@@ -263,7 +273,7 @@ export class FrigateCardImage extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>`
|
/>`)
|
||||||
: html``;
|
: html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,12 @@ export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
if (this._player?.video) {
|
||||||
|
this._player.video.controls = controls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
disconnectedCallback(): void {
|
disconnectedCallback(): void {
|
||||||
this._player = undefined;
|
this._player = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPla
|
|||||||
this._playerRef.value?.seek(seconds);
|
this._playerRef.value?.seek(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
this._playerRef.value?.setControls(controls);
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
|
|||||||
// Not implemented.
|
// Not implemented.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
public async setControls(_controls: boolean): Promise<void> {
|
||||||
|
// Not implemented.
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || !this.cameraConfig) {
|
if (!this.hass || !this.cameraConfig) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
|||||||
// JSMPEG does not support seeking.
|
// JSMPEG does not support seeking.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
public async setControls(_controls: boolean): Promise<void> {
|
||||||
|
// Not implemented.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a JSMPEG player.
|
* Create a JSMPEG player.
|
||||||
* @param url The URL for the player to connect to.
|
* @param url The URL for the player to connect to.
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ export class FrigateCardLiveWebRTCCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
const player = this._getPlayer();
|
||||||
|
if (player) {
|
||||||
|
player.controls = controls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
|
|||||||
+66
-55
@@ -47,6 +47,7 @@ import {
|
|||||||
import '../next-prev-control.js';
|
import '../next-prev-control.js';
|
||||||
import '../title-control.js';
|
import '../title-control.js';
|
||||||
import '../surround.js';
|
import '../surround.js';
|
||||||
|
import '../zoomer.js';
|
||||||
import { CarouselSelect, EmblaCarouselPlugins } from '../carousel.js';
|
import { CarouselSelect, EmblaCarouselPlugins } from '../carousel.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js';
|
import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js';
|
||||||
@@ -772,6 +773,12 @@ export class FrigateCardLiveProvider
|
|||||||
this._refProvider.value?.seek(seconds);
|
this._refProvider.value?.seek(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
await this.updateComplete;
|
||||||
|
await this._refProvider.value?.updateComplete;
|
||||||
|
this._refProvider.value?.setControls(controls);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fully resolved live provider.
|
* Get the fully resolved live provider.
|
||||||
* @returns A live provider (that is not 'auto').
|
* @returns A live provider (that is not 'auto').
|
||||||
@@ -886,35 +893,41 @@ export class FrigateCardLiveProvider
|
|||||||
};
|
};
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${showImageDuringLoading || provider === 'image'
|
<frigate-card-zoomer
|
||||||
? html`<frigate-card-live-image
|
@frigate-card:zoom:zoomed=${() => this.setControls(false)}
|
||||||
${ref(this._refProvider)}
|
@frigate-card:zoom:unzoomed=${() => this.setControls(true)}
|
||||||
.hass=${this.hass}
|
>
|
||||||
.cameraConfig=${this.cameraConfig}
|
${showImageDuringLoading || provider === 'image'
|
||||||
@frigate-card:media:loaded=${(ev: Event) => {
|
? html`
|
||||||
if (provider === 'image') {
|
<frigate-card-live-image
|
||||||
// Only count the media has loaded if the required provider is
|
${ref(this._refProvider)}
|
||||||
// the image (not just the temporary image shown during
|
.hass=${this.hass}
|
||||||
// loading).
|
.cameraConfig=${this.cameraConfig}
|
||||||
this._videoMediaShowHandler();
|
@frigate-card:media:loaded=${(ev: Event) => {
|
||||||
} else {
|
if (provider === 'image') {
|
||||||
ev.stopPropagation();
|
// Only count the media has loaded if the required provider is
|
||||||
}
|
// the image (not just the temporary image shown during
|
||||||
}}
|
// loading).
|
||||||
>
|
this._videoMediaShowHandler();
|
||||||
</frigate-card-live-image>`
|
} else {
|
||||||
: html``}
|
ev.stopPropagation();
|
||||||
${provider === 'ha'
|
}
|
||||||
? html` <frigate-card-live-ha
|
}}
|
||||||
${ref(this._refProvider)}
|
>
|
||||||
class=${classMap(providerClasses)}
|
</frigate-card-live-image
|
||||||
.hass=${this.hass}
|
></frigate-card-zoomer>`
|
||||||
.cameraConfig=${this.cameraConfig}
|
: html``}
|
||||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
${provider === 'ha'
|
||||||
>
|
? html` <frigate-card-live-ha
|
||||||
</frigate-card-live-ha>`
|
${ref(this._refProvider)}
|
||||||
: provider === 'go2rtc'
|
class=${classMap(providerClasses)}
|
||||||
? html`<frigate-card-live-go2rtc
|
.hass=${this.hass}
|
||||||
|
.cameraConfig=${this.cameraConfig}
|
||||||
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
|
>
|
||||||
|
</frigate-card-live-ha>`
|
||||||
|
: provider === 'go2rtc'
|
||||||
|
? html`<frigate-card-live-go2rtc
|
||||||
${ref(this._refProvider)}
|
${ref(this._refProvider)}
|
||||||
class=${classMap(providerClasses)}
|
class=${classMap(providerClasses)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -923,35 +936,33 @@ export class FrigateCardLiveProvider
|
|||||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-live-webrtc-card>`
|
</frigate-card-live-webrtc-card>`
|
||||||
: provider === 'webrtc-card'
|
: provider === 'webrtc-card'
|
||||||
? html`<frigate-card-live-webrtc-card
|
? html`<frigate-card-live-webrtc-card
|
||||||
${ref(this._refProvider)}
|
${ref(this._refProvider)}
|
||||||
class=${classMap(providerClasses)}
|
class=${classMap(providerClasses)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${this.cameraConfig}
|
.cameraConfig=${this.cameraConfig}
|
||||||
.cameraEndpoints=${this.cameraEndpoints}
|
.cameraEndpoints=${this.cameraEndpoints}
|
||||||
.cardWideConfig=${this.cardWideConfig}
|
.cardWideConfig=${this.cardWideConfig}
|
||||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-live-webrtc-card>`
|
</frigate-card-live-webrtc-card>`
|
||||||
: provider === 'jsmpeg'
|
: provider === 'jsmpeg'
|
||||||
? html` <frigate-card-live-jsmpeg
|
? html` <frigate-card-live-jsmpeg
|
||||||
${ref(this._refProvider)}
|
${ref(this._refProvider)}
|
||||||
class=${classMap(providerClasses)}
|
class=${classMap(providerClasses)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${this.cameraConfig}
|
.cameraConfig=${this.cameraConfig}
|
||||||
.cameraEndpoints=${this.cameraEndpoints}
|
.cameraEndpoints=${this.cameraEndpoints}
|
||||||
.cardWideConfig=${this.cardWideConfig}
|
.cardWideConfig=${this.cardWideConfig}
|
||||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-live-jsmpeg>`
|
</frigate-card-live-jsmpeg>`
|
||||||
: html``}
|
: html``}
|
||||||
|
</frigate-card-zoomer>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get styles.
|
|
||||||
*/
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return unsafeCSS(liveProviderStyle);
|
return unsafeCSS(liveProviderStyle);
|
||||||
}
|
}
|
||||||
|
|||||||
+88
-72
@@ -9,8 +9,12 @@ import {
|
|||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
|
import { guard } from 'lit/directives/guard.js';
|
||||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||||
|
import { CameraManager } from '../camera-manager/manager.js';
|
||||||
import { dispatchMessageEvent, renderProgressIndicator } from '../components/message.js';
|
import { dispatchMessageEvent, renderProgressIndicator } from '../components/message.js';
|
||||||
|
import { localize } from '../localize/localize.js';
|
||||||
|
import '../patches/ha-hls-player';
|
||||||
import viewerCarouselStyle from '../scss/viewer-carousel.scss';
|
import viewerCarouselStyle from '../scss/viewer-carousel.scss';
|
||||||
import viewerProviderStyle from '../scss/viewer-provider.scss';
|
import viewerProviderStyle from '../scss/viewer-provider.scss';
|
||||||
import viewerStyle from '../scss/viewer.scss';
|
import viewerStyle from '../scss/viewer.scss';
|
||||||
@@ -25,38 +29,35 @@ import {
|
|||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||||
import { contentsChanged, errorToConsole } from '../utils/basic.js';
|
import { contentsChanged, errorToConsole } from '../utils/basic.js';
|
||||||
|
import { canonicalizeHAURL } from '../utils/ha/index.js';
|
||||||
import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js';
|
import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js';
|
||||||
import { View } from '../view/view.js';
|
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
||||||
|
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
||||||
|
import {
|
||||||
|
changeViewToRecentEventsForCameraAndDependents,
|
||||||
|
changeViewToRecentRecordingForCameraAndDependents,
|
||||||
|
} from '../utils/media-to-view.js';
|
||||||
|
import {
|
||||||
|
hideMediaControlsTemporarily,
|
||||||
|
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||||
|
playMediaMutingIfNecessary,
|
||||||
|
} from '../utils/media.js';
|
||||||
|
import { ViewMediaClassifier } from '../view/media-classifier';
|
||||||
import { MediaQueriesClassifier } from '../view/media-queries-classifier';
|
import { MediaQueriesClassifier } from '../view/media-queries-classifier';
|
||||||
|
import { MediaQueriesResults } from '../view/media-queries-results.js';
|
||||||
|
import { VideoContentType, ViewMedia } from '../view/media.js';
|
||||||
|
import { View } from '../view/view.js';
|
||||||
|
import './zoomer.js';
|
||||||
|
import type { CarouselSelect } from './carousel.js';
|
||||||
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
|
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
|
||||||
import { Lazyload } from './embla-plugins/lazyload.js';
|
import { Lazyload } from './embla-plugins/lazyload.js';
|
||||||
import {
|
import {
|
||||||
FrigateCardMediaCarousel,
|
FrigateCardMediaCarousel,
|
||||||
wrapMediaLoadedEventForCarousel,
|
wrapMediaLoadedEventForCarousel,
|
||||||
} from './media-carousel.js';
|
} from './media-carousel.js';
|
||||||
import type { CarouselSelect } from './carousel.js';
|
|
||||||
import './next-prev-control.js';
|
import './next-prev-control.js';
|
||||||
import './title-control.js';
|
|
||||||
import '../patches/ha-hls-player';
|
|
||||||
import './surround.js';
|
import './surround.js';
|
||||||
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
import './title-control.js';
|
||||||
import { CameraManager } from '../camera-manager/manager.js';
|
|
||||||
import {
|
|
||||||
changeViewToRecentEventsForCameraAndDependents,
|
|
||||||
changeViewToRecentRecordingForCameraAndDependents,
|
|
||||||
} from '../utils/media-to-view.js';
|
|
||||||
import { VideoContentType, ViewMedia } from '../view/media.js';
|
|
||||||
import { ViewMediaClassifier } from '../view/media-classifier';
|
|
||||||
import { guard } from 'lit/directives/guard.js';
|
|
||||||
import { localize } from '../localize/localize.js';
|
|
||||||
import { MediaQueriesResults } from '../view/media-queries-results.js';
|
|
||||||
import { canonicalizeHAURL } from '../utils/ha/index.js';
|
|
||||||
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
|
||||||
import { playMediaMutingIfNecessary } from '../utils/media.js';
|
|
||||||
import {
|
|
||||||
hideMediaControlsTemporarily,
|
|
||||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
|
||||||
} from '../utils/media.js';
|
|
||||||
|
|
||||||
export interface MediaViewerViewContext {
|
export interface MediaViewerViewContext {
|
||||||
seek?: Date;
|
seek?: Date;
|
||||||
@@ -602,6 +603,14 @@ export class FrigateCardViewerProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
if (this._refFrigateCardMediaPlayer.value) {
|
||||||
|
return this._refFrigateCardMediaPlayer.value.setControls(controls);
|
||||||
|
} else if (this._refVideoProvider.value) {
|
||||||
|
this._refVideoProvider.value.controls = controls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a clip view that matches the current (snapshot) query.
|
* Dispatch a clip view that matches the current (snapshot) query.
|
||||||
*/
|
*/
|
||||||
@@ -693,61 +702,68 @@ export class FrigateCardViewerProvider
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ViewMediaClassifier.isVideo(this.media)
|
return html`
|
||||||
? this.media.getVideoContentType() === VideoContentType.HLS
|
<frigate-card-zoomer
|
||||||
? html`<frigate-card-ha-hls-player
|
@frigate-card:zoom:zoomed=${() => this.setControls(false)}
|
||||||
${ref(this._refFrigateCardMediaPlayer)}
|
@frigate-card:zoom:unzoomed=${() => this.setControls(true)}
|
||||||
allow-exoplayer
|
>
|
||||||
aria-label="${this.media.getTitle() ?? ''}"
|
${ViewMediaClassifier.isVideo(this.media)
|
||||||
?autoplay=${false}
|
? this.media.getVideoContentType() === VideoContentType.HLS
|
||||||
controls
|
? html`<frigate-card-ha-hls-player
|
||||||
muted
|
${ref(this._refFrigateCardMediaPlayer)}
|
||||||
playsinline
|
allow-exoplayer
|
||||||
title="${this.media.getTitle() ?? ''}"
|
aria-label="${this.media.getTitle() ?? ''}"
|
||||||
url=${canonicalizeHAURL(this.hass, resolvedMedia?.url) ?? ''}
|
?autoplay=${false}
|
||||||
.hass=${this.hass}
|
controls
|
||||||
>
|
muted
|
||||||
</frigate-card-ha-hls-player>`
|
playsinline
|
||||||
: html`
|
title="${this.media.getTitle() ?? ''}"
|
||||||
<video
|
url=${canonicalizeHAURL(this.hass, resolvedMedia?.url) ?? ''}
|
||||||
${ref(this._refVideoProvider)}
|
.hass=${this.hass}
|
||||||
|
>
|
||||||
|
</frigate-card-ha-hls-player>`
|
||||||
|
: html`
|
||||||
|
<video
|
||||||
|
${ref(this._refVideoProvider)}
|
||||||
|
aria-label="${this.media.getTitle() ?? ''}"
|
||||||
|
title="${this.media.getTitle() ?? ''}"
|
||||||
|
muted
|
||||||
|
controls
|
||||||
|
playsinline
|
||||||
|
?autoplay=${false}
|
||||||
|
@loadedmetadata=${(ev: Event) => {
|
||||||
|
if (ev.target) {
|
||||||
|
hideMediaControlsTemporarily(
|
||||||
|
ev.target as HTMLVideoElement,
|
||||||
|
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
@loadeddata=${(ev: Event) => {
|
||||||
|
dispatchMediaLoadedEvent(this, ev);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<source
|
||||||
|
src=${canonicalizeHAURL(this.hass, resolvedMedia?.url) ?? ''}
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
|
</video>
|
||||||
|
`
|
||||||
|
: html`<img
|
||||||
aria-label="${this.media.getTitle() ?? ''}"
|
aria-label="${this.media.getTitle() ?? ''}"
|
||||||
|
src="${canonicalizeHAURL(this.hass, resolvedMedia?.url) ?? ''}"
|
||||||
title="${this.media.getTitle() ?? ''}"
|
title="${this.media.getTitle() ?? ''}"
|
||||||
muted
|
@click=${() => {
|
||||||
controls
|
if (this.viewerConfig?.snapshot_click_plays_clip) {
|
||||||
playsinline
|
this._dispatchRelatedClipView();
|
||||||
?autoplay=${false}
|
|
||||||
@loadedmetadata=${(ev: Event) => {
|
|
||||||
if (ev.target) {
|
|
||||||
hideMediaControlsTemporarily(
|
|
||||||
ev.target as HTMLVideoElement,
|
|
||||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@loadeddata=${(ev: Event) => {
|
@load=${(e: Event) => {
|
||||||
dispatchMediaLoadedEvent(this, ev);
|
dispatchMediaLoadedEvent(this, e);
|
||||||
}}
|
}}
|
||||||
>
|
/>`}
|
||||||
<source
|
</frigate-card-zoomer>
|
||||||
src=${canonicalizeHAURL(this.hass, resolvedMedia?.url) ?? ''}
|
`;
|
||||||
type="video/mp4"
|
|
||||||
/>
|
|
||||||
</video>
|
|
||||||
`
|
|
||||||
: html`<img
|
|
||||||
aria-label="${this.media.getTitle() ?? ''}"
|
|
||||||
src="${canonicalizeHAURL(this.hass, resolvedMedia?.url) ?? ''}"
|
|
||||||
title="${this.media.getTitle() ?? ''}"
|
|
||||||
@click=${() => {
|
|
||||||
if (this.viewerConfig?.snapshot_click_plays_clip) {
|
|
||||||
this._dispatchRelatedClipView();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
@load=${(e: Event) => {
|
|
||||||
dispatchMediaLoadedEvent(this, e);
|
|
||||||
}}
|
|
||||||
/>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
unsafeCSS
|
unsafeCSS,
|
||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
@@ -72,7 +72,7 @@ export class FrigateCardViews extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
protected shouldUpdate(_: PropertyValues): boolean {
|
protected shouldUpdate(_: PropertyValues): boolean {
|
||||||
// Future: Updates to `hass` and `conditionState` here will be frequent.
|
// Future: Updates to `hass` and `conditionState` here will be frequent.
|
||||||
@@ -150,6 +150,7 @@ export class FrigateCardViews extends LitElement {
|
|||||||
.view=${this.view}
|
.view=${this.view}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.cameraConfig=${cameraConfig}
|
.cameraConfig=${cameraConfig}
|
||||||
|
.supportZoom=${true}
|
||||||
>
|
>
|
||||||
</frigate-card-image>`
|
</frigate-card-image>`
|
||||||
: ``}
|
: ``}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from 'lit';
|
||||||
|
import { customElement } from 'lit/decorators.js';
|
||||||
|
import { Zoom } from '../utils/zoom/zoom.js';
|
||||||
|
|
||||||
|
@customElement('frigate-card-zoomer')
|
||||||
|
export class FrigateCardZoomer extends LitElement {
|
||||||
|
protected _zoom = new Zoom(this);
|
||||||
|
|
||||||
|
connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._zoom.activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback(): void {
|
||||||
|
this._zoom.deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html` <slot></slot> `;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'frigate-card-zoomer': FrigateCardZoomer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,6 +71,12 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
this._player?.seek(seconds);
|
this._player?.seek(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
if (this._player) {
|
||||||
|
this._player.setControls(controls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Master render method.
|
* Master render method.
|
||||||
* @returns A rendered template.
|
* @returns A rendered template.
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
if (this._video) {
|
||||||
|
this._video.controls = controls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
// Minor modifications from:
|
// Minor modifications from:
|
||||||
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-hls-player.ts
|
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-hls-player.ts
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { FrigateCardMediaPlayer } from '../types.js';
|
|||||||
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
||||||
import {
|
import {
|
||||||
hideMediaControlsTemporarily,
|
hideMediaControlsTemporarily,
|
||||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS
|
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||||
} from '../utils/media.js';
|
} from '../utils/media.js';
|
||||||
|
|
||||||
customElements.whenDefined('ha-web-rtc-player').then(() => {
|
customElements.whenDefined('ha-web-rtc-player').then(() => {
|
||||||
@@ -66,6 +66,12 @@ customElements.whenDefined('ha-web-rtc-player').then(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setControls(controls: boolean): Promise<void> {
|
||||||
|
if (this._video) {
|
||||||
|
this._video.controls = controls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
// Minor modifications from:
|
// Minor modifications from:
|
||||||
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-web-rtc-player.ts
|
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-web-rtc-player.ts
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1455,6 +1455,7 @@ export interface FrigateCardMediaPlayer {
|
|||||||
unmute(): Promise<void>;
|
unmute(): Promise<void>;
|
||||||
isMuted(): boolean;
|
isMuted(): boolean;
|
||||||
seek(seconds: number): Promise<void>;
|
seek(seconds: number): Promise<void>;
|
||||||
|
setControls(controls: boolean): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardHelpers {
|
export interface CardHelpers {
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { PanzoomObject, PanzoomEventDetail } from '@dermotduffy/panzoom';
|
||||||
|
import Panzoom from '@dermotduffy/panzoom';
|
||||||
|
import round from 'lodash-es/round';
|
||||||
|
import { dispatchFrigateCardEvent, isHoverableDevice } from '../basic';
|
||||||
|
|
||||||
|
export class Zoom {
|
||||||
|
constructor(element: HTMLElement) {
|
||||||
|
this._element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _element: HTMLElement;
|
||||||
|
protected _panzoom?: PanzoomObject;
|
||||||
|
protected _zoomed = false;
|
||||||
|
|
||||||
|
protected _events = isHoverableDevice()
|
||||||
|
? {
|
||||||
|
down: ['pointerdown'],
|
||||||
|
move: ['pointermove'],
|
||||||
|
up: ['pointerup', 'pointerleave', 'pointercancel'],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
down: ['touchstart'],
|
||||||
|
move: ['touchmove'],
|
||||||
|
up: ['touchend', 'touchcancel'],
|
||||||
|
};
|
||||||
|
|
||||||
|
protected _downHandler = (ev: Event) => {
|
||||||
|
if (this._shouldZoomOrPan(ev)) {
|
||||||
|
this._panzoom?.handleDown(ev as PointerEvent);
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
// If we do not prevent default here, the media carousels scroll.
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected _moveHandler = (ev: Event) => {
|
||||||
|
if (this._shouldZoomOrPan(ev)) {
|
||||||
|
this._panzoom?.handleMove(ev as PointerEvent);
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected _upHandler = (ev: Event) => {
|
||||||
|
if (this._shouldZoomOrPan(ev)) {
|
||||||
|
this._panzoom?.handleUp(ev as PointerEvent);
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected _wheelHandler = (ev: Event) => {
|
||||||
|
if (ev instanceof WheelEvent && this._shouldZoomOrPan(ev)) {
|
||||||
|
this._panzoom?.zoomWithWheel(ev);
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected _isScaleNormal(scale?: number): boolean {
|
||||||
|
// Floating point arithmetic warning: comparing floating point numbers,
|
||||||
|
// round them first.
|
||||||
|
return scale !== undefined && round(scale, 4) <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _shouldZoomOrPan(ev: Event): boolean {
|
||||||
|
return (
|
||||||
|
!this._isScaleNormal(this._panzoom?.getScale()) ||
|
||||||
|
(ev instanceof TouchEvent && ev.touches.length > 1) ||
|
||||||
|
(ev instanceof WheelEvent && ev.ctrlKey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public activate(): void {
|
||||||
|
this._panzoom = Panzoom(this._element, {
|
||||||
|
contain: 'outside',
|
||||||
|
maxScale: 10,
|
||||||
|
minScale: 1,
|
||||||
|
noBind: true,
|
||||||
|
cursor: 'auto',
|
||||||
|
});
|
||||||
|
|
||||||
|
const registerListeners = (
|
||||||
|
events: string[],
|
||||||
|
func: (ev: Event) => void,
|
||||||
|
options?: AddEventListenerOptions,
|
||||||
|
) => {
|
||||||
|
events.forEach((eventName) => {
|
||||||
|
this._element.addEventListener(eventName, func, options);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
registerListeners(this._events['down'], this._downHandler, { capture: true });
|
||||||
|
registerListeners(this._events['move'], this._moveHandler, { capture: true });
|
||||||
|
registerListeners(this._events['up'], this._upHandler, { capture: true });
|
||||||
|
registerListeners(['wheel'], this._wheelHandler);
|
||||||
|
|
||||||
|
this._element.addEventListener('panzoomzoom', (ev: Event) => {
|
||||||
|
// Take care here to only dispatch the zoomed/unzoomed events when the
|
||||||
|
// absolute state changes (rather than on every single zoom adjustment).
|
||||||
|
if (this._isScaleNormal((<CustomEvent<PanzoomEventDetail>>ev).detail.scale)) {
|
||||||
|
if (this._zoomed) {
|
||||||
|
dispatchFrigateCardEvent(this._element, 'zoom:unzoomed');
|
||||||
|
}
|
||||||
|
this._zoomed = false;
|
||||||
|
} else {
|
||||||
|
if (!this._zoomed) {
|
||||||
|
dispatchFrigateCardEvent(this._element, 'zoom:zoomed');
|
||||||
|
}
|
||||||
|
this._zoomed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public deactivate(): void {
|
||||||
|
const unregisterListener = (events: string[], func: (ev: Event) => void) => {
|
||||||
|
events.forEach((eventName) => {
|
||||||
|
this._element.removeEventListener(eventName, func);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
unregisterListener(this._events['down'], this._downHandler);
|
||||||
|
unregisterListener(this._events['move'], this._moveHandler);
|
||||||
|
unregisterListener(this._events['up'], this._upHandler);
|
||||||
|
unregisterListener(['wheel'], this._wheelHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect, vi, afterAll, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, afterAll } from 'vitest';
|
||||||
import { FrigateCardError } from '../../src/types';
|
import { FrigateCardError } from '../../src/types';
|
||||||
import {
|
import {
|
||||||
allPromises,
|
allPromises,
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
||||||
|
import { Zoom } from '../../src/utils/zoom/zoom';
|
||||||
|
import { PanzoomObject, PanzoomEventDetail } from '@dermotduffy/panzoom';
|
||||||
|
import Panzoom from '@dermotduffy/panzoom';
|
||||||
|
import { mock } from 'vitest-mock-extended';
|
||||||
|
|
||||||
|
vi.mock('@dermotduffy/panzoom');
|
||||||
|
|
||||||
|
// https://github.com/jsdom/jsdom/issues/2527
|
||||||
|
(window as any).PointerEvent = MouseEvent;
|
||||||
|
|
||||||
|
// @vitest-environment jsdom
|
||||||
|
describe('Zoom', () => {
|
||||||
|
const mediaMediSpy = vi.spyOn(window, 'matchMedia');
|
||||||
|
|
||||||
|
const createMockPanZoom = (): PanzoomObject => {
|
||||||
|
const panzoom = mock<PanzoomObject>();
|
||||||
|
panzoom.getScale.mockReturnValue(1.0);
|
||||||
|
return panzoom;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAndRegisterZoom = (element: HTMLElement): Zoom => {
|
||||||
|
const zoom = new Zoom(element);
|
||||||
|
zoom.activate();
|
||||||
|
return zoom;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createTouch = (target: HTMLElement): Touch => {
|
||||||
|
return {
|
||||||
|
clientX: 0,
|
||||||
|
clientY: 0,
|
||||||
|
force: 0,
|
||||||
|
identifier: 0,
|
||||||
|
pageX: 0,
|
||||||
|
pageY: 0,
|
||||||
|
radiusX: 0,
|
||||||
|
radiusY: 0,
|
||||||
|
rotationAngle: 0,
|
||||||
|
screenX: 0,
|
||||||
|
screenY: 0,
|
||||||
|
target: target,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mediaMediSpy.mockReturnValue(<MediaQueryList>{ matches: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be creatable', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
const zoom = new Zoom(element);
|
||||||
|
expect(zoom).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond with pointer', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
const panzoom = createMockPanZoom();
|
||||||
|
vi.mocked(Panzoom).mockReturnValueOnce(panzoom);
|
||||||
|
|
||||||
|
createAndRegisterZoom(element);
|
||||||
|
|
||||||
|
// Won't zoom without control key.
|
||||||
|
const ev_1 = new WheelEvent('wheel', { bubbles: false, deltaY: -120 });
|
||||||
|
element.dispatchEvent(ev_1);
|
||||||
|
expect(panzoom.zoomWithWheel).not.toBeCalled();
|
||||||
|
|
||||||
|
const ev_2 = new WheelEvent('wheel', {
|
||||||
|
bubbles: false,
|
||||||
|
deltaY: -120,
|
||||||
|
ctrlKey: true,
|
||||||
|
});
|
||||||
|
element.dispatchEvent(ev_2);
|
||||||
|
expect(panzoom.zoomWithWheel).toBeCalledWith(ev_2);
|
||||||
|
|
||||||
|
panzoom.getScale = vi.fn().mockReturnValue(1.2);
|
||||||
|
|
||||||
|
const ev_3 = new PointerEvent('pointerdown');
|
||||||
|
element.dispatchEvent(ev_3);
|
||||||
|
expect(panzoom.handleDown).toBeCalledWith(ev_3);
|
||||||
|
|
||||||
|
const ev_4 = new PointerEvent('pointermove');
|
||||||
|
element.dispatchEvent(ev_4);
|
||||||
|
expect(panzoom.handleMove).toBeCalledWith(ev_4);
|
||||||
|
|
||||||
|
const ev_5 = new PointerEvent('pointerup');
|
||||||
|
element.dispatchEvent(ev_5);
|
||||||
|
expect(panzoom.handleUp).toBeCalledWith(ev_5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond with touch', () => {
|
||||||
|
mediaMediSpy.mockReturnValue(<MediaQueryList>{ matches: false });
|
||||||
|
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
const panzoom = createMockPanZoom();
|
||||||
|
vi.mocked(Panzoom).mockReturnValueOnce(panzoom);
|
||||||
|
|
||||||
|
createAndRegisterZoom(element);
|
||||||
|
|
||||||
|
const ev_1 = new TouchEvent('touchstart', {
|
||||||
|
bubbles: false,
|
||||||
|
touches: [createTouch(element), createTouch(element)],
|
||||||
|
});
|
||||||
|
element.dispatchEvent(ev_1);
|
||||||
|
expect(panzoom.handleDown).toBeCalledWith(ev_1);
|
||||||
|
|
||||||
|
panzoom.getScale = vi.fn().mockReturnValue(1.2);
|
||||||
|
|
||||||
|
const ev_3 = new TouchEvent('touchstart');
|
||||||
|
element.dispatchEvent(ev_3);
|
||||||
|
expect(panzoom.handleDown).toBeCalledWith(ev_3);
|
||||||
|
|
||||||
|
const ev_4 = new TouchEvent('touchmove');
|
||||||
|
element.dispatchEvent(ev_4);
|
||||||
|
expect(panzoom.handleMove).toBeCalledWith(ev_4);
|
||||||
|
|
||||||
|
const ev_5 = new TouchEvent('touchend');
|
||||||
|
element.dispatchEvent(ev_5);
|
||||||
|
expect(panzoom.handleUp).toBeCalledWith(ev_5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deactivate should remove event handlers', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
const panzoom = createMockPanZoom();
|
||||||
|
vi.mocked(Panzoom).mockReturnValueOnce(panzoom);
|
||||||
|
|
||||||
|
createAndRegisterZoom(element).deactivate();
|
||||||
|
|
||||||
|
const ev_1 = new WheelEvent('wheel', {
|
||||||
|
bubbles: false,
|
||||||
|
deltaY: -120,
|
||||||
|
ctrlKey: true,
|
||||||
|
});
|
||||||
|
element.dispatchEvent(ev_1);
|
||||||
|
expect(panzoom.zoomWithWheel).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire frigate cards on zoom/unzoom', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
const zoomedFunc = vi.fn();
|
||||||
|
const unzoomedFunc = vi.fn();
|
||||||
|
|
||||||
|
element.addEventListener('frigate-card:zoom:zoomed', zoomedFunc);
|
||||||
|
element.addEventListener('frigate-card:zoom:unzoomed', unzoomedFunc);
|
||||||
|
|
||||||
|
const panzoom = createMockPanZoom();
|
||||||
|
vi.mocked(Panzoom).mockReturnValueOnce(panzoom);
|
||||||
|
|
||||||
|
createAndRegisterZoom(element);
|
||||||
|
|
||||||
|
const ev_1 = new CustomEvent<PanzoomEventDetail>('panzoomzoom', {
|
||||||
|
detail: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
scale: 1.2,
|
||||||
|
isSVG: false,
|
||||||
|
originalEvent: new PointerEvent('pointermove'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
element.dispatchEvent(ev_1);
|
||||||
|
expect(zoomedFunc).toBeCalled();
|
||||||
|
expect(unzoomedFunc).not.toBeCalled();
|
||||||
|
|
||||||
|
const ev_2 = new CustomEvent<PanzoomEventDetail>('panzoomzoom', {
|
||||||
|
detail: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
scale: 1,
|
||||||
|
isSVG: false,
|
||||||
|
originalEvent: new PointerEvent('pointermove'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
element.dispatchEvent(ev_2);
|
||||||
|
expect(unzoomedFunc).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -375,6 +375,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@dermotduffy/panzoom@npm:^4.5.1":
|
||||||
|
version: 4.5.1
|
||||||
|
resolution: "@dermotduffy/panzoom@npm:4.5.1"
|
||||||
|
checksum: 4c826f910425e50d9155005947e14bf344ec6b6a68783feb5a62f6bd2d7261cc541ceb18462552be271539a284a24b423dca27201caf45e153002fc5bf1c188e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@duetds/date-picker@npm:^1.4.0":
|
"@duetds/date-picker@npm:^1.4.0":
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
resolution: "@duetds/date-picker@npm:1.4.0"
|
resolution: "@duetds/date-picker@npm:1.4.0"
|
||||||
@@ -3032,6 +3039,7 @@ __metadata:
|
|||||||
"@babel/plugin-proposal-class-properties": ^7.18.6
|
"@babel/plugin-proposal-class-properties": ^7.18.6
|
||||||
"@babel/plugin-proposal-decorators": ^7.19.0
|
"@babel/plugin-proposal-decorators": ^7.19.0
|
||||||
"@cycjimmy/jsmpeg-player": ^6.0.4
|
"@cycjimmy/jsmpeg-player": ^6.0.4
|
||||||
|
"@dermotduffy/panzoom": ^4.5.1
|
||||||
"@egjs/hammerjs": ^2.0.17
|
"@egjs/hammerjs": ^2.0.17
|
||||||
"@graphiteds/core": ^1.9.6
|
"@graphiteds/core": ^1.9.6
|
||||||
"@lit-labs/scoped-registry-mixin": ^1.0.1
|
"@lit-labs/scoped-registry-mixin": ^1.0.1
|
||||||
|
|||||||
Reference in New Issue
Block a user