Add initial keyboard shortcut support.
This commit is contained in:
+38
-62
@@ -9,13 +9,10 @@ import {
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { CachedValueController } from '../components-lib/cached-value-controller.js';
|
||||
import { ZoomDefault } from '../components-lib/zoom/types.js';
|
||||
import { handleZoomDefaultEvent } from '../components-lib/zoom/zoom-view-context.js';
|
||||
import { CameraConfig, ImageViewConfig } from '../config/types.js';
|
||||
import defaultImage from '../images/frigate-bird-in-sky.jpg';
|
||||
import { localize } from '../localize/localize.js';
|
||||
@@ -31,6 +28,7 @@ import {
|
||||
} from '../utils/media-info.js';
|
||||
import { View } from '../view/view.js';
|
||||
import { dispatchErrorMessageEvent } from './message.js';
|
||||
import { CameraManager } from '../camera-manager/manager.js';
|
||||
|
||||
// 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;
|
||||
@@ -46,6 +44,9 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
|
||||
@property({ attribute: false })
|
||||
public cameraConfig?: CameraConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
|
||||
// Using contentsChanged to ensure overridden configs (e.g. when the
|
||||
// 'show_image_during_load' option is true for live views, an overridden
|
||||
// config may be used here).
|
||||
@@ -155,10 +156,6 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
|
||||
() => dispatchMediaPauseEvent(this),
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProps.has('imageConfig') && this.imageConfig?.zoomable) {
|
||||
import('./zoomer.js');
|
||||
}
|
||||
}
|
||||
|
||||
// If the camera or view changed, immediately discard the old value (view to
|
||||
@@ -279,67 +276,46 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
|
||||
}
|
||||
}
|
||||
|
||||
protected _useZoomIfRequired(template: TemplateResult): TemplateResult {
|
||||
const targetID = this.cameraConfig?.id;
|
||||
const zoomConfig = targetID ? this.view?.context?.zoom?.[targetID]?.zoom : undefined;
|
||||
|
||||
return this.imageConfig?.zoomable
|
||||
? html` <frigate-card-zoomer
|
||||
.defaultConfig=${guard([this.cameraConfig?.dimensions?.layout], () =>
|
||||
this.cameraConfig?.dimensions?.layout
|
||||
? {
|
||||
pan: this.cameraConfig.dimensions.layout.pan,
|
||||
zoom: this.cameraConfig.dimensions.layout.zoom,
|
||||
}
|
||||
: undefined,
|
||||
)}
|
||||
.config=${zoomConfig}
|
||||
@frigate-card:zoom:default=${(ev: CustomEvent<ZoomDefault>) =>
|
||||
handleZoomDefaultEvent(this, ev, targetID)}
|
||||
>
|
||||
${template}
|
||||
</frigate-card-zoomer>`
|
||||
: template;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const src = this._cachedValueController?.value;
|
||||
// Note the use of live() below to ensure the update will restore the image
|
||||
// src if it's been changed via _forceSafeImage().
|
||||
return src
|
||||
? this._useZoomIfRequired(html` <img
|
||||
${ref(this._refImage)}
|
||||
src=${live(src)}
|
||||
@load=${(ev: Event) => {
|
||||
const mediaLoadedInfo = createMediaLoadedInfo(ev, {
|
||||
player: this,
|
||||
capabilities: {
|
||||
supportsPause: !!this.imageConfig?.refresh_seconds,
|
||||
},
|
||||
});
|
||||
// Avoid the media being reported as repeatedly loading unless the
|
||||
// media info changes.
|
||||
if (mediaLoadedInfo && !isEqual(this._mediaLoadedInfo, mediaLoadedInfo)) {
|
||||
this._mediaLoadedInfo = mediaLoadedInfo;
|
||||
dispatchExistingMediaLoadedInfoAsEvent(this, mediaLoadedInfo);
|
||||
}
|
||||
}}
|
||||
@error=${() => {
|
||||
if (this.imageConfig?.mode === 'camera') {
|
||||
// In camera mode, the user has likely not made an error, but HA
|
||||
// may be unavailble, so show the stock image. Don't let the URL
|
||||
// override the stock image in this case, as this could create an
|
||||
// error loop if that URL subsequently failed to load.
|
||||
this._forceSafeImage(true);
|
||||
} else if (this.imageConfig?.mode === 'url') {
|
||||
// In url mode, the user likely specified a URL that cannot be
|
||||
// resolved. Show an error message.
|
||||
dispatchErrorMessageEvent(this, localize('error.image_load_error'), {
|
||||
context: this.imageConfig,
|
||||
? html`
|
||||
<img
|
||||
${ref(this._refImage)}
|
||||
src=${live(src)}
|
||||
@load=${(ev: Event) => {
|
||||
const mediaLoadedInfo = createMediaLoadedInfo(ev, {
|
||||
player: this,
|
||||
capabilities: {
|
||||
supportsPause: !!this.imageConfig?.refresh_seconds,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>`)
|
||||
// Avoid the media being reported as repeatedly loading unless the
|
||||
// media info changes.
|
||||
if (mediaLoadedInfo && !isEqual(this._mediaLoadedInfo, mediaLoadedInfo)) {
|
||||
this._mediaLoadedInfo = mediaLoadedInfo;
|
||||
dispatchExistingMediaLoadedInfoAsEvent(this, mediaLoadedInfo);
|
||||
}
|
||||
}}
|
||||
@error=${() => {
|
||||
if (this.imageConfig?.mode === 'camera') {
|
||||
// In camera mode, the user has likely not made an error, but HA
|
||||
// may be unavailble, so show the stock image. Don't let the URL
|
||||
// override the stock image in this case, as this could create an
|
||||
// error loop if that URL subsequently failed to load.
|
||||
this._forceSafeImage(true);
|
||||
} else if (this.imageConfig?.mode === 'url') {
|
||||
// In url mode, the user likely specified a URL that cannot be
|
||||
// resolved. Show an error message.
|
||||
dispatchErrorMessageEvent(this, localize('error.image_load_error'), {
|
||||
context: this.imageConfig,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
`
|
||||
: html``;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { KeyAssignerController } from '../components-lib/key-assigner-controller';
|
||||
import { KeyboardShortcut } from '../config/keyboard-shortcuts';
|
||||
import keyAssignerStyle from '../scss/key-assigner.scss';
|
||||
import { localize } from '../localize/localize';
|
||||
|
||||
@customElement('frigate-card-key-assigner')
|
||||
export class FrigateCardKeyAssigner extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public label?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public value?: KeyboardShortcut | null;
|
||||
|
||||
protected _controller = new KeyAssignerController(this);
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has('value')) {
|
||||
this._controller.setValue(this.value ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.label) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderKey = (key: string) => {
|
||||
return html`<div class="key">
|
||||
<div class="key-inner">${key}</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="label">${this.label}</div>
|
||||
<ha-button
|
||||
class="assign"
|
||||
@click=${() => {
|
||||
this._controller.toggleAssigning();
|
||||
}}
|
||||
>
|
||||
<ha-icon icon="mdi:keyboard-settings"></ha-icon>
|
||||
<span class="${classMap({
|
||||
dotdotdot: this._controller.isAssigning(),
|
||||
})}">
|
||||
${
|
||||
this._controller.isAssigning()
|
||||
? localize('key_assigner.assigning')
|
||||
: localize('key_assigner.assign')
|
||||
}
|
||||
</span>
|
||||
</ha-button>
|
||||
${
|
||||
this._controller.hasValue()
|
||||
? html`<ha-button
|
||||
@click=${() => {
|
||||
this._controller.setValue(null);
|
||||
}}
|
||||
>
|
||||
<ha-icon icon="mdi:keyboard-off"></ha-icon>
|
||||
<span> ${localize('key_assigner.unassign')} </span>
|
||||
</ha-button>`
|
||||
: ''
|
||||
}
|
||||
<div class="key-row">
|
||||
${this.value?.ctrl ? renderKey(localize('key_assigner.modifiers.ctrl')) : ''}
|
||||
${this.value?.shift ? renderKey(localize('key_assigner.modifiers.shift')) : ''}
|
||||
${this.value?.meta ? renderKey(localize('key_assigner.modifiers.meta')) : ''}
|
||||
${this.value?.alt ? renderKey(localize('key_assigner.modifiers.alt')) : ''}
|
||||
${this.value?.key ? renderKey(this.value.key) : ''}
|
||||
</div>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(keyAssignerStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'frigate-card-key-assigner': FrigateCardKeyAssigner;
|
||||
}
|
||||
}
|
||||
+27
-23
@@ -21,8 +21,6 @@ import {
|
||||
import { ReadonlyMicrophoneManager } from '../../card-controller/microphone-manager.js';
|
||||
import { LiveController } from '../../components-lib/live/live-controller.js';
|
||||
import { MediaGridSelected } from '../../components-lib/media-grid-controller.js';
|
||||
import { ZoomConfig, ZoomDefault } from '../../components-lib/zoom/types.js';
|
||||
import { handleZoomDefaultEvent } from '../../components-lib/zoom/zoom-view-context.js';
|
||||
import {
|
||||
CameraConfig,
|
||||
CardWideConfig,
|
||||
@@ -62,6 +60,12 @@ import {
|
||||
FrigateCardTitleControl,
|
||||
getDefaultTitleConfigForView,
|
||||
} from '../title-control.js';
|
||||
import {
|
||||
PartialZoomSettings,
|
||||
ZoomSettingsObserved,
|
||||
} from '../../components-lib/zoom/types.js';
|
||||
import { handleZoomSettingsObservedEvent } from '../../components-lib/zoom/zoom-view-context.js';
|
||||
import { getStreamCameraID } from '../../utils/substream.js';
|
||||
|
||||
const FRIGATE_CARD_LIVE_PROVIDER = 'frigate-card-live-provider';
|
||||
|
||||
@@ -503,9 +507,9 @@ export class FrigateCardLiveCarousel extends LitElement {
|
||||
.liveConfig=${liveConfig}
|
||||
.hass=${this.hass}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.zoomConfig=${this.view?.context?.zoom?.[cameraID]?.zoom}
|
||||
@frigate-card:zoom:default=${(ev: CustomEvent<ZoomDefault>) =>
|
||||
handleZoomDefaultEvent(this, ev, cameraID)}
|
||||
.zoomSettings=${this.view?.context?.zoom?.[cameraID]?.requested}
|
||||
@frigate-card:zoom:change=${(ev: CustomEvent<ZoomSettingsObserved>) =>
|
||||
handleZoomSettingsObservedEvent(this, ev, cameraID)}
|
||||
>
|
||||
</frigate-card-live-provider>
|
||||
</div>
|
||||
@@ -633,11 +637,11 @@ export class FrigateCardLiveCarousel extends LitElement {
|
||||
</frigate-card-next-previous-control>
|
||||
</frigate-card-carousel>
|
||||
<frigate-card-ptz
|
||||
.hass=${this.hass}
|
||||
.config=${this.overriddenLiveConfig.controls.ptz}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.cameraID=${cameraID}
|
||||
.forceVisibility=${this._mediaHasLoaded && this.view.context?.live?.ptzVisible}
|
||||
.cameraID=${getStreamCameraID(this.view, cameraID)}
|
||||
.forceVisibility=${this._mediaHasLoaded &&
|
||||
this.view.context?.ptzControls?.enabled}
|
||||
>
|
||||
</frigate-card-ptz>
|
||||
${cameraMetadataCurrent && titleConfig
|
||||
@@ -694,7 +698,7 @@ export class FrigateCardLiveProvider
|
||||
public microphoneStream?: MediaStream;
|
||||
|
||||
@property({ attribute: false })
|
||||
public zoomConfig?: ZoomConfig | null;
|
||||
public zoomSettings?: PartialZoomSettings | null;
|
||||
|
||||
@state()
|
||||
protected _isVideoMediaLoaded = false;
|
||||
@@ -857,7 +861,7 @@ export class FrigateCardLiveProvider
|
||||
protected _useZoomIfRequired(template: TemplateResult): TemplateResult {
|
||||
return this.liveConfig?.zoomable
|
||||
? html` <frigate-card-zoomer
|
||||
.defaultConfig=${guard([this.cameraConfig?.dimensions?.layout], () =>
|
||||
.defaultSettings=${guard([this.cameraConfig?.dimensions?.layout], () =>
|
||||
this.cameraConfig?.dimensions?.layout
|
||||
? {
|
||||
pan: this.cameraConfig.dimensions.layout.pan,
|
||||
@@ -865,7 +869,7 @@ export class FrigateCardLiveProvider
|
||||
}
|
||||
: undefined,
|
||||
)}
|
||||
.config=${this.zoomConfig}
|
||||
.settings=${this.zoomSettings}
|
||||
@frigate-card:zoom:zoomed=${() => this.setControls(false)}
|
||||
@frigate-card:zoom:unzoomed=${() => this.setControls()}
|
||||
>
|
||||
@@ -944,17 +948,17 @@ export class FrigateCardLiveProvider
|
||||
</frigate-card-live-ha>`
|
||||
: provider === 'go2rtc'
|
||||
? html`<frigate-card-live-go2rtc
|
||||
${ref(this._refProvider)}
|
||||
class=${classMap(providerClasses)}
|
||||
.hass=${this.hass}
|
||||
.cameraConfig=${this.cameraConfig}
|
||||
.cameraEndpoints=${this.cameraEndpoints}
|
||||
.microphoneStream=${this.microphoneStream}
|
||||
.microphoneConfig=${this.liveConfig.microphone}
|
||||
?controls=${this.liveConfig.controls.builtin}
|
||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||
>
|
||||
</frigate-card-live-webrtc-card>`
|
||||
${ref(this._refProvider)}
|
||||
class=${classMap(providerClasses)}
|
||||
.hass=${this.hass}
|
||||
.cameraConfig=${this.cameraConfig}
|
||||
.cameraEndpoints=${this.cameraEndpoints}
|
||||
.microphoneStream=${this.microphoneStream}
|
||||
.microphoneConfig=${this.liveConfig.microphone}
|
||||
?controls=${this.liveConfig.controls.builtin}
|
||||
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
|
||||
>
|
||||
</frigate-card-live-go2rtc>`
|
||||
: provider === 'webrtc-card'
|
||||
? html`<frigate-card-live-webrtc-card
|
||||
${ref(this._refProvider)}
|
||||
@@ -995,7 +999,7 @@ export class FrigateCardLiveProvider
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
FRIGATE_CARD_LIVE_PROVIDER: FrigateCardLiveProvider;
|
||||
'frigate-card-live-provider': FrigateCardLiveProvider;
|
||||
'frigate-card-live-carousel': FrigateCardLiveCarousel;
|
||||
'frigate-card-live-grid': FrigateCardLiveGrid;
|
||||
'frigate-card-live': FrigateCardLive;
|
||||
|
||||
+5
-10
@@ -46,7 +46,7 @@ export class FrigateCardMenu extends LitElement {
|
||||
return html` <frigate-card-submenu
|
||||
.hass=${this.hass}
|
||||
.submenu=${button}
|
||||
@action=${(ev) => this.hass && this._controller.actionHandler(this.hass, ev)}
|
||||
@action=${(ev) => this._controller.actionHandler(ev)}
|
||||
>
|
||||
</frigate-card-submenu>`;
|
||||
} else if (button.type === 'custom:frigate-card-menu-submenu-select') {
|
||||
@@ -54,7 +54,7 @@ export class FrigateCardMenu extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.submenuSelect=${button}
|
||||
.entityRegistryManager=${this.entityRegistryManager}
|
||||
@action=${(ev) => this.hass && this._controller.actionHandler(this.hass, ev)}
|
||||
@action=${(ev) => this._controller.actionHandler(ev)}
|
||||
>
|
||||
</frigate-card-submenu-select>`;
|
||||
}
|
||||
@@ -84,8 +84,7 @@ export class FrigateCardMenu extends LitElement {
|
||||
hasDoubleClick: frigateCardHasAction(button.double_tap_action),
|
||||
})}
|
||||
.label=${buttonState.title || ''}
|
||||
@action=${(ev) =>
|
||||
this.hass && this._controller.actionHandler(this.hass, ev, button)}
|
||||
@action=${(ev) => this._controller.actionHandler(ev, button)}
|
||||
>
|
||||
${svgPath
|
||||
? html`<ha-svg-icon .path="${svgPath}"></ha-svg-icon>`
|
||||
@@ -106,17 +105,13 @@ export class FrigateCardMenu extends LitElement {
|
||||
|
||||
return html` <div
|
||||
class="matching"
|
||||
style="${styleMap({
|
||||
flex: String(matchingButtons.length),
|
||||
})}"
|
||||
style="${styleMap({ flex: String(matchingButtons.length) })}"
|
||||
>
|
||||
${matchingButtons.map((button) => this._renderButton(button))}
|
||||
</div>
|
||||
<div
|
||||
class="opposing"
|
||||
style="${styleMap({
|
||||
flex: String(opposingButtons.length),
|
||||
})}"
|
||||
style="${styleMap({ flex: String(opposingButtons.length) })}"
|
||||
>
|
||||
${opposingButtons.map((button) => this._renderButton(button))}
|
||||
</div>`;
|
||||
|
||||
+38
-52
@@ -1,18 +1,19 @@
|
||||
import { HASSDomEvent, HomeAssistant } from '@dermotduffy/custom-card-helpers';
|
||||
import { HASSDomEvent } from '@dermotduffy/custom-card-helpers';
|
||||
import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS
|
||||
html,
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { actionHandler } from '../action-handler-directive.js';
|
||||
import { CameraManager } from '../camera-manager/manager.js';
|
||||
import { PTZController } from '../components-lib/ptz-controller.js';
|
||||
import { Actions, FrigateCardPTZConfig } from '../config/types.js';
|
||||
import { PTZController } from '../components-lib/ptz/ptz-controller.js';
|
||||
import { PTZActionPresence } from '../components-lib/ptz/types.js';
|
||||
import { Actions, PTZControlsConfig } from '../config/types.js';
|
||||
import { localize } from '../localize/localize.js';
|
||||
import ptzStyle from '../scss/ptz.scss';
|
||||
import { frigateCardHasAction } from '../utils/action.js';
|
||||
@@ -20,10 +21,7 @@ import { frigateCardHasAction } from '../utils/action.js';
|
||||
@customElement('frigate-card-ptz')
|
||||
export class FrigateCardPTZ extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public config?: FrigateCardPTZConfig;
|
||||
public config?: PTZControlsConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
@@ -35,20 +33,22 @@ export class FrigateCardPTZ extends LitElement {
|
||||
public forceVisibility?: boolean;
|
||||
|
||||
protected _controller = new PTZController(this);
|
||||
protected _actions = this._controller.getPTZActions();
|
||||
protected _actionPresence: PTZActionPresence | null = null;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has('config')) {
|
||||
this._controller.setConfig(this.config);
|
||||
}
|
||||
if (changedProps.has('hass')) {
|
||||
this._controller.setHASS(this.hass);
|
||||
}
|
||||
if (changedProps.has('cameraManager') || changedProps.has('cameraID')) {
|
||||
this._controller.setCamera(this.cameraManager, this.cameraID);
|
||||
}
|
||||
if (changedProps.has('forceVisibility')) {
|
||||
this._controller.setForceVisibility(this.forceVisibility);
|
||||
}
|
||||
if (changedProps.has('cameraID') || changedProps.has('cameraManager')) {
|
||||
this._actionPresence = this._controller.hasUsefulAction();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@@ -59,62 +59,48 @@ export class FrigateCardPTZ extends LitElement {
|
||||
const renderIcon = (
|
||||
name: string,
|
||||
icon: string,
|
||||
actions: Actions | null,
|
||||
actions?: Actions | null,
|
||||
): TemplateResult => {
|
||||
const classes = {
|
||||
[name]: true,
|
||||
disabled: !actions,
|
||||
};
|
||||
|
||||
return html`<ha-icon
|
||||
class=${classMap(classes)}
|
||||
icon=${icon}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: frigateCardHasAction(actions?.hold_action),
|
||||
hasDoubleClick: frigateCardHasAction(actions?.double_tap_action),
|
||||
})}
|
||||
.title=${localize(`elements.ptz.${name}`)}
|
||||
@action=${(ev: HASSDomEvent<{ action: string }>) =>
|
||||
this._controller.handleAction(ev, actions)}
|
||||
></ha-icon>`;
|
||||
return actions
|
||||
? html`<ha-icon
|
||||
class=${classMap(classes)}
|
||||
icon=${icon}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: frigateCardHasAction(actions?.hold_action),
|
||||
hasDoubleClick: frigateCardHasAction(actions?.double_tap_action),
|
||||
})}
|
||||
.title=${localize(`elements.ptz.${name}`)}
|
||||
@action=${(ev: HASSDomEvent<{ action: string }>) =>
|
||||
this._controller.handleAction(ev, actions)}
|
||||
></ha-icon>`
|
||||
: html``;
|
||||
};
|
||||
|
||||
const config = this._controller.getConfig();
|
||||
const actionsZoomIn = this._controller.getPTZActions('zoom_in');
|
||||
const actionsZoomOut = this._controller.getPTZActions('zoom_out');
|
||||
const actionsHome = this._controller.getPTZActions('home');
|
||||
|
||||
return html` <div class="ptz">
|
||||
${!config?.hide_pan_tilt
|
||||
${!config?.hide_pan_tilt && this._actionPresence?.pt
|
||||
? html`<div class="ptz-move">
|
||||
${renderIcon(
|
||||
'right',
|
||||
'mdi:arrow-right',
|
||||
this._controller.getPTZActions('right'),
|
||||
)}
|
||||
${renderIcon(
|
||||
'left',
|
||||
'mdi:arrow-left',
|
||||
this._controller.getPTZActions('left'),
|
||||
)}
|
||||
${renderIcon('up', 'mdi:arrow-up', this._controller.getPTZActions('up'))}
|
||||
${renderIcon(
|
||||
'down',
|
||||
'mdi:arrow-down',
|
||||
this._controller.getPTZActions('down'),
|
||||
)}
|
||||
${renderIcon('right', 'mdi:arrow-right', this._actions.right)}
|
||||
${renderIcon('left', 'mdi:arrow-left', this._actions.left)}
|
||||
${renderIcon('up', 'mdi:arrow-up', this._actions.up)}
|
||||
${renderIcon('down', 'mdi:arrow-down', this._actions.down)}
|
||||
</div>`
|
||||
: ''}
|
||||
${!config?.hide_zoom && (actionsZoomIn || actionsZoomOut)
|
||||
${!config?.hide_zoom && this._actionPresence?.z
|
||||
? html` <div class="ptz-zoom">
|
||||
${renderIcon('zoom_in', 'mdi:plus', actionsZoomIn)}
|
||||
${renderIcon('zoom_out', 'mdi:minus', actionsZoomOut)}
|
||||
${renderIcon('zoom_in', 'mdi:plus', this._actions.zoom_in)}
|
||||
${renderIcon('zoom_out', 'mdi:minus', this._actions.zoom_out)}
|
||||
</div>`
|
||||
: html``}
|
||||
${!config?.hide_home && actionsHome
|
||||
? html`
|
||||
<div class="ptz-home">${renderIcon('home', 'mdi:home', actionsHome)}</div>
|
||||
`
|
||||
${!config?.hide_home && this._actionPresence?.home
|
||||
? html`<div class="ptz-home">
|
||||
${renderIcon('home', 'mdi:home', this._actions.home)}
|
||||
</div>`
|
||||
: html``}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||
import { CameraManager } from '../camera-manager/manager.js';
|
||||
import { MediaGridSelected } from '../components-lib/media-grid-controller.js';
|
||||
import { ZoomDefault } from '../components-lib/zoom/types.js';
|
||||
import { handleZoomDefaultEvent } from '../components-lib/zoom/zoom-view-context.js';
|
||||
import { ZoomSettingsObserved } from '../components-lib/zoom/types.js';
|
||||
import { handleZoomSettingsObservedEvent } from '../components-lib/zoom/zoom-view-context.js';
|
||||
import {
|
||||
dispatchMessageEvent,
|
||||
renderMessage,
|
||||
@@ -76,6 +76,7 @@ import { VideoContentType, ViewMedia } from '../view/media.js';
|
||||
import { View } from '../view/view.js';
|
||||
import type { EmblaCarouselPlugins } from './carousel.js';
|
||||
import './next-prev-control.js';
|
||||
import './ptz';
|
||||
import './surround.js';
|
||||
import './title-control.js';
|
||||
import {
|
||||
@@ -501,6 +502,13 @@ export class FrigateCardViewerCarousel extends LitElement {
|
||||
}}
|
||||
></frigate-card-next-previous-control>
|
||||
</frigate-card-carousel>
|
||||
${this.view
|
||||
? html` <frigate-card-ptz
|
||||
.config=${this.viewerConfig?.controls.ptz}
|
||||
.forceVisibility=${this.view?.context?.ptzControls?.enabled}
|
||||
>
|
||||
</frigate-card-ptz>`
|
||||
: ''}
|
||||
<div class="seek-warning">
|
||||
<ha-icon title="${localize('media_viewer.unseekable')}" icon="mdi:clock-remove">
|
||||
</ha-icon>
|
||||
@@ -876,7 +884,7 @@ export class FrigateCardViewerProvider
|
||||
|
||||
return this.viewerConfig?.zoomable
|
||||
? html` <frigate-card-zoomer
|
||||
.defaultConfig=${guard([cameraConfig?.dimensions?.layout], () =>
|
||||
.defaultSettings=${guard([cameraConfig?.dimensions?.layout], () =>
|
||||
cameraConfig?.dimensions?.layout
|
||||
? {
|
||||
pan: cameraConfig.dimensions.layout.pan,
|
||||
@@ -884,11 +892,13 @@ export class FrigateCardViewerProvider
|
||||
}
|
||||
: undefined,
|
||||
)}
|
||||
.config=${mediaID ? this.view?.context?.zoom?.[mediaID]?.zoom : undefined}
|
||||
.settings=${mediaID
|
||||
? this.view?.context?.zoom?.[mediaID]?.requested
|
||||
: undefined}
|
||||
@frigate-card:zoom:zoomed=${() => this.setControls(false)}
|
||||
@frigate-card:zoom:unzoomed=${() => this.setControls()}
|
||||
@frigate-card:zoom:default=${(ev: CustomEvent<ZoomDefault>) =>
|
||||
handleZoomDefaultEvent(this, ev, mediaID)}
|
||||
@frigate-card:zoom:change=${(ev: CustomEvent<ZoomSettingsObserved>) =>
|
||||
handleZoomSettingsObservedEvent(this, ev, mediaID)}
|
||||
>
|
||||
${template}
|
||||
</frigate-card-zoomer>`
|
||||
@@ -995,6 +1005,6 @@ declare global {
|
||||
'frigate-card-viewer-carousel': FrigateCardViewerCarousel;
|
||||
'frigate-card-viewer': FrigateCardViewer;
|
||||
'frigate-card-viewer-grid': FrigateCardViewerGrid;
|
||||
FRIGATE_CARD_VIEWER_PROVIDER: FrigateCardViewerProvider;
|
||||
'frigate-card-viewer-provider': FrigateCardViewerProvider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export class FrigateCardViews extends LitElement {
|
||||
.view=${this.view}
|
||||
.hass=${this.hass}
|
||||
.cameraConfig=${cameraConfig}
|
||||
.supportZoom=${true}
|
||||
.cameraManager=${this.cameraManager}
|
||||
>
|
||||
</frigate-card-image>`
|
||||
: ``}
|
||||
|
||||
@@ -7,19 +7,19 @@ import {
|
||||
TemplateResult,
|
||||
} from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { ZoomConfig } from '../components-lib/zoom/types.js';
|
||||
import { ZoomController } from '../components-lib/zoom/zoom-controller.js';
|
||||
import { setOrRemoveAttribute } from '../utils/basic.js';
|
||||
import { PartialZoomSettings } from '../components-lib/zoom/types.js';
|
||||
|
||||
@customElement('frigate-card-zoomer')
|
||||
export class FrigateCardZoomer extends LitElement {
|
||||
protected _zoom: ZoomController | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
public defaultConfig?: ZoomConfig;
|
||||
public defaultSettings?: PartialZoomSettings;
|
||||
|
||||
@property({ attribute: false })
|
||||
public config?: ZoomConfig | null;
|
||||
public settings?: PartialZoomSettings | null;
|
||||
|
||||
@state()
|
||||
protected _zoomed = false;
|
||||
@@ -48,19 +48,19 @@ export class FrigateCardZoomer extends LitElement {
|
||||
}
|
||||
|
||||
if (this._zoom) {
|
||||
if (changedProps.has('defaultConfig')) {
|
||||
this._zoom.setDefaultConfig(this.defaultConfig ?? null);
|
||||
if (changedProps.has('defaultSettings')) {
|
||||
this._zoom.setDefaultSettings(this.defaultSettings ?? null);
|
||||
}
|
||||
// If config is null, make no change to the zoom.
|
||||
if (changedProps.has('config') && this.config) {
|
||||
this._zoom.setConfig(this.config);
|
||||
if (changedProps.has('settings') && this.settings) {
|
||||
this._zoom.setSettings(this.settings);
|
||||
}
|
||||
} else {
|
||||
// Ensure that the configuration will be set before activation (vs
|
||||
// activating in `connectedCallback`).
|
||||
this._zoom = new ZoomController(this, {
|
||||
config: this.config,
|
||||
defaultConfig: this.defaultConfig,
|
||||
config: this.settings,
|
||||
defaultConfig: this.defaultSettings,
|
||||
});
|
||||
this._zoom.activate();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user