Initial version of status bar.
This commit is contained in:
+107
-52
@@ -20,6 +20,10 @@ import {
|
||||
MenuSubmenu,
|
||||
MenuSubmenuSelect,
|
||||
PictureElements,
|
||||
StatusBarIcon,
|
||||
StatusBarImage,
|
||||
StatusBarItem,
|
||||
StatusBarString,
|
||||
} from '../config/types.js';
|
||||
import { localize } from '../localize/localize.js';
|
||||
import elementsStyle from '../scss/elements.scss';
|
||||
@@ -164,50 +168,66 @@ export class FrigateCardElements extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public elements: PictureElements;
|
||||
|
||||
protected _boundMenuRemoveHandler = this._menuRemoveHandler.bind(this);
|
||||
|
||||
/**
|
||||
* Handle a picture element to be removed from the menu.
|
||||
* @param ev The event.
|
||||
*/
|
||||
protected _menuRemoveHandler(ev: Event): void {
|
||||
// Re-dispatch event from this element (instead of the disconnected one, as
|
||||
// there is no parent of the disconnected element).
|
||||
dispatchFrigateCardEvent<MenuItem>(this, 'menu-remove', (ev as CustomEvent).detail);
|
||||
protected _addHandler(
|
||||
target: EventTarget,
|
||||
eventName: string,
|
||||
handler: (ev: Event) => void,
|
||||
) {
|
||||
// Ensure listener is only attached 1 time by removing it first.
|
||||
target.removeEventListener(eventName, handler);
|
||||
target.addEventListener(eventName, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a picture element to be added to the menu.
|
||||
* @param ev The event.
|
||||
*/
|
||||
protected _menuAddHandler(ev: Event): void {
|
||||
protected _menuRemoveHandler = (ev: Event): void => {
|
||||
// Re-dispatch event from this element (instead of the disconnected one, as
|
||||
// there is no parent of the disconnected element).
|
||||
dispatchFrigateCardEvent<MenuItem>(this, 'menu:remove', (ev as CustomEvent).detail);
|
||||
};
|
||||
|
||||
protected _statusBarRemoveHandler = (ev: Event): void => {
|
||||
// Re-dispatch event from this element (instead of the disconnected one, as
|
||||
// there is no parent of the disconnected element).
|
||||
dispatchFrigateCardEvent<StatusBarItem>(
|
||||
this,
|
||||
'status-bar:remove',
|
||||
(ev as CustomEvent).detail,
|
||||
);
|
||||
};
|
||||
|
||||
protected _menuAddHandler = (ev: Event): void => {
|
||||
ev = ev as CustomEvent<MenuItem>;
|
||||
const path = ev.composedPath();
|
||||
if (!path.length) {
|
||||
return;
|
||||
}
|
||||
this._addHandler(path[0], 'frigate-card:menu:remove', this._menuRemoveHandler);
|
||||
};
|
||||
|
||||
// See 'A note on custom elements' above to explain what's going on here.
|
||||
|
||||
// Ensure listener is only attached 1 time by removing it first.
|
||||
path[0].removeEventListener(
|
||||
'frigate-card:menu-remove',
|
||||
this._boundMenuRemoveHandler,
|
||||
protected _statusBarAddHandler = (ev: Event): void => {
|
||||
ev = ev as CustomEvent<MenuItem>;
|
||||
const path = ev.composedPath();
|
||||
if (!path.length) {
|
||||
return;
|
||||
}
|
||||
this._addHandler(
|
||||
path[0],
|
||||
'frigate-card:status-bar:add',
|
||||
this._statusBarRemoveHandler,
|
||||
);
|
||||
|
||||
path[0].addEventListener('frigate-card:menu-remove', this._boundMenuRemoveHandler);
|
||||
}
|
||||
};
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
// Catch icons being added to the menu (so their removal can be subsequently
|
||||
// handled).
|
||||
this.addEventListener('frigate-card:menu-add', this._menuAddHandler);
|
||||
// Catch icons being added to the menu or status-bar (so their removal can
|
||||
// be subsequently handled).
|
||||
this.addEventListener('frigate-card:menu:add', this._menuAddHandler);
|
||||
this.addEventListener('frigate-card:status-bar:add', this._statusBarAddHandler);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
this.removeEventListener('frigate-card:menu-add', this._menuAddHandler);
|
||||
this.removeEventListener('frigate-card:menu:add', this._menuAddHandler);
|
||||
this.addEventListener('frigate-card:status-bar:add', this._statusBarAddHandler);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
@@ -286,59 +306,94 @@ export class FrigateCardElementsConditional extends LitElement {
|
||||
}
|
||||
|
||||
// A base class for rendering menu icons / menu state icons.
|
||||
export class FrigateCardElementsBaseMenuIcon<T> extends LitElement {
|
||||
@state()
|
||||
protected _config: T | null = null;
|
||||
export class FrigateCardElementsBaseItem<ConfigType> extends LitElement {
|
||||
protected _eventCategory: string;
|
||||
|
||||
/**
|
||||
* Set the card config.
|
||||
* @param config The configuration.
|
||||
*/
|
||||
public setConfig(config: T): void {
|
||||
constructor(eventCategory: string) {
|
||||
super();
|
||||
this._eventCategory = eventCategory;
|
||||
}
|
||||
|
||||
@state()
|
||||
protected _config: ConfigType | null = null;
|
||||
|
||||
public setConfig(config: ConfigType): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connected callback.
|
||||
*/
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this._config) {
|
||||
dispatchFrigateCardEvent<T>(this, 'menu-add', this._config);
|
||||
dispatchFrigateCardEvent<ConfigType>(
|
||||
this,
|
||||
`${this._eventCategory}:add`,
|
||||
this._config,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnected callback.
|
||||
*/
|
||||
disconnectedCallback(): void {
|
||||
if (this._config) {
|
||||
dispatchFrigateCardEvent<T>(this, 'menu-remove', this._config);
|
||||
dispatchFrigateCardEvent<ConfigType>(
|
||||
this,
|
||||
`${this._eventCategory}:remove`,
|
||||
this._config,
|
||||
);
|
||||
}
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
export class FrigateCardElementsBaseMenuItem<
|
||||
ConfigType,
|
||||
> extends FrigateCardElementsBaseItem<ConfigType> {
|
||||
constructor() {
|
||||
super('menu');
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('frigate-card-menu-icon')
|
||||
export class FrigateCardElementsMenuIcon extends FrigateCardElementsBaseMenuIcon<MenuIcon> {}
|
||||
export class FrigateCardElementsMenuIcon extends FrigateCardElementsBaseMenuItem<MenuIcon> {}
|
||||
|
||||
@customElement('frigate-card-menu-state-icon')
|
||||
export class FrigateCardElementsMenuStateIcon extends FrigateCardElementsBaseMenuIcon<MenuStateIcon> {}
|
||||
export class FrigateCardElementsMenuStateIcon extends FrigateCardElementsBaseMenuItem<MenuStateIcon> {}
|
||||
|
||||
@customElement('frigate-card-menu-submenu')
|
||||
export class FrigateCardElementsMenuSubmenu extends FrigateCardElementsBaseMenuIcon<MenuSubmenu> {}
|
||||
export class FrigateCardElementsMenuSubmenu extends FrigateCardElementsBaseMenuItem<MenuSubmenu> {}
|
||||
|
||||
@customElement('frigate-card-menu-submenu-select')
|
||||
export class FrigateCardElementsMenuSubmenuSelect extends FrigateCardElementsBaseMenuIcon<MenuSubmenuSelect> {}
|
||||
export class FrigateCardElementsMenuSubmenuSelect extends FrigateCardElementsBaseMenuItem<MenuSubmenuSelect> {}
|
||||
|
||||
export class FrigateCardElementsBaseStatusBarItem<
|
||||
ConfigType,
|
||||
> extends FrigateCardElementsBaseItem<ConfigType> {
|
||||
constructor() {
|
||||
super('status-bar');
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('frigate-card-status-bar-icon')
|
||||
export class FrigateCardElementsStatusBarIcon extends FrigateCardElementsBaseStatusBarItem<StatusBarIcon> {}
|
||||
|
||||
@customElement('frigate-card-status-bar-image')
|
||||
export class FrigateCardElementsStatusBarImage extends FrigateCardElementsBaseStatusBarItem<StatusBarImage> {}
|
||||
|
||||
@customElement('frigate-card-status-bar-string')
|
||||
export class FrigateCardElementsStatusBarString extends FrigateCardElementsBaseStatusBarItem<StatusBarString> {}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'frigate-card-conditional': FrigateCardElementsConditional;
|
||||
'frigate-card-elements': FrigateCardElements;
|
||||
'frigate-card-menu-submenu-select': FrigateCardElementsMenuSubmenuSelect;
|
||||
'frigate-card-menu-submenu': FrigateCardElementsMenuSubmenu;
|
||||
'frigate-card-menu-state-icon': FrigateCardElementsMenuStateIcon;
|
||||
'frigate-card-menu-icon': FrigateCardElementsMenuIcon;
|
||||
'frigate-card-elements-core': FrigateCardElementsCore;
|
||||
|
||||
'frigate-card-menu-icon': FrigateCardElementsMenuIcon;
|
||||
'frigate-card-menu-state-icon': FrigateCardElementsMenuStateIcon;
|
||||
'frigate-card-menu-submenu': FrigateCardElementsMenuSubmenu;
|
||||
'frigate-card-menu-submenu-select': FrigateCardElementsMenuSubmenuSelect;
|
||||
|
||||
'frigate-card-status-bar-icon': FrigateCardElementsStatusBarIcon;
|
||||
'frigate-card-status-bar-image': FrigateCardElementsStatusBarImage;
|
||||
'frigate-card-status-bar-string': FrigateCardElementsStatusBarString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
dispatchMediaPlayEvent,
|
||||
dispatchMediaVolumeChangeEvent,
|
||||
} from '../../../utils/media-info';
|
||||
import { getTechnologyForVideoRTC } from '../../../components-lib/live/utils/get-technology-for-video-rtc.js';
|
||||
|
||||
/**
|
||||
* Video player for go2rtc streaming application.
|
||||
@@ -325,6 +326,7 @@ export class VideoRTC extends HTMLElement {
|
||||
supportsPause: true,
|
||||
hasAudio: mayHaveAudio(this.video),
|
||||
},
|
||||
technology: getTechnologyForVideoRTC(this),
|
||||
});
|
||||
};
|
||||
this.video.onvolumechange = () => dispatchMediaVolumeChangeEvent(this);
|
||||
@@ -641,6 +643,7 @@ export class VideoRTC extends HTMLElement {
|
||||
receivedFirstFrame = true;
|
||||
dispatchMediaLoadedEvent(this, this.video, {
|
||||
player: this.containingPlayer,
|
||||
technology: ['mjpeg'],
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -668,6 +671,7 @@ export class VideoRTC extends HTMLElement {
|
||||
|
||||
dispatchMediaLoadedEvent(this, video2, {
|
||||
player: this.containingPlayer,
|
||||
technology: ['mp4'],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,7 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
||||
capabilities: {
|
||||
supportsPause: true,
|
||||
},
|
||||
technology: ['jsmpeg'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Task } from '@lit-labs/task';
|
||||
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
|
||||
import { Task } from '@lit-labs/task';
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { getTechnologyForVideoRTC } from '../../components-lib/live/utils/get-technology-for-video-rtc.js';
|
||||
import { CameraConfig, CardWideConfig } from '../../config/types.js';
|
||||
import { localize } from '../../localize/localize.js';
|
||||
import liveWebRTCCardStyle from '../../scss/live-webrtc-card.scss';
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
import { screenshotMedia } from '../../utils/screenshot.js';
|
||||
import { renderTask } from '../../utils/task.js';
|
||||
import { dispatchErrorMessageEvent, renderProgressIndicator } from '../message.js';
|
||||
import { VideoRTC } from './go2rtc/video-rtc.js';
|
||||
|
||||
// Create a wrapper for AlexxIT's WebRTC card
|
||||
// - https://github.com/AlexxIT/WebRTC
|
||||
@@ -104,15 +106,16 @@ export class FrigateCardLiveWebRTCCard
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
protected _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.
|
||||
*/
|
||||
protected _getPlayer(): HTMLVideoElement | null {
|
||||
const root = this.renderRoot?.querySelector('#webrtc') as
|
||||
| (HTMLElement & { video?: HTMLVideoElement })
|
||||
| null;
|
||||
return root?.video ?? null;
|
||||
return this._getVideoRTC()?.video ?? null;
|
||||
}
|
||||
|
||||
protected async _getWebRTCCardElement(): Promise<
|
||||
@@ -192,6 +195,7 @@ export class FrigateCardLiveWebRTCCard
|
||||
// Extract the video component after it has been rendered and generate the
|
||||
// media load event.
|
||||
this.updateComplete.then(() => {
|
||||
const videoRTC = this._getVideoRTC();
|
||||
const video = this._getPlayer();
|
||||
if (video) {
|
||||
setControlsOnVideo(video, this.controls);
|
||||
@@ -205,6 +209,7 @@ export class FrigateCardLiveWebRTCCard
|
||||
supportsPause: true,
|
||||
hasAudio: mayHaveAudio(video),
|
||||
},
|
||||
...(videoRTC && { technology: getTechnologyForVideoRTC(videoRTC) }),
|
||||
});
|
||||
};
|
||||
video.onplay = () => dispatchMediaPlayEvent(this);
|
||||
|
||||
@@ -62,11 +62,6 @@ import '../next-prev-control.js';
|
||||
import '../ptz.js';
|
||||
import { FrigateCardPTZ } from '../ptz.js';
|
||||
import '../surround.js';
|
||||
import '../title-control.js';
|
||||
import {
|
||||
FrigateCardTitleControl,
|
||||
getDefaultTitleConfigForView,
|
||||
} from '../title-control.js';
|
||||
|
||||
const FRIGATE_CARD_LIVE_PROVIDER = 'frigate-card-live-provider';
|
||||
|
||||
@@ -294,7 +289,6 @@ export class FrigateCardLiveCarousel extends LitElement {
|
||||
|
||||
// Index between camera name and slide number.
|
||||
protected _cameraToSlide: Record<string, number> = {};
|
||||
protected _refTitleControl: Ref<FrigateCardTitleControl> = createRef();
|
||||
protected _refPTZControl: Ref<FrigateCardPTZ> = createRef();
|
||||
|
||||
@state()
|
||||
@@ -540,18 +534,10 @@ export class FrigateCardLiveCarousel extends LitElement {
|
||||
? this.cameraManager.getCameraMetadata(this._getSubstreamCameraID(prevID, view))
|
||||
: null;
|
||||
const cameraID = this.viewFilterCameraID ?? view.camera;
|
||||
const cameraMetadataCurrent = this.cameraManager.getCameraMetadata(
|
||||
this._getSubstreamCameraID(cameraID, view),
|
||||
);
|
||||
const cameraMetadataNext = nextID
|
||||
? this.cameraManager.getCameraMetadata(this._getSubstreamCameraID(nextID, view))
|
||||
: null;
|
||||
|
||||
const titleConfig = getDefaultTitleConfigForView(
|
||||
view,
|
||||
this.overriddenLiveConfig?.controls.title,
|
||||
);
|
||||
|
||||
// Notes on the below:
|
||||
// - guard() is used to avoid reseting the carousel unless the
|
||||
// options/plugins actually change.
|
||||
@@ -574,9 +560,6 @@ export class FrigateCardLiveCarousel extends LitElement {
|
||||
transitionEffect=${this._getTransitionEffect()}
|
||||
@frigate-card:carousel:select=${this._setViewHandler.bind(this)}
|
||||
@frigate-card:media:loaded=${() => {
|
||||
if (this._refTitleControl.value) {
|
||||
this._refTitleControl.value.show();
|
||||
}
|
||||
this._mediaHasLoaded = true;
|
||||
}}
|
||||
@frigate-card:media:unloaded=${() => {
|
||||
@@ -620,18 +603,6 @@ export class FrigateCardLiveCarousel extends LitElement {
|
||||
.forceVisibility=${this._mediaHasLoaded && view.context?.ptzControls?.enabled}
|
||||
>
|
||||
</frigate-card-ptz>
|
||||
${cameraMetadataCurrent && titleConfig
|
||||
? html`<frigate-card-title-control
|
||||
${ref(this._refTitleControl)}
|
||||
.config=${titleConfig}
|
||||
.text="${cameraMetadataCurrent
|
||||
? `${localize('common.live')}: ${cameraMetadataCurrent.title}`
|
||||
: ''}"
|
||||
.logo="${cameraMetadataCurrent.engineLogo}"
|
||||
.fitInto=${this as HTMLElement}
|
||||
>
|
||||
</frigate-card-title-control> `
|
||||
: ``}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import overlayStyle from '../scss/overlay.scss';
|
||||
|
||||
@customElement('frigate-card-overlay')
|
||||
export class FrigateCardOverlay extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<slot name="top"></slot>
|
||||
<slot name="left"></slot>
|
||||
<slot name="right"></slot>
|
||||
<slot name="bottom"></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(overlayStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'frigate-card-overlay': FrigateCardOverlay;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { CSSResultGroup, LitElement, PropertyValues, TemplateResult, 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 { StatusBarController } from '../components-lib/status-bar-controller';
|
||||
import { StatusBarConfig, StatusBarItem } from '../config/types';
|
||||
import statusStyle from '../scss/status.scss';
|
||||
import { frigateCardHasAction } from '../utils/action';
|
||||
|
||||
@customElement('frigate-card-status-bar')
|
||||
export class FrigateCardStatusBar extends LitElement {
|
||||
protected _controller = new StatusBarController(this);
|
||||
|
||||
@property({ attribute: false })
|
||||
public items?: StatusBarItem[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public config?: StatusBarConfig;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
// Always set config before items.
|
||||
if (changedProperties.has('config') && this.config) {
|
||||
this._controller.setConfig(this.config);
|
||||
}
|
||||
|
||||
if (changedProperties.has('items')) {
|
||||
this._controller.setItems(this.items ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this._controller.shouldRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="status">
|
||||
${this._controller.getRenderItems().map((item): TemplateResult | void => {
|
||||
if (item.enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const classes = classMap({
|
||||
item: true,
|
||||
expand: !!item.expand,
|
||||
action: !!Object.keys(item.actions ?? {}).length,
|
||||
});
|
||||
|
||||
const handler = actionHandler({
|
||||
hasHold: frigateCardHasAction(item.actions?.hold_action),
|
||||
hasDoubleClick: frigateCardHasAction(item.actions?.double_tap_action),
|
||||
});
|
||||
|
||||
if (item.type === 'custom:frigate-card-status-bar-string') {
|
||||
return html`<div
|
||||
.actionHandler=${handler}
|
||||
class="${classes}"
|
||||
@action=${(ev) => this._controller.actionHandler(ev, item.actions)}
|
||||
>
|
||||
${item.string}
|
||||
</div>`;
|
||||
} else if (item.type === 'custom:frigate-card-status-bar-icon') {
|
||||
return html`<ha-icon
|
||||
.actionHandler=${handler}
|
||||
class="${classes}"
|
||||
icon="${item.icon}"
|
||||
@action=${(ev) => this._controller.actionHandler(ev, item.actions)}
|
||||
></ha-icon>`;
|
||||
} else if (item.type === 'custom:frigate-card-status-bar-image') {
|
||||
return html`<img
|
||||
.actionHandler=${handler}
|
||||
class="${classes}"
|
||||
src="${item.image}"
|
||||
@action=${(ev) => this._controller.actionHandler(ev, item.actions)}
|
||||
/>`;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(statusStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'frigate-card-status-bar': FrigateCardStatusBar;
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ export class FrigateCardSubmenu extends LitElement {
|
||||
if (!this.submenu) {
|
||||
return html``;
|
||||
}
|
||||
const items = this.submenu.items as MenuSubmenuItem[];
|
||||
return html`
|
||||
<ha-button-menu
|
||||
corner=${'BOTTOM_LEFT'}
|
||||
@@ -109,7 +110,7 @@ export class FrigateCardSubmenu extends LitElement {
|
||||
>
|
||||
<ha-icon icon="${this.submenu.icon}"></ha-icon>
|
||||
</ha-icon-button>
|
||||
${this.submenu.items.map(this._renderItem.bind(this))}
|
||||
${items.map(this._renderItem.bind(this))}
|
||||
</ha-button-menu>
|
||||
`;
|
||||
}
|
||||
@@ -217,9 +218,11 @@ export class FrigateCardSubmenuSelect extends LitElement {
|
||||
// the items correctly (below).
|
||||
delete submenu['options'];
|
||||
|
||||
const items = submenu.items as MenuSubmenuItem[];
|
||||
|
||||
for (const option of options) {
|
||||
const title = this._optionTitles?.[option] ?? option;
|
||||
submenu.items.push({
|
||||
items.push({
|
||||
state_color: true,
|
||||
selected: stateObj.state === option,
|
||||
enabled: true,
|
||||
|
||||
@@ -51,10 +51,6 @@ export class FrigateCardSurroundBasic extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Master render method.
|
||||
* @returns A rendered template.
|
||||
*/
|
||||
protected render(): TemplateResult | void {
|
||||
return html` <slot name="above"></slot>
|
||||
<slot></slot>
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||
import { TitleControlConfig } from '../config/types';
|
||||
import titleStyle from '../scss/title-control.scss';
|
||||
import { View } from '../view/view.js';
|
||||
|
||||
type PaperToast = HTMLElement & {
|
||||
opened: boolean;
|
||||
};
|
||||
|
||||
export const getDefaultTitleConfigForView = (
|
||||
view?: Readonly<View> | null,
|
||||
baseConfig?: TitleControlConfig,
|
||||
): TitleControlConfig | null => {
|
||||
if (!baseConfig && view?.isGrid()) {
|
||||
return { mode: 'none', duration_seconds: 2 };
|
||||
}
|
||||
return {
|
||||
mode: 'popup-bottom-right',
|
||||
duration_seconds: 2,
|
||||
...baseConfig,
|
||||
};
|
||||
};
|
||||
|
||||
@customElement('frigate-card-title-control')
|
||||
export class FrigateCardTitleControl extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public config?: TitleControlConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public text?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public fitInto?: HTMLElement;
|
||||
|
||||
@property({ attribute: false })
|
||||
public logo?: string;
|
||||
|
||||
protected _toastRef: Ref<PaperToast> = createRef();
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
!this.text ||
|
||||
!this.config ||
|
||||
!this.config.mode ||
|
||||
this.config.duration_seconds === undefined ||
|
||||
this.config.mode === 'none' ||
|
||||
!this.fitInto
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const verticalAlign = this.config.mode.match(/-top-/) ? 'top' : 'bottom';
|
||||
const horizontalAlign = this.config.mode.match(/-left$/) ? 'left' : 'right';
|
||||
|
||||
return html` <paper-toast
|
||||
${ref(this._toastRef)}
|
||||
class="capsule"
|
||||
.duration=${this.config.duration_seconds * 1000}
|
||||
.verticalAlign=${verticalAlign}
|
||||
.horizontalAlign=${horizontalAlign}
|
||||
.text="${this.text}"
|
||||
.fitInto=${this.fitInto}
|
||||
>
|
||||
${this.logo ? html`<img src=${this.logo} />` : ''}
|
||||
</paper-toast>`;
|
||||
}
|
||||
|
||||
public isVisible(): boolean {
|
||||
return this._toastRef.value?.opened ?? false;
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
if (this._toastRef.value) {
|
||||
// Set it to false first, to ensure the timer resets.
|
||||
this._toastRef.value.opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (this._toastRef.value) {
|
||||
// Set it to false first, to ensure the timer resets.
|
||||
this._toastRef.value.opened = false;
|
||||
this._toastRef.value.opened = true;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(titleStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'frigate-card-title-control': FrigateCardTitleControl;
|
||||
}
|
||||
}
|
||||
@@ -73,11 +73,6 @@ import type { EmblaCarouselPlugins } from './carousel.js';
|
||||
import './next-prev-control.js';
|
||||
import './ptz';
|
||||
import './surround.js';
|
||||
import './title-control.js';
|
||||
import {
|
||||
FrigateCardTitleControl,
|
||||
getDefaultTitleConfigForView,
|
||||
} from './title-control.js';
|
||||
|
||||
export interface MediaViewerViewContext {
|
||||
seek?: Date;
|
||||
@@ -194,7 +189,6 @@ export class FrigateCardViewerCarousel extends LitElement {
|
||||
protected _selected = 0;
|
||||
|
||||
protected _media: ViewMedia[] | null = null;
|
||||
protected _refTitleControl: Ref<FrigateCardTitleControl> = createRef();
|
||||
protected _player: FrigateCardMediaPlayer | null = null;
|
||||
|
||||
/**
|
||||
@@ -407,15 +401,7 @@ export class FrigateCardViewerCarousel extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
const cameraMetadata = this.cameraManager.getCameraMetadata(
|
||||
selectedMedia.getCameraID(),
|
||||
);
|
||||
|
||||
const view = this.viewManagerEpoch?.manager.getView();
|
||||
const titleConfig = getDefaultTitleConfigForView(
|
||||
view,
|
||||
this.viewerConfig?.controls.title,
|
||||
);
|
||||
|
||||
return html`
|
||||
<frigate-card-carousel
|
||||
@@ -427,9 +413,6 @@ export class FrigateCardViewerCarousel extends LitElement {
|
||||
this._setViewSelectedIndex(ev.detail.index);
|
||||
}}
|
||||
@frigate-card:media:loaded=${(ev: CustomEvent<MediaLoadedInfo>) => {
|
||||
if (this._refTitleControl.value) {
|
||||
this._refTitleControl.value.show();
|
||||
}
|
||||
this._player = ev.detail.player ?? null;
|
||||
this._seekHandler();
|
||||
}}
|
||||
@@ -480,16 +463,6 @@ export class FrigateCardViewerCarousel extends LitElement {
|
||||
<ha-icon title="${localize('media_viewer.unseekable')}" icon="mdi:clock-remove">
|
||||
</ha-icon>
|
||||
</div>
|
||||
${cameraMetadata && titleConfig
|
||||
? html`<frigate-card-title-control
|
||||
${ref(this._refTitleControl)}
|
||||
.config=${titleConfig}
|
||||
.text="${selectedMedia.getTitle() ?? undefined}"
|
||||
.logo="${cameraMetadata?.engineLogo}"
|
||||
.fitInto=${this as HTMLElement}
|
||||
>
|
||||
</frigate-card-title-control> `
|
||||
: ``}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -919,6 +892,7 @@ export class FrigateCardViewerProvider
|
||||
supportsPause: true,
|
||||
hasAudio: mayHaveAudio(ev.target as HTMLVideoElement),
|
||||
},
|
||||
technology: ['hls'],
|
||||
});
|
||||
}}
|
||||
@volumechange=${() => dispatchMediaVolumeChangeEvent(this)}
|
||||
@@ -942,7 +916,7 @@ export class FrigateCardViewerProvider
|
||||
}
|
||||
}}
|
||||
@load=${(ev: Event) => {
|
||||
dispatchMediaLoadedEvent(this, ev, { player: this });
|
||||
dispatchMediaLoadedEvent(this, ev, { player: this, technology: ['jpg'] });
|
||||
}}
|
||||
/>`}
|
||||
`);
|
||||
|
||||
Reference in New Issue
Block a user