Merge pull request #85 from dermotduffy/fullscreen-button
Add fullscreen mode
This commit is contained in:
@@ -23,6 +23,7 @@ A full-featured Frigate Lovelace card:
|
|||||||
* Automatic updating to continually show latest clip / snapshot.
|
* Automatic updating to continually show latest clip / snapshot.
|
||||||
* Support for filtering events by zone and label.
|
* Support for filtering events by zone and label.
|
||||||
* Arbitrary entity access via menu (e.g. motion sensor access).
|
* Arbitrary entity access via menu (e.g. motion sensor access).
|
||||||
|
* Fullscreen event gallery & media viewing.
|
||||||
* Full Lovelace editing support.
|
* Full Lovelace editing support.
|
||||||
* Theme friendly.
|
* Theme friendly.
|
||||||
* **Advanced**: Support for [WebRTC](https://github.com/AlexxIT/WebRTC) live viewing by embedding the WebRTC card.
|
* **Advanced**: Support for [WebRTC](https://github.com/AlexxIT/WebRTC) live viewing by embedding the WebRTC card.
|
||||||
@@ -73,19 +74,28 @@ lovelace:
|
|||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| ------------- | --------------------------------------------- | - |
|
| ------------- | --------------------------------------------- | - |
|
||||||
| `frigate_camera_name` | The string after the "camera." in the `camera_entity` option (above). | This parameter allows the camera name heuristic to be overriden for cases where the entity name does not cleanly map to the Frigate camera name (e.g. when the Frigate camera name is capitalized, but the entity name is lower case). This camera name is used for communicating with the Frigate backend, e.g. for fetching events. |
|
| `frigate_camera_name` | The string after the "camera." in the `camera_entity` option (above). | This parameter allows the camera name heuristic to be overriden for cases where the entity name does not cleanly map to the Frigate camera name (e.g. when the Frigate camera name is capitalized, but the entity name is lower case). This camera name is used for communicating with the Frigate backend, e.g. for fetching events. |
|
||||||
| `live_provider` | `frigate` | Whether `frigate` (the default Frigate camera in Home Assistant which uses an RTMP stream), `frigate-jsmpeg` (JSMPEG stream proxied from the Frigate backend) or `webrtc` should provide the live camera view. See [note below on the required integration version](#jsmpeg-troubleshooting) for `frigate-jsmpeg` to function.|
|
| `live_provider` | `frigate` | The means through which the live camera view is displayed. See [Live Provider](#live-provider) below.|
|
||||||
| `view_default` | `live` | The view to show by default. See [views](#views) below.|
|
| `view_default` | `live` | The view to show by default. See [views](#views) below.|
|
||||||
| `frigate_client_id` | `frigate` | The Frigate client id to use. If this Home Assistant server has multiple Frigate server backends configured, this selects which server should be used. It should be set to the MQTT client id configured for this server, see [Frigate Integration Multiple Instance Support](https://blakeblackshear.github.io/frigate/usage/home-assistant/#multiple-instance-support).|
|
| `frigate_client_id` | `frigate` | The Frigate client id to use. If this Home Assistant server has multiple Frigate server backends configured, this selects which server should be used. It should be set to the MQTT client id configured for this server, see [Frigate Integration Multiple Instance Support](https://blakeblackshear.github.io/frigate/usage/home-assistant/#multiple-instance-support).|
|
||||||
| `view_timeout` | | A numbers of seconds of inactivity after which the card will reset to the default configured view. Inactivity is defined as lack of interaction with the Frigate menu.|
|
| `view_timeout` | | A numbers of seconds of inactivity after which the card will reset to the default configured view. Inactivity is defined as lack of interaction with the Frigate menu.|
|
||||||
| `frigate_url` | | The URL of the frigate server. If set, this value will be (exclusively) used for a `Frigate UI` menu button. |
|
| `frigate_url` | | The URL of the frigate server. If set, this value will be (exclusively) used for a `Frigate UI` menu button. |
|
||||||
| `autoplay_clip` | `false` | Whether or not to autoplay clips in the 'clip' [view](#views). Clips manually chosen in the clips gallery will still autoplay.|
|
| `autoplay_clip` | `false` | Whether or not to autoplay clips in the 'clip' [view](#views). Clips manually chosen in the clips gallery will still autoplay.|
|
||||||
|
|
||||||
|
|
||||||
|
#### Live Provider
|
||||||
|
|
||||||
|
|Live Provider|Latency|Frame Rate|Installation|Description|
|
||||||
|
| -- | -- | -- | -- | -- |
|
||||||
|
|`frigate`|High|High|Builtin|Use the built-in Home Assistant camera stream from Frigate (RTMP). The camera doesn't even need to be a Frigate camera! |
|
||||||
|
|`frigate-jsmpeg`|Lower|Low|Builtin|Stream the JSMPEG stream from Frigate (proxied via the Frigate integration). See [note below on the required integration version](#jsmpeg-troubleshooting) for this live provider to function.|
|
||||||
|
|`webrtc`|Lowest|High|Separate installation required|Uses [WebRTC](https://github.com/AlexxIT/WebRTC) to stream live feed, requires manual extra setup, see [below](#webrtc).|
|
||||||
|
|
||||||
### Appearance
|
### Appearance
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| ------------- | --------------------------------------------- | - |
|
| ------------- | --------------------------------------------- | - |
|
||||||
| `menu_mode` | `hidden-top` | The menu mode to show by default. See [menu modes](#menu-modes) below.|
|
| `menu_mode` | `hidden-top` | The menu mode to show by default. See [menu modes](#menu-modes) below.|
|
||||||
| `menu_buttons.{frigate, live, clips, snapshots, frigate_ui}` | `true` | Whether or not to show these builtin actions in the card menu. |
|
| `menu_buttons.{frigate, live, clips, snapshots, frigate_ui, fullscreen}` | `true` | Whether or not to show these builtin actions in the card menu. |
|
||||||
| `controls.nextprev` | `thumbnails` | When viewing media, what kind of controls to show to move to the previous/next media item. Acceptable values: `thumbnails`, `chevrons`, `none` . |
|
| `controls.nextprev` | `thumbnails` | When viewing media, what kind of controls to show to move to the previous/next media item. Acceptable values: `thumbnails`, `chevrons`, `none` . |
|
||||||
| `dimensions.aspect_ratio_mode` | `dynamic` | The aspect ratio mode to use. Acceptable values: `dynamic`, `static`, `unconstrained`. See [aspect ratios](#aspect-ratios) below.|
|
| `dimensions.aspect_ratio_mode` | `dynamic` | The aspect ratio mode to use. Acceptable values: `dynamic`, `static`, `unconstrained`. See [aspect ratios](#aspect-ratios) below.|
|
||||||
| `dimensions.aspect_ratio` | `16:9` | The aspect ratio to use. Acceptable values: `<W>:<H>` or `<W>/<H>`. See [aspect ratios](#aspect-ratios) below.|
|
| `dimensions.aspect_ratio` | `16:9` | The aspect ratio to use. Acceptable values: `<W>:<H>` or `<W>/<H>`. See [aspect ratios](#aspect-ratios) below.|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"dayjs": "^1.10.6",
|
"dayjs": "^1.10.6",
|
||||||
"home-assistant-js-websocket": "^5.11.1",
|
"home-assistant-js-websocket": "^5.11.1",
|
||||||
"lit": "^2.0.0-rc.2",
|
"lit": "^2.0.0-rc.2",
|
||||||
|
"screenfull": "^5.1.0",
|
||||||
"zod": "^3.8.2"
|
"zod": "^3.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+68
-11
@@ -17,6 +17,7 @@ import {
|
|||||||
getLovelace,
|
getLovelace,
|
||||||
stateIcon,
|
stateIcon,
|
||||||
} from 'custom-card-helpers';
|
} from 'custom-card-helpers';
|
||||||
|
import screenfull from 'screenfull';
|
||||||
|
|
||||||
import { MenuButton, frigateCardConfigSchema } from './types';
|
import { MenuButton, frigateCardConfigSchema } from './types';
|
||||||
import type {
|
import type {
|
||||||
@@ -191,6 +192,13 @@ export class FrigateCard extends LitElement {
|
|||||||
description: localize('menu.frigate_ui'),
|
description: localize('menu.frigate_ui'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if ((this.config.menu_buttons?.fullscreen ?? true) && screenfull.isEnabled) {
|
||||||
|
buttons.set('fullscreen', {
|
||||||
|
icon: screenfull.isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen',
|
||||||
|
description: localize('menu.fullscreen'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const entities = this.config.entities || [];
|
const entities = this.config.entities || [];
|
||||||
for (let i = 0; this._hass && i < entities.length; i++) {
|
for (let i = 0; this._hass && i < entities.length; i++) {
|
||||||
if (!entities[i].show) {
|
if (!entities[i].show) {
|
||||||
@@ -294,6 +302,11 @@ export class FrigateCard extends LitElement {
|
|||||||
window.open(frigate_url);
|
window.open(frigate_url);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'fullscreen':
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.toggle(this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// If it's unknown, it's assumed to be an entity_id.
|
// If it's unknown, it's assumed to be an entity_id.
|
||||||
fireEvent(this, 'hass-more-info', { entityId: name });
|
fireEvent(this, 'hass-more-info', { entityId: name });
|
||||||
@@ -383,16 +396,37 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _fullScreenHandler(): void {
|
||||||
|
// Re-render after a change to fullscreen mode to take advantage of
|
||||||
|
// the expanded screen real-estate (vs staying in aspect-ratio locked
|
||||||
|
// modes).
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.on('change', this._fullScreenHandler.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disconnectedCallback(): void {
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.off('change', this._fullScreenHandler.bind(this));
|
||||||
|
}
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
protected _getAspectRatioPadding(): number | null {
|
protected _getAspectRatioPadding(): number | null {
|
||||||
const aspect_ratio_mode = this.config.dimensions?.aspect_ratio_mode ?? 'dynamic';
|
const aspect_ratio_mode = this.config.dimensions?.aspect_ratio_mode ?? 'dynamic';
|
||||||
|
|
||||||
// Do not constrain aspect ratio if either it's entire disabled or it's a
|
// Do not artifically constrain aspect ratio if:
|
||||||
// media view (i.e. not the gallery) and there's a loaded media item in
|
// - It's fullscreen.
|
||||||
// dynamic mode (as the aspect_ratio is essentially whatever the media
|
// - Aspect ratio enforcement is disabled.
|
||||||
// dimensions are).
|
// - Or it's a media view (i.e. not the gallery) and there's a loaded media
|
||||||
|
// item.
|
||||||
if (
|
if (
|
||||||
|
(screenfull.isEnabled && screenfull.isFullscreen) ||
|
||||||
aspect_ratio_mode == 'unconstrained' ||
|
aspect_ratio_mode == 'unconstrained' ||
|
||||||
(!this._view.isGalleryView() && aspect_ratio_mode == 'dynamic' && this._mediaInfo)
|
(this._view.isMediaView() && aspect_ratio_mode == 'dynamic' && this._mediaInfo)
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -419,11 +453,34 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const padding = this._getAspectRatioPadding();
|
const padding = this._getAspectRatioPadding();
|
||||||
let containerStyleMap = {};
|
const outerStyle = {}, innerStyle = {};
|
||||||
|
|
||||||
|
// Padding to force a particular aspect ratio.
|
||||||
if (padding != null) {
|
if (padding != null) {
|
||||||
containerStyleMap = {
|
outerStyle['padding-top'] = `${padding}%`;
|
||||||
'padding-top': `${padding}%`,
|
}
|
||||||
};
|
|
||||||
|
// Special treatment required when:
|
||||||
|
//
|
||||||
|
// - It's in fullscreen mode
|
||||||
|
// - It's viewing a media clip
|
||||||
|
// - And the media clip is taller than wider (portrait)
|
||||||
|
//
|
||||||
|
// We cannot seem to scale the video by height in CSS without actually
|
||||||
|
// styling the underlying video element (which we do not have access to as
|
||||||
|
// it's buried past multiple shadow roots), so instead scale the width in
|
||||||
|
// terms of'vh' (viewport height) in proportion to the aspect-ratio of the
|
||||||
|
// media.
|
||||||
|
if (
|
||||||
|
screenfull.isEnabled &&
|
||||||
|
screenfull.isFullscreen &&
|
||||||
|
this._view.isMediaView() &&
|
||||||
|
this._mediaInfo &&
|
||||||
|
this._mediaInfo.width < this._mediaInfo.height
|
||||||
|
) {
|
||||||
|
innerStyle['max-width'] = `${
|
||||||
|
(100 * this._mediaInfo.width) / this._mediaInfo.height
|
||||||
|
}vh`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentClasses = {
|
const contentClasses = {
|
||||||
@@ -433,8 +490,8 @@ export class FrigateCard extends LitElement {
|
|||||||
|
|
||||||
return html` <ha-card @click=${this._interactionHandler}>
|
return html` <ha-card @click=${this._interactionHandler}>
|
||||||
${this.config.menu_mode == 'above' ? this._renderMenu() : ''}
|
${this.config.menu_mode == 'above' ? this._renderMenu() : ''}
|
||||||
<div class="container outer" style="${styleMap(containerStyleMap)}">
|
<div class="container outer" style="${styleMap(outerStyle)}">
|
||||||
<div class="${classMap(contentClasses)}">
|
<div class="${classMap(contentClasses)}" style="${styleMap(innerStyle)}">
|
||||||
${this._view.is('clips') || this._view.is('snapshots')
|
${this._view.is('clips') || this._view.is('snapshots')
|
||||||
? html` <frigate-card-gallery
|
? html` <frigate-card-gallery
|
||||||
.hass=${this._hass}
|
.hass=${this._hass}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators';
|
import { customElement, property, state } from 'lit/decorators';
|
||||||
import { until } from 'lit/directives/until.js';
|
import { until } from 'lit/directives/until.js';
|
||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
|
|
||||||
@@ -16,6 +16,10 @@ import { localize } from '../localize/localize';
|
|||||||
import { renderMessage, renderErrorMessage, renderProgressIndicator } from './message';
|
import { renderMessage, renderErrorMessage, renderProgressIndicator } from './message';
|
||||||
|
|
||||||
import galleryStyle from '../scss/gallery.scss';
|
import galleryStyle from '../scss/gallery.scss';
|
||||||
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
|
|
||||||
|
const MAX_THUMBNAIL_WIDTH = 175;
|
||||||
|
const DEFAULT_COLUMNS = 5;
|
||||||
|
|
||||||
@customElement('frigate-card-gallery')
|
@customElement('frigate-card-gallery')
|
||||||
export class FrigateCardGallery extends LitElement {
|
export class FrigateCardGallery extends LitElement {
|
||||||
@@ -28,10 +32,35 @@ export class FrigateCardGallery extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
protected browseMediaQueryParameters!: BrowseMediaQueryParameters;
|
protected browseMediaQueryParameters!: BrowseMediaQueryParameters;
|
||||||
|
|
||||||
|
protected _resizeObserver: ResizeObserver;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
protected _columns = DEFAULT_COLUMNS;
|
||||||
|
|
||||||
protected _getMediaType(): 'clips' | 'snapshots' {
|
protected _getMediaType(): 'clips' | 'snapshots' {
|
||||||
return this.view?.view == 'clips' ? 'clips' : 'snapshots';
|
return this.view?.view == 'clips' ? 'clips' : 'snapshots';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._resizeObserver?.observe(this);
|
||||||
|
}
|
||||||
|
disconnectedCallback(): void {
|
||||||
|
this._resizeObserver.disconnect();
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
protected _resizeHandler(): void {
|
||||||
|
this._columns = Math.max(
|
||||||
|
DEFAULT_COLUMNS,
|
||||||
|
Math.floor(this.clientWidth / MAX_THUMBNAIL_WIDTH),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`${until(this._render(), renderProgressIndicator())}`;
|
return html`${until(this._render(), renderProgressIndicator())}`;
|
||||||
}
|
}
|
||||||
@@ -57,9 +86,15 @@ export class FrigateCardGallery extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
// Controls the number of columns in the gallery (allows for 1px gutter).
|
||||||
|
width: `calc(${100 / this._columns}% - 1.2px)`,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.info(this.clientWidth);
|
||||||
return html` <ul class="mdc-image-list frigate-card-gallery">
|
return html` <ul class="mdc-image-list frigate-card-gallery">
|
||||||
${this.view && this.view.previous
|
${this.view && this.view.previous
|
||||||
? html`<li class="mdc-image-list__item">
|
? html`<li class="mdc-image-list__item" style="${styleMap(styles)}">
|
||||||
<div class="mdc-image-list__image-aspect-container">
|
<div class="mdc-image-list__image-aspect-container">
|
||||||
<div class="mdc-image-list__image">
|
<div class="mdc-image-list__image">
|
||||||
<ha-card
|
<ha-card
|
||||||
@@ -79,7 +114,7 @@ export class FrigateCardGallery extends LitElement {
|
|||||||
: ''}
|
: ''}
|
||||||
${parent.children.map(
|
${parent.children.map(
|
||||||
(child, index) =>
|
(child, index) =>
|
||||||
html` <li class="mdc-image-list__item">
|
html` <li class="mdc-image-list__item" style="${styleMap(styles)}">
|
||||||
<div class="mdc-image-list__image-aspect-container">
|
<div class="mdc-image-list__image-aspect-container">
|
||||||
${child.can_expand
|
${child.can_expand
|
||||||
? html`<div class="mdc-image-list__image">
|
? html`<div class="mdc-image-list__image">
|
||||||
|
|||||||
@@ -386,6 +386,18 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
@change=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
<br />
|
||||||
|
<ha-formfield
|
||||||
|
.label=${localize('editor.show_button') +
|
||||||
|
': ' +
|
||||||
|
localize('menu.fullscreen')}
|
||||||
|
>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this._config?.menu_buttons?.frigate_ui ?? true}
|
||||||
|
.configValue=${'menu_buttons.fullscreen'}
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
></ha-switch>
|
||||||
|
</ha-formfield>
|
||||||
<br />`
|
<br />`
|
||||||
: ''}
|
: ''}
|
||||||
<div class="option" @click=${this._toggleOption} .option=${'advanced'}>
|
<div class="option" @click=${this._toggleOption} .option=${'advanced'}>
|
||||||
|
|||||||
@@ -41,7 +41,8 @@
|
|||||||
"snapshots": "Snapshots Gallery",
|
"snapshots": "Snapshots Gallery",
|
||||||
"clip": "Latest Clip",
|
"clip": "Latest Clip",
|
||||||
"snapshot": "Latest Snapshot",
|
"snapshot": "Latest Snapshot",
|
||||||
"frigate_ui": "Frigate User Interface"
|
"frigate_ui": "Frigate User Interface",
|
||||||
|
"fullscreen": "Fullscreen"
|
||||||
},
|
},
|
||||||
"control": {
|
"control": {
|
||||||
"nextprev": "Media Next & Previous Controls",
|
"nextprev": "Media Next & Previous Controls",
|
||||||
@@ -93,7 +94,6 @@
|
|||||||
"could_not_resolve": "Could not resolve media URL",
|
"could_not_resolve": "Could not resolve media URL",
|
||||||
"no_live_camera": "No live camera",
|
"no_live_camera": "No live camera",
|
||||||
"invalid_configuration": "Invalid configuration",
|
"invalid_configuration": "Invalid configuration",
|
||||||
"missing_webrtc": "WebRTC component not found",
|
"missing_webrtc": "WebRTC component not found"
|
||||||
"internal": "Internal error"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ customElements.whenDefined("ha-hls-player").then(() => {
|
|||||||
.muted=${this.muted}
|
.muted=${this.muted}
|
||||||
?playsinline=${this.playsInline}
|
?playsinline=${this.playsInline}
|
||||||
?controls=${this.controls}
|
?controls=${this.controls}
|
||||||
@loadedmetadata=${(e) => dispatchMediaLoadEvent(this, e)}
|
@loadeddata=${(e) => {
|
||||||
@loadeddata=${this._elementResized}
|
this._elementResized();
|
||||||
|
dispatchMediaLoadEvent(this, e);
|
||||||
|
}}
|
||||||
@pause=${() => dispatchPauseEvent(this)}
|
@pause=${() => dispatchPauseEvent(this)}
|
||||||
@play=${() => dispatchPlayEvent(this)}
|
@play=${() => dispatchPlayEvent(this)}
|
||||||
></video>
|
></video>
|
||||||
|
|||||||
+40
-3
@@ -1,10 +1,16 @@
|
|||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.frigate-card-contents {
|
.frigate-card-contents {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
margin: auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
-ms-overflow-style: none; /* Hide scrollbar: IE and Edge */
|
-ms-overflow-style: none; /* Hide scrollbar: IE and Edge */
|
||||||
scrollbar-width: none; /* Hide scrollbar: Firefox */
|
scrollbar-width: none; /* Hide scrollbar: Firefox */
|
||||||
@@ -15,7 +21,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The 'hover' menu mode is styling applied outside of the menu itself */
|
/* When forcing aspect ratio absolute positioning is required */
|
||||||
.frigate-card-contents.absolute {
|
.frigate-card-contents.absolute {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@@ -24,7 +30,7 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Support the 'hover' menu mode. */
|
/* The 'hover' menu mode is styling applied outside of the menu itself */
|
||||||
.hover-menu {
|
.hover-menu {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
@@ -50,4 +56,35 @@ ha-card {
|
|||||||
|
|
||||||
ha-card a {
|
ha-card a {
|
||||||
color: var(--primary-text-color, white);
|
color: var(--primary-text-color, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
frigate-card-gallery, frigate-card-viewer, frigate-card-live {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
frigate-card-gallery {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browsers will reject invalid whole CSS selectors if one selector is bad, so
|
||||||
|
// need to use mixin here instead of just comma-separated selectors.
|
||||||
|
// - Related: https://stackoverflow.com/questions/16982449/why-isnt-it-possible-to-combine-vendor-specific-pseudo-elements-classes-into-on
|
||||||
|
@mixin fullscreen-ha-card {
|
||||||
|
// Make the background black, to give it the expected fullscreen feel.
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
// Hide corners on Safari fullscreen.
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
:host(:fullscreen) ha-card {
|
||||||
|
@include fullscreen-ha-card;
|
||||||
|
}
|
||||||
|
:host(:-webkit-full-screen) ha-card {
|
||||||
|
@include fullscreen-ha-card;
|
||||||
|
}
|
||||||
|
:host(:-moz-full-screen) ha-card {
|
||||||
|
@include fullscreen-ha-card;
|
||||||
|
}
|
||||||
|
:host(:-webkit-full-screen) ha-card {
|
||||||
|
@include fullscreen-ha-card;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
@use "@material/image-list";
|
@use "@material/image-list";
|
||||||
|
|
||||||
.frigate-card-gallery {
|
.frigate-card-gallery {
|
||||||
|
// Note: In fullscreen, number of columns is overwritten in Javascript based
|
||||||
|
// on dimensions.
|
||||||
@include image-list.standard-columns(5, 1px);
|
@include image-list.standard-columns(5, 1px);
|
||||||
@include image-list.shape-radius(5px);
|
@include image-list.shape-radius(5px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const frigateCardConfigSchema = z.object({
|
|||||||
clips: z.boolean().default(true),
|
clips: z.boolean().default(true),
|
||||||
snapshots: z.boolean().default(true),
|
snapshots: z.boolean().default(true),
|
||||||
frigate_ui: z.boolean().default(true),
|
frigate_ui: z.boolean().default(true),
|
||||||
|
fullscreen: z.boolean().default(true),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
entities: z
|
entities: z
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ export class View {
|
|||||||
return this.view == 'clips' || this.view == 'snapshots';
|
return this.view == 'clips' || this.view == 'snapshots';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isMediaView(): boolean {
|
||||||
|
return !this.isGalleryView();
|
||||||
|
}
|
||||||
|
|
||||||
get media(): BrowseMediaSource | undefined {
|
get media(): BrowseMediaSource | undefined {
|
||||||
if (this.target) {
|
if (this.target) {
|
||||||
if (this.target.children && this.childIndex !== undefined) {
|
if (this.target.children && this.childIndex !== undefined) {
|
||||||
|
|||||||
Reference in New Issue
Block a user