Add ability to change media layout.
This commit is contained in:
Vendored
+96
@@ -29,3 +29,99 @@ reviews:
|
|||||||
Mostrar imagem estática enquanto a transmissão ao vivo está
|
Mostrar imagem estática enquanto a transmissão ao vivo está
|
||||||
carregando
|
carregando
|
||||||
time: '2022-08-07T23:00:35.117Z'
|
time: '2022-08-07T23:00:35.117Z'
|
||||||
|
config.common.layout.fit:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Disposizione adatta
|
||||||
|
time: '2022-08-12T03:00:50.438Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Ajuste de layout
|
||||||
|
time: '2022-08-12T03:00:54.209Z'
|
||||||
|
config.common.layout.fits.contain:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: I media sono contenuti/incartati
|
||||||
|
time: '2022-08-12T03:01:00.885Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: A mídia está contida/em letterbox
|
||||||
|
time: '2022-08-12T03:01:03.607Z'
|
||||||
|
config.common.layout.fits.cover:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Il supporto si espande proporzionalmente per coprire la scheda
|
||||||
|
time: '2022-08-12T03:01:09.001Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: A mídia se expande proporcionalmente para cobrir o cartão
|
||||||
|
time: '2022-08-12T03:01:11.240Z'
|
||||||
|
config.common.layout.fits.fill:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Il supporto viene allungato per riempire la scheda
|
||||||
|
time: '2022-08-12T03:01:14.657Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: A mídia é esticada para preencher o cartão
|
||||||
|
time: '2022-08-12T03:01:17.319Z'
|
||||||
|
config.common.layout.position.x:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Percentuale di posizionamento orizzontale
|
||||||
|
time: '2022-08-12T03:01:20.714Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Porcentagem de posicionamento horizontal
|
||||||
|
time: '2022-08-12T03:01:22.619Z'
|
||||||
|
config.common.layout.position.y:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Percentuale di posizionamento verticale
|
||||||
|
time: '2022-08-12T03:01:26.332Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Porcentagem de posicionamento vertical
|
||||||
|
time: '2022-08-12T03:01:28.398Z'
|
||||||
|
config.image.layout:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Disposizione dell'immagine
|
||||||
|
time: '2022-08-12T03:01:33.020Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Esquema de imagem
|
||||||
|
time: '2022-08-12T03:01:34.987Z'
|
||||||
|
config.media_viewer.layout:
|
||||||
|
locales:
|
||||||
|
it:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Layout del visualizzatore multimediale
|
||||||
|
time: '2022-08-12T03:01:45.033Z'
|
||||||
|
pt-BR:
|
||||||
|
translation_candidate:
|
||||||
|
source: en
|
||||||
|
text: Layout do visualizador de mídia
|
||||||
|
time: '2022-08-12T03:01:46.528Z'
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ See the [fully expanded live configuration example](#config-expanded-live) for h
|
|||||||
| `controls` | | :white_check_mark: | Configuration for the `live` view controls. See below. |
|
| `controls` | | :white_check_mark: | Configuration for the `live` view controls. See below. |
|
||||||
| `jsmpeg` | | :white_check_mark: | Configuration for the `frigate-jsmpeg` live provider. See below.|
|
| `jsmpeg` | | :white_check_mark: | Configuration for the `frigate-jsmpeg` live provider. See below.|
|
||||||
| `webrtc_card` | | :white_check_mark: | Configuration for the `webrtc-card` live provider. See below.|
|
| `webrtc_card` | | :white_check_mark: | Configuration for the `webrtc-card` live provider. See below.|
|
||||||
|
| `layout` | | :white_check_mark: | See [media layout](#media-layout) below.|
|
||||||
|
|
||||||
#### Live Provider: JSMPEG Configuration
|
#### Live Provider: JSMPEG Configuration
|
||||||
|
|
||||||
@@ -437,6 +438,7 @@ See the [fully expanded Media viewer configuration example](#config-expanded-med
|
|||||||
| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between event media. Accepted values: `slide` or `none`. |
|
| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between event media. Accepted values: `slide` or `none`. |
|
||||||
| `controls` | | :heavy_multiplication_x: | Configuration for the Media viewer controls. See below. |
|
| `controls` | | :heavy_multiplication_x: | Configuration for the Media viewer controls. See below. |
|
||||||
| `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `media_viewer` (e.g. `clip`, `snapshot`). See [actions](#actions) below.|
|
| `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `media_viewer` (e.g. `clip`, `snapshot`). See [actions](#actions) below.|
|
||||||
|
| `layout` | | :white_check_mark: | See [media layout](#media-layout) below.|
|
||||||
|
|
||||||
#### Media Viewer Controls: Next / Previous
|
#### Media Viewer Controls: Next / Previous
|
||||||
|
|
||||||
@@ -468,8 +470,8 @@ media_viewer:
|
|||||||
| `mode` | `none` | :heavy_multiplication_x: | Whether to show the thumbnail carousel `below` the media, `above` the media, in a drawer to the `left` or `right` of the media or to hide it entirely (`none`).|
|
| `mode` | `none` | :heavy_multiplication_x: | Whether to show the thumbnail carousel `below` the media, `above` the media, in a drawer to the `left` or `right` of the media or to hide it entirely (`none`).|
|
||||||
| `size` | 100 | :heavy_multiplication_x: | The size of the thumbnails in the thumbnail carousel pixels. Must be >= `75` and <= `175`.|
|
| `size` | 100 | :heavy_multiplication_x: | The size of the thumbnails in the thumbnail carousel pixels. Must be >= `75` and <= `175`.|
|
||||||
| `show_details` | `false` | :heavy_multiplication_x: | Whether to show event details (e.g. duration, start time, object detected, etc) alongside the thumbnail.|
|
| `show_details` | `false` | :heavy_multiplication_x: | Whether to show event details (e.g. duration, start time, object detected, etc) alongside the thumbnail.|
|
||||||
| `show_favorite_control` | `true` | :white_check_mark: | Whether to show the favorite ('star') control on each thumbnail.|
|
| `show_favorite_control` | `true` | :heavy_multiplication_x: | Whether to show the favorite ('star') control on each thumbnail.|
|
||||||
| `show_timeline_control` | `true` | :white_check_mark: | Whether to show the timeline ('target') control on each thumbnail.|
|
| `show_timeline_control` | `true` | :heavy_multiplication_x: | Whether to show the timeline ('target') control on each thumbnail.|
|
||||||
|
|
||||||
#### Media Viewer Controls: Title
|
#### Media Viewer Controls: Title
|
||||||
|
|
||||||
@@ -502,8 +504,8 @@ See the [fully expanded event gallery configuration example](#config-expanded-ev
|
|||||||
| - | - | - | - |
|
| - | - | - | - |
|
||||||
| `size` | 100 | :heavy_multiplication_x: | The size of the thumbnails in the event gallery in pixels. Must be >= `75` and <= `175`.|
|
| `size` | 100 | :heavy_multiplication_x: | The size of the thumbnails in the event gallery in pixels. Must be >= `75` and <= `175`.|
|
||||||
| `show_details` | `false` | :heavy_multiplication_x: | Whether to show event details (e.g. duration, start time, object detected, etc) alongside the thumbnail.|
|
| `show_details` | `false` | :heavy_multiplication_x: | Whether to show event details (e.g. duration, start time, object detected, etc) alongside the thumbnail.|
|
||||||
| `show_favorite_control` | `true` | :white_check_mark: | Whether to show the favorite ('star') control on each thumbnail.|
|
| `show_favorite_control` | `true` | :heavy_multiplication_x: | Whether to show the favorite ('star') control on each thumbnail.|
|
||||||
| `show_timeline_control` | `true` | :white_check_mark: | Whether to show the timeline ('target') control on each thumbnail.|
|
| `show_timeline_control` | `true` | :heavy_multiplication_x: | Whether to show the timeline ('target') control on each thumbnail.|
|
||||||
| `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `event_gallery` (e.g. `clips`, `snapshots`). See [actions](#actions) below.|
|
| `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `event_gallery` (e.g. `clips`, `snapshots`). See [actions](#actions) below.|
|
||||||
|
|
||||||
### Image Options
|
### Image Options
|
||||||
@@ -565,6 +567,8 @@ timeline:
|
|||||||
| `show_favorite_control` | `true` | :white_check_mark: | Whether to show the favorite ('star') control on each thumbnail.|
|
| `show_favorite_control` | `true` | :white_check_mark: | Whether to show the favorite ('star') control on each thumbnail.|
|
||||||
| `show_timeline_control` | `true` | :white_check_mark: | Whether to show the timeline ('target') control on each thumbnail.|
|
| `show_timeline_control` | `true` | :white_check_mark: | Whether to show the timeline ('target') control on each thumbnail.|
|
||||||
|
|
||||||
|
<a name="dimensions"></a>
|
||||||
|
|
||||||
### Dimensions Options
|
### Dimensions Options
|
||||||
|
|
||||||
These options control the aspect-ratio of the entire card to make placement in
|
These options control the aspect-ratio of the entire card to make placement in
|
||||||
@@ -583,8 +587,8 @@ See the [fully expanded dimensions configuration example](#config-expanded-dimen
|
|||||||
|
|
||||||
| Option | Default | Overridable | Description |
|
| Option | Default | Overridable | Description |
|
||||||
| - | - | - | - |
|
| - | - | - | - |
|
||||||
| `aspect_ratio_mode` | `dynamic` | :heavy_multiplication_x: | The aspect ratio mode to use. Acceptable values: `dynamic`, `static`, `unconstrained`. See [aspect ratios](#aspect-ratios) below.|
|
| `aspect_ratio_mode` | `dynamic` | :white_check_mark: | The aspect ratio mode to use. Acceptable values: `dynamic`, `static`, `unconstrained`. See [aspect ratios](#aspect-ratios) below.|
|
||||||
| `aspect_ratio` | `16:9` | :heavy_multiplication_x: | The aspect ratio to use. Acceptable values: `<W>:<H>` or `<W>/<H>`. See [aspect ratios](#aspect-ratios) below.|
|
| `aspect_ratio` | `16:9` | :white_check_mark: | The aspect ratio to use. Acceptable values: `<W>:<H>` or `<W>/<H>`. See [aspect ratios](#aspect-ratios) below.|
|
||||||
|
|
||||||
#### `dimensions.aspect_ratio_mode`:
|
#### `dimensions.aspect_ratio_mode`:
|
||||||
|
|
||||||
@@ -615,6 +619,8 @@ The card aspect ratio can be changed with the `dimensions.aspect_ratio_mode` and
|
|||||||
If no aspect ratio is specified or available, but one is needed then `16:9` will
|
If no aspect ratio is specified or available, but one is needed then `16:9` will
|
||||||
be used by default.
|
be used by default.
|
||||||
|
|
||||||
|
<a name="overrides"></a>
|
||||||
|
|
||||||
### Overrides Options
|
### Overrides Options
|
||||||
|
|
||||||
All configuration is a list under:
|
All configuration is a list under:
|
||||||
@@ -643,6 +649,34 @@ item, that has both of the following parameters set:
|
|||||||
| `conditions` | | :heavy_multiplication_x: | A set of conditions that must evaluate to `true` in order for the overrides to be applied. See [Frigate Card Conditions](#frigate-card-conditions). |
|
| `conditions` | | :heavy_multiplication_x: | A set of conditions that must evaluate to `true` in order for the overrides to be applied. See [Frigate Card Conditions](#frigate-card-conditions). |
|
||||||
| `overrides` | | :heavy_multiplication_x: |Configuration overrides to be applied. Any configuration parameter described in this documentation as 'Overridable' is supported. |
|
| `overrides` | | :heavy_multiplication_x: |Configuration overrides to be applied. Any configuration parameter described in this documentation as 'Overridable' is supported. |
|
||||||
|
|
||||||
|
<a name="media-layout"></a>
|
||||||
|
|
||||||
|
### Media Layout
|
||||||
|
|
||||||
|
The `live`, `media_viewer` and `image` sections all support `layout` option which is used to control the fit and position of the media _within_ the card dimensions (in order to control the card dimensions themselves see [the dimensions parameter](#dimensions) ).
|
||||||
|
|
||||||
|
As the default card behavior is for the card to always expand to fit the media, these options only make sense if `dimensions.aspect_ratio_mode` is set to `static`.
|
||||||
|
|
||||||
|
All configuration is under:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
live:
|
||||||
|
layout:
|
||||||
|
image:
|
||||||
|
layout:
|
||||||
|
media_viewer:
|
||||||
|
layout:
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Overridable | Description |
|
||||||
|
| - | - | - | - |
|
||||||
|
| `fit` | `contain` | :white_check_mark: | If `contain`, the media is contained within the card and letterboxed if necessary. If `cover`, the media is expanded proportionally (i.e. maintaining the media aspect ratio) until the card is fully covered. If `fill`, the media is stretched to fill the card (i.e. ignoring the media aspect ratio). See [CSS object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) for technical details and a visualization. |
|
||||||
|
| `position` | | :white_check_mark: | A dictionary that contains an `x` and `y` percentage (`0` - `100`) to control the position of the media when the fit is `cover`. This can be effectively used to "pan" the media around. At any given time, only one of `x` and `y` will have an effect, depending on whether media width is larger than the card width (in which case `x` controls the position) or the media height is larger than the card height (in which case `y` controls the position). A value of `0` means maximally to the left or top of the media, a value of `100` means maximally to the right or bottom of the media. See [CSS object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) for technical details and a visualization. |
|
||||||
|
|
||||||
|
If multiple cameras are configured in the card, use [overrides](#overrides) to configure different values per camera.
|
||||||
|
|
||||||
|
See [media layout examples](#media-layout-examples).
|
||||||
|
|
||||||
<a name="webrtc"></a>
|
<a name="webrtc"></a>
|
||||||
|
|
||||||
### Using AlexxIT's WebRTC Card
|
### Using AlexxIT's WebRTC Card
|
||||||
@@ -1281,6 +1315,11 @@ live:
|
|||||||
title:
|
title:
|
||||||
mode: popup-bottom-right
|
mode: popup-bottom-right
|
||||||
duration_seconds: 2
|
duration_seconds: 2
|
||||||
|
layout:
|
||||||
|
fit: contain
|
||||||
|
position:
|
||||||
|
x: 50
|
||||||
|
y: 50
|
||||||
actions:
|
actions:
|
||||||
entity: light.office_main_lights
|
entity: light.office_main_lights
|
||||||
tap_action:
|
tap_action:
|
||||||
@@ -1326,6 +1365,11 @@ media_viewer:
|
|||||||
title:
|
title:
|
||||||
mode: popup-bottom-right
|
mode: popup-bottom-right
|
||||||
duration_seconds: 2
|
duration_seconds: 2
|
||||||
|
layout:
|
||||||
|
fit: contain
|
||||||
|
position:
|
||||||
|
x: 50
|
||||||
|
y: 50
|
||||||
actions:
|
actions:
|
||||||
entity: light.office_main_lights
|
entity: light.office_main_lights
|
||||||
tap_action:
|
tap_action:
|
||||||
@@ -1382,6 +1426,11 @@ Reference: [Image Options](#image-options).
|
|||||||
image:
|
image:
|
||||||
mode: url
|
mode: url
|
||||||
refresh_seconds: 0
|
refresh_seconds: 0
|
||||||
|
layout:
|
||||||
|
fit: contain
|
||||||
|
position:
|
||||||
|
x: 50
|
||||||
|
y: 50
|
||||||
actions:
|
actions:
|
||||||
entity: light.office_main_lights
|
entity: light.office_main_lights
|
||||||
tap_action:
|
tap_action:
|
||||||
@@ -2598,6 +2647,95 @@ view:
|
|||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<a name="media-layout-examples"></a>
|
||||||
|
|
||||||
|
### Changing the Media Layout
|
||||||
|
|
||||||
|
Change how the media fits and is positioned within the card dimensions.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand: Stretching an image</summary>
|
||||||
|
|
||||||
|
Stretch the stock Frigate card image into a 4:4 square.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: custom:frigate-card
|
||||||
|
cameras:
|
||||||
|
- camera_entity: camera.landing
|
||||||
|
view:
|
||||||
|
default: image
|
||||||
|
dimensions:
|
||||||
|
aspect_ratio_mode: static
|
||||||
|
aspect_ratio: '4:4'
|
||||||
|
image:
|
||||||
|
layout:
|
||||||
|
fit: fill
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand: Convert a landscape camera to a portrait live view</summary>
|
||||||
|
|
||||||
|
Take the left-hand side (position with x == `0` and use that as the basis of a `9:16` (i.e. portrait) live view.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: custom:frigate-card
|
||||||
|
cameras:
|
||||||
|
- camera_entity: camera.landing
|
||||||
|
dimensions:
|
||||||
|
aspect_ratio_mode: static
|
||||||
|
aspect_ratio: '9:16'
|
||||||
|
live:
|
||||||
|
layout:
|
||||||
|
fit: cover
|
||||||
|
position:
|
||||||
|
x: 0
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand: Complex example to vary the media layouts for different cameras</summary>
|
||||||
|
|
||||||
|
Configure three cameras (all natively landscape): the first uses automatic expansion of dimensions (the default), the second uses a portrait (`9:16`) card focused on the left side (`x` == `0`) of the media, the third uses a portrait (`9:16`) card focused on the right side (`x` == `100`) of the media.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: custom:frigate-card
|
||||||
|
cameras:
|
||||||
|
- camera_entity: camera.landing
|
||||||
|
- camera_entity: camera.living_room
|
||||||
|
- camera_entity: camera.sitting_room
|
||||||
|
overrides:
|
||||||
|
- conditions:
|
||||||
|
camera:
|
||||||
|
- camera.living_room
|
||||||
|
- camera.sitting_room
|
||||||
|
overrides:
|
||||||
|
dimensions:
|
||||||
|
aspect_ratio_mode: static
|
||||||
|
aspect_ratio: '9:16'
|
||||||
|
live:
|
||||||
|
layout:
|
||||||
|
fit: cover
|
||||||
|
- conditions:
|
||||||
|
camera:
|
||||||
|
- camera.living_room
|
||||||
|
overrides:
|
||||||
|
live:
|
||||||
|
layout:
|
||||||
|
position:
|
||||||
|
x: 0
|
||||||
|
- conditions:
|
||||||
|
camera:
|
||||||
|
- camera.sitting_room
|
||||||
|
overrides:
|
||||||
|
live:
|
||||||
|
layout:
|
||||||
|
position:
|
||||||
|
x: 100
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
<a name="card-updates"></a>
|
<a name="card-updates"></a>
|
||||||
|
|
||||||
## Card Refreshes
|
## Card Refreshes
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import { localize } from '../localize/localize.js';
|
|||||||
import imageStyle from '../scss/image.scss';
|
import imageStyle from '../scss/image.scss';
|
||||||
import { CameraConfig, ImageViewConfig } from '../types.js';
|
import { CameraConfig, ImageViewConfig } from '../types.js';
|
||||||
import { isHassDifferent } from '../utils/ha';
|
import { isHassDifferent } from '../utils/ha';
|
||||||
import { dispatchMediaShowEvent } from '../utils/media-info.js';
|
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
||||||
|
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
import { dispatchErrorMessageEvent } from './message.js';
|
import { dispatchErrorMessageEvent } from './message.js';
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ export class FrigateCardImage extends LitElement {
|
|||||||
this._getImageSource.bind(this),
|
this._getImageSource.bind(this),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
updateElementStyleFromMediaLayoutConfig(this, this.imageConfig?.layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the camera or view changed, immediately discard the old value (view to
|
// If the camera or view changed, immediately discard the old value (view to
|
||||||
@@ -228,7 +230,7 @@ export class FrigateCardImage extends LitElement {
|
|||||||
${ref(this._refImage)}
|
${ref(this._refImage)}
|
||||||
src=${live(src)}
|
src=${live(src)}
|
||||||
@load=${(ev: Event) => {
|
@load=${(ev: Event) => {
|
||||||
dispatchMediaShowEvent(this, ev);
|
dispatchMediaLoadedEvent(this, ev);
|
||||||
}}
|
}}
|
||||||
@error=${() => {
|
@error=${() => {
|
||||||
if (this.imageConfig?.mode === 'camera') {
|
if (this.imageConfig?.mode === 'camera') {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import { homeAssistantSignPath } from '../utils/ha';
|
|||||||
import { getFullDependentBrowseMediaQueryParameters } from '../utils/ha/browse-media.js';
|
import { getFullDependentBrowseMediaQueryParameters } from '../utils/ha/browse-media.js';
|
||||||
import {
|
import {
|
||||||
dispatchExistingMediaLoadedInfoAsEvent,
|
dispatchExistingMediaLoadedInfoAsEvent,
|
||||||
dispatchMediaShowEvent,
|
dispatchMediaLoadedEvent,
|
||||||
dispatchMediaUnloadedEvent,
|
dispatchMediaUnloadedEvent,
|
||||||
} from '../utils/media-info.js';
|
} from '../utils/media-info.js';
|
||||||
import { dispatchViewContextChangeEvent, View } from '../view.js';
|
import { dispatchViewContextChangeEvent, View } from '../view.js';
|
||||||
@@ -67,6 +67,7 @@ import { EmblaCarouselPlugins } from './carousel.js';
|
|||||||
import { renderTask } from '../utils/task.js';
|
import { renderTask } from '../utils/task.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
import './image';
|
import './image';
|
||||||
|
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
||||||
|
|
||||||
// Number of seconds a signed URL is valid for.
|
// Number of seconds a signed URL is valid for.
|
||||||
const URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
const URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
||||||
@@ -751,6 +752,9 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
dispatchMediaUnloadedEvent(this);
|
dispatchMediaUnloadedEvent(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (changedProps.has('liveConfig')) {
|
||||||
|
updateElementStyleFromMediaLayoutConfig(this, this.liveConfig?.layout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1080,7 +1084,7 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
|
|||||||
if (onloadeddata) {
|
if (onloadeddata) {
|
||||||
onloadeddata.call(video, e);
|
onloadeddata.call(video, e);
|
||||||
}
|
}
|
||||||
dispatchMediaShowEvent(this, video);
|
dispatchMediaLoadedEvent(this, video);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1219,7 +1223,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
|||||||
// ignore any subsequent calls.
|
// ignore any subsequent calls.
|
||||||
if (!videoDecoded && this._jsmpegCanvasElement) {
|
if (!videoDecoded && this._jsmpegCanvasElement) {
|
||||||
videoDecoded = true;
|
videoDecoded = true;
|
||||||
dispatchMediaShowEvent(this, this._jsmpegCanvasElement);
|
dispatchMediaLoadedEvent(this, this._jsmpegCanvasElement);
|
||||||
resolve(player);
|
resolve(player);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import '../patches/ha-hls-player';
|
|||||||
import './surround-thumbnails';
|
import './surround-thumbnails';
|
||||||
import { EmblaCarouselPlugins } from './carousel.js';
|
import { EmblaCarouselPlugins } from './carousel.js';
|
||||||
import { renderTask } from '../utils/task.js';
|
import { renderTask } from '../utils/task.js';
|
||||||
|
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
||||||
|
|
||||||
@customElement('frigate-card-viewer')
|
@customElement('frigate-card-viewer')
|
||||||
export class FrigateCardViewer extends LitElement {
|
export class FrigateCardViewer extends LitElement {
|
||||||
@@ -573,6 +574,16 @@ export class FrigateCardViewerCarousel extends LitElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an update will occur.
|
||||||
|
* @param changedProps The changed properties
|
||||||
|
*/
|
||||||
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
|
if (changedProps.has('viewerConfig')) {
|
||||||
|
updateElementStyleFromMediaLayoutConfig(this, this.viewerConfig?.layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the element, resolving the media first if necessary.
|
* Render the element, resolving the media first if necessary.
|
||||||
*/
|
*/
|
||||||
@@ -664,6 +675,12 @@ export class FrigateCardViewerCarousel extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single media item in the viewer carousel.
|
||||||
|
* @param mediaToRender The FrigateBrowseMediaSource to render.
|
||||||
|
* @param slideIndex The index of the slide to render.
|
||||||
|
* @returns A rendered template.
|
||||||
|
*/
|
||||||
protected _renderMediaItem(
|
protected _renderMediaItem(
|
||||||
mediaToRender: FrigateBrowseMediaSource,
|
mediaToRender: FrigateBrowseMediaSource,
|
||||||
slideIndex: number,
|
slideIndex: number,
|
||||||
|
|||||||
+13
-1
@@ -88,6 +88,11 @@ export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE =
|
|||||||
`${CONF_MEDIA_VIEWER}.controls.title.mode` as const;
|
`${CONF_MEDIA_VIEWER}.controls.title.mode` as const;
|
||||||
export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
|
export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
|
||||||
`${CONF_MEDIA_VIEWER}.controls.title.duration_seconds` as const;
|
`${CONF_MEDIA_VIEWER}.controls.title.duration_seconds` as const;
|
||||||
|
export const CONF_MEDIA_VIEWER_LAYOUT_FIT = `${CONF_MEDIA_VIEWER}.layout.fit` as const;
|
||||||
|
export const CONF_MEDIA_VIEWER_LAYOUT_POSITION_X =
|
||||||
|
`${CONF_MEDIA_VIEWER}.layout.position.x` as const;
|
||||||
|
export const CONF_MEDIA_VIEWER_LAYOUT_POSITION_Y =
|
||||||
|
`${CONF_MEDIA_VIEWER}.layout.position.y` as const;
|
||||||
|
|
||||||
export const CONF_LIVE = 'live' as const;
|
export const CONF_LIVE = 'live' as const;
|
||||||
export const CONF_LIVE_AUTO_PLAY = `${CONF_LIVE}.auto_play` as const;
|
export const CONF_LIVE_AUTO_PLAY = `${CONF_LIVE}.auto_play` as const;
|
||||||
@@ -113,16 +118,23 @@ export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL =
|
|||||||
export const CONF_LIVE_CONTROLS_TITLE_MODE = `${CONF_LIVE}.controls.title.mode` as const;
|
export const CONF_LIVE_CONTROLS_TITLE_MODE = `${CONF_LIVE}.controls.title.mode` as const;
|
||||||
export const CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS =
|
export const CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS =
|
||||||
`${CONF_LIVE}.controls.title.duration_seconds` as const;
|
`${CONF_LIVE}.controls.title.duration_seconds` as const;
|
||||||
|
export const CONF_LIVE_LAYOUT_FIT = `${CONF_LIVE}.layout.fit` as const;
|
||||||
|
export const CONF_LIVE_LAYOUT_POSITION_X = `${CONF_LIVE}.layout.position.x` as const;
|
||||||
|
export const CONF_LIVE_LAYOUT_POSITION_Y = `${CONF_LIVE}.layout.position.y` as const;
|
||||||
export const CONF_LIVE_DRAGGABLE = `${CONF_LIVE}.draggable` as const;
|
export const CONF_LIVE_DRAGGABLE = `${CONF_LIVE}.draggable` as const;
|
||||||
export const CONF_LIVE_JSMPEG = `${CONF_LIVE}.jsmpeg` as const;
|
export const CONF_LIVE_JSMPEG = `${CONF_LIVE}.jsmpeg` as const;
|
||||||
export const CONF_LIVE_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const;
|
export const CONF_LIVE_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const;
|
||||||
export const CONF_LIVE_LAZY_UNLOAD = `${CONF_LIVE}.lazy_unload` as const;
|
export const CONF_LIVE_LAZY_UNLOAD = `${CONF_LIVE}.lazy_unload` as const;
|
||||||
export const CONF_LIVE_PRELOAD = `${CONF_LIVE}.preload` as const;
|
export const CONF_LIVE_PRELOAD = `${CONF_LIVE}.preload` as const;
|
||||||
export const CONF_LIVE_TRANSITION_EFFECT = `${CONF_LIVE}.transition_effect` as const;
|
export const CONF_LIVE_TRANSITION_EFFECT = `${CONF_LIVE}.transition_effect` as const;
|
||||||
export const CONF_LIVE_SHOW_IMAGE_DURING_LOAD = `${CONF_LIVE}.show_image_during_load` as const;
|
export const CONF_LIVE_SHOW_IMAGE_DURING_LOAD =
|
||||||
|
`${CONF_LIVE}.show_image_during_load` as const;
|
||||||
export const CONF_LIVE_WEBRTC_CARD = `${CONF_LIVE}.webrtc_card` as const;
|
export const CONF_LIVE_WEBRTC_CARD = `${CONF_LIVE}.webrtc_card` as const;
|
||||||
|
|
||||||
export const CONF_IMAGE = 'image' as const;
|
export const CONF_IMAGE = 'image' as const;
|
||||||
|
export const CONF_IMAGE_LAYOUT_FIT = `${CONF_IMAGE}.layout.fit` as const;
|
||||||
|
export const CONF_IMAGE_LAYOUT_POSITION_X = `${CONF_IMAGE}.layout.position.x` as const;
|
||||||
|
export const CONF_IMAGE_LAYOUT_POSITION_Y = `${CONF_IMAGE}.layout.position.y` as const;
|
||||||
export const CONF_IMAGE_MODE = `${CONF_IMAGE}.mode` as const;
|
export const CONF_IMAGE_MODE = `${CONF_IMAGE}.mode` as const;
|
||||||
export const CONF_IMAGE_REFRESH_SECONDS = `${CONF_IMAGE}.refresh_seconds` as const;
|
export const CONF_IMAGE_REFRESH_SECONDS = `${CONF_IMAGE}.refresh_seconds` as const;
|
||||||
export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const;
|
export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const;
|
||||||
|
|||||||
+117
-38
@@ -36,6 +36,9 @@ import {
|
|||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
|
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
|
||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
|
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
|
||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE,
|
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE,
|
||||||
|
CONF_IMAGE_LAYOUT_FIT,
|
||||||
|
CONF_IMAGE_LAYOUT_POSITION_X,
|
||||||
|
CONF_IMAGE_LAYOUT_POSITION_Y,
|
||||||
CONF_IMAGE_MODE,
|
CONF_IMAGE_MODE,
|
||||||
CONF_IMAGE_REFRESH_SECONDS,
|
CONF_IMAGE_REFRESH_SECONDS,
|
||||||
CONF_IMAGE_URL,
|
CONF_IMAGE_URL,
|
||||||
@@ -54,6 +57,9 @@ import {
|
|||||||
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
|
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
|
||||||
CONF_LIVE_CONTROLS_TITLE_MODE,
|
CONF_LIVE_CONTROLS_TITLE_MODE,
|
||||||
CONF_LIVE_DRAGGABLE,
|
CONF_LIVE_DRAGGABLE,
|
||||||
|
CONF_LIVE_LAYOUT_FIT,
|
||||||
|
CONF_LIVE_LAYOUT_POSITION_X,
|
||||||
|
CONF_LIVE_LAYOUT_POSITION_Y,
|
||||||
CONF_LIVE_LAZY_LOAD,
|
CONF_LIVE_LAZY_LOAD,
|
||||||
CONF_LIVE_LAZY_UNLOAD,
|
CONF_LIVE_LAZY_UNLOAD,
|
||||||
CONF_LIVE_PRELOAD,
|
CONF_LIVE_PRELOAD,
|
||||||
@@ -73,6 +79,9 @@ import {
|
|||||||
CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
|
CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE,
|
CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE,
|
||||||
CONF_MEDIA_VIEWER_DRAGGABLE,
|
CONF_MEDIA_VIEWER_DRAGGABLE,
|
||||||
|
CONF_MEDIA_VIEWER_LAYOUT_FIT,
|
||||||
|
CONF_MEDIA_VIEWER_LAYOUT_POSITION_X,
|
||||||
|
CONF_MEDIA_VIEWER_LAYOUT_POSITION_Y,
|
||||||
CONF_MEDIA_VIEWER_LAZY_LOAD,
|
CONF_MEDIA_VIEWER_LAZY_LOAD,
|
||||||
CONF_MEDIA_VIEWER_TRANSITION_EFFECT,
|
CONF_MEDIA_VIEWER_TRANSITION_EFFECT,
|
||||||
CONF_MENU_ALIGNMENT,
|
CONF_MENU_ALIGNMENT,
|
||||||
@@ -125,8 +134,11 @@ const MENU_CAMERAS_FRIGATE = 'cameras.frigate';
|
|||||||
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
|
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
|
||||||
const MENU_CAMERAS_WEBRTC = 'cameras.webrtc';
|
const MENU_CAMERAS_WEBRTC = 'cameras.webrtc';
|
||||||
const MENU_EVENT_GALLERY_CONTROLS = 'event_gallery.controls';
|
const MENU_EVENT_GALLERY_CONTROLS = 'event_gallery.controls';
|
||||||
|
const MENU_IMAGE_LAYOUT = 'image.layout';
|
||||||
const MENU_LIVE_CONTROLS = 'live.controls';
|
const MENU_LIVE_CONTROLS = 'live.controls';
|
||||||
|
const MENU_LIVE_LAYOUT = 'live.layout';
|
||||||
const MENU_MEDIA_VIEWER_CONTROLS = 'media_viewer.controls';
|
const MENU_MEDIA_VIEWER_CONTROLS = 'media_viewer.controls';
|
||||||
|
const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout';
|
||||||
const MENU_TIMELINE_CONTROLS = 'timeline.controls';
|
const MENU_TIMELINE_CONTROLS = 'timeline.controls';
|
||||||
const MENU_OPTIONS = 'options';
|
const MENU_OPTIONS = 'options';
|
||||||
const MENU_VIEW_SCAN = 'scan';
|
const MENU_VIEW_SCAN = 'scan';
|
||||||
@@ -402,6 +414,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
{ value: 'never', label: localize('config.common.media_action_conditions.never') },
|
{ value: 'never', label: localize('config.common.media_action_conditions.never') },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected _layoutFits: EditorSelectOption[] = [
|
||||||
|
{ value: '', label: '' },
|
||||||
|
{ value: 'contain', label: localize('config.common.layout.fits.contain') },
|
||||||
|
{ value: 'cover', label: localize('config.common.layout.fits.cover') },
|
||||||
|
{ value: 'fill', label: localize('config.common.layout.fits.fill') },
|
||||||
|
];
|
||||||
|
|
||||||
public setConfig(config: RawFrigateCardConfig): void {
|
public setConfig(config: RawFrigateCardConfig): void {
|
||||||
// Note: This does not use Zod to parse the configuration, so it may be
|
// Note: This does not use Zod to parse the configuration, so it may be
|
||||||
// partially or completely invalid. It's more useful to have a partially
|
// partially or completely invalid. It's more useful to have a partially
|
||||||
@@ -763,6 +782,43 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a media layout section.
|
||||||
|
* @param domain The submenu domain.
|
||||||
|
* @param labelPath The path to the label.
|
||||||
|
* @param configPathFit The path to the fit config.
|
||||||
|
* @param configPathPositionX The path to the position.x config.
|
||||||
|
* @param configPathPositionY The path to the position.y config.
|
||||||
|
* @returns A rendered template.
|
||||||
|
*/
|
||||||
|
protected _renderMediaLayout(
|
||||||
|
domain: string,
|
||||||
|
labelPath: string,
|
||||||
|
configPathFit: string,
|
||||||
|
configPathPositionX: string,
|
||||||
|
configPathPositionY: string,
|
||||||
|
): TemplateResult | void {
|
||||||
|
return this._putInSubmenu(
|
||||||
|
domain,
|
||||||
|
true,
|
||||||
|
labelPath,
|
||||||
|
{ name: 'mdi:page-layout-body' },
|
||||||
|
html`
|
||||||
|
${this._renderOptionSelector(configPathFit, this._layoutFits)}
|
||||||
|
${this._renderNumberInput(configPathPositionX, {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
label: localize('config.common.layout.position.x'),
|
||||||
|
})}
|
||||||
|
${this._renderNumberInput(configPathPositionY, {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
label: localize('config.common.layout.position.y'),
|
||||||
|
})}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a camera section.
|
* Render a camera section.
|
||||||
* @param cameras The full array of cameras.
|
* @param cameras The full array of cameras.
|
||||||
@@ -1258,6 +1314,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
)}
|
)}
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
${this._renderMediaLayout(
|
||||||
|
MENU_LIVE_LAYOUT,
|
||||||
|
'config.live.layout',
|
||||||
|
CONF_LIVE_LAYOUT_FIT,
|
||||||
|
CONF_LIVE_LAYOUT_POSITION_X,
|
||||||
|
CONF_LIVE_LAYOUT_POSITION_Y,
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
@@ -1327,44 +1390,53 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
true,
|
true,
|
||||||
'config.media_viewer.controls.options',
|
'config.media_viewer.controls.options',
|
||||||
{ name: 'mdi:gamepad' },
|
{ name: 'mdi:gamepad' },
|
||||||
html` ${this._renderOptionSelector(
|
html`
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
${this._renderOptionSelector(
|
||||||
this._eventViewerNextPreviousControlStyles,
|
CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||||
)}
|
this._eventViewerNextPreviousControlStyles,
|
||||||
${this._renderNumberInput(
|
)}
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
${this._renderNumberInput(
|
||||||
{
|
CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
||||||
min: BUTTON_SIZE_MIN,
|
{
|
||||||
},
|
min: BUTTON_SIZE_MIN,
|
||||||
)}
|
},
|
||||||
${this._renderOptionSelector(
|
)}
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE,
|
${this._renderOptionSelector(
|
||||||
this._thumbnailModes,
|
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE,
|
||||||
)}
|
this._thumbnailModes,
|
||||||
${this._renderNumberInput(CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE, {
|
)}
|
||||||
min: THUMBNAIL_WIDTH_MIN,
|
${this._renderNumberInput(CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE, {
|
||||||
max: THUMBNAIL_WIDTH_MAX,
|
min: THUMBNAIL_WIDTH_MIN,
|
||||||
})}
|
max: THUMBNAIL_WIDTH_MAX,
|
||||||
${this._renderSwitch(
|
})}
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
${this._renderSwitch(
|
||||||
defaults.media_viewer.controls.thumbnails.show_details,
|
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
||||||
)}
|
defaults.media_viewer.controls.thumbnails.show_details,
|
||||||
${this._renderSwitch(
|
)}
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
|
${this._renderSwitch(
|
||||||
defaults.media_viewer.controls.thumbnails.show_favorite_control,
|
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
|
||||||
)}
|
defaults.media_viewer.controls.thumbnails.show_favorite_control,
|
||||||
${this._renderSwitch(
|
)}
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
|
${this._renderSwitch(
|
||||||
defaults.media_viewer.controls.thumbnails.show_timeline_control,
|
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
|
||||||
)}
|
defaults.media_viewer.controls.thumbnails.show_timeline_control,
|
||||||
${this._renderOptionSelector(
|
)}
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE,
|
${this._renderOptionSelector(
|
||||||
this._titleModes,
|
CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE,
|
||||||
)}
|
this._titleModes,
|
||||||
${this._renderNumberInput(
|
)}
|
||||||
CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
|
${this._renderNumberInput(
|
||||||
{ min: 0, max: 60 },
|
CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
|
||||||
)}`,
|
{ min: 0, max: 60 },
|
||||||
|
)}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
${this._renderMediaLayout(
|
||||||
|
MENU_MEDIA_VIEWER_LAYOUT,
|
||||||
|
'config.media_viewer.layout',
|
||||||
|
CONF_MEDIA_VIEWER_LAYOUT_FIT,
|
||||||
|
CONF_MEDIA_VIEWER_LAYOUT_POSITION_X,
|
||||||
|
CONF_MEDIA_VIEWER_LAYOUT_POSITION_Y,
|
||||||
)}
|
)}
|
||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
@@ -1374,6 +1446,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
${this._renderOptionSelector(CONF_IMAGE_MODE, this._imageModes)}
|
${this._renderOptionSelector(CONF_IMAGE_MODE, this._imageModes)}
|
||||||
${this._renderStringInput(CONF_IMAGE_URL)}
|
${this._renderStringInput(CONF_IMAGE_URL)}
|
||||||
${this._renderNumberInput(CONF_IMAGE_REFRESH_SECONDS)}
|
${this._renderNumberInput(CONF_IMAGE_REFRESH_SECONDS)}
|
||||||
|
${this._renderMediaLayout(
|
||||||
|
MENU_IMAGE_LAYOUT,
|
||||||
|
'config.image.layout',
|
||||||
|
CONF_IMAGE_LAYOUT_FIT,
|
||||||
|
CONF_IMAGE_LAYOUT_POSITION_X,
|
||||||
|
CONF_IMAGE_LAYOUT_POSITION_Y,
|
||||||
|
)}
|
||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('timeline')}
|
${this._renderOptionSetHeader('timeline')}
|
||||||
|
|||||||
@@ -56,6 +56,18 @@
|
|||||||
"selected": "On selection",
|
"selected": "On selection",
|
||||||
"unselected": "On unselection",
|
"unselected": "On unselection",
|
||||||
"visible": "On browser/tab visibility"
|
"visible": "On browser/tab visibility"
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"fit": "Layout fit",
|
||||||
|
"fits": {
|
||||||
|
"cover": "Media expands proportionally to cover the card",
|
||||||
|
"contain": "Media is contained/letterboxed",
|
||||||
|
"fill": "Media is stretched to fill the card"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"x": "Horizontal placement percentage",
|
||||||
|
"y": "Vertical placement percentage"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
@@ -79,6 +91,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"image": {
|
"image": {
|
||||||
|
"layout": "Image Layout",
|
||||||
"mode": "Image view mode",
|
"mode": "Image view mode",
|
||||||
"modes": {
|
"modes": {
|
||||||
"camera": "Home Assistant camera snapshot of camera entity",
|
"camera": "Home Assistant camera snapshot of camera entity",
|
||||||
@@ -122,6 +135,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"draggable": "Live cameras view can be dragged/swiped",
|
"draggable": "Live cameras view can be dragged/swiped",
|
||||||
|
"layout": "Live Layout",
|
||||||
"lazy_load": "Live cameras are lazily loaded",
|
"lazy_load": "Live cameras are lazily loaded",
|
||||||
"lazy_unload": "Live cameras are lazily unloaded",
|
"lazy_unload": "Live cameras are lazily unloaded",
|
||||||
"preload": "Preload live view in the background",
|
"preload": "Preload live view in the background",
|
||||||
@@ -172,6 +186,7 @@
|
|||||||
},
|
},
|
||||||
"draggable": "Media Viewer can be dragged/swiped",
|
"draggable": "Media Viewer can be dragged/swiped",
|
||||||
"lazy_load": "Media Viewer media is lazily loaded in carousel",
|
"lazy_load": "Media Viewer media is lazily loaded in carousel",
|
||||||
|
"layout": "Media Viewer Layout",
|
||||||
"transition_effect": "Media Viewer transition effect",
|
"transition_effect": "Media Viewer transition effect",
|
||||||
"transition_effects": {
|
"transition_effects": {
|
||||||
"none": "No transition",
|
"none": "No transition",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-nocheck
|
//@ts-nocheck
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// ** Keep modifications to this file to a minimum **
|
// ** Keep modifications to this file to a minimum **
|
||||||
@@ -9,13 +9,14 @@
|
|||||||
// available as compilation time.
|
// available as compilation time.
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
|
|
||||||
import { css, html, TemplateResult } from 'lit';
|
import { css, CSSResultGroup, html, unsafeCSS, TemplateResult } from 'lit';
|
||||||
import { customElement } from 'lit/decorators.js';
|
import { customElement } from 'lit/decorators.js';
|
||||||
import { query } from 'lit/decorators/query.js';
|
import { query } from 'lit/decorators/query.js';
|
||||||
import { FrigateCardMediaPlayer } from '../types.js';
|
import { FrigateCardMediaPlayer } from '../types.js';
|
||||||
import { dispatchMediaShowEvent } from '../utils/media-info.js';
|
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
||||||
import "./ha-hls-player";
|
import './ha-hls-player';
|
||||||
import "./ha-web-rtc-player";
|
import './ha-web-rtc-player';
|
||||||
|
import liveHAComponentsStyle from '../scss/live-ha-components.scss';
|
||||||
|
|
||||||
customElements.whenDefined('ha-camera-stream').then(() => {
|
customElements.whenDefined('ha-camera-stream').then(() => {
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
@@ -98,8 +99,8 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
if (this._shouldRenderMJPEG) {
|
if (this._shouldRenderMJPEG) {
|
||||||
return html`
|
return html`
|
||||||
<img
|
<img
|
||||||
@load=${(e) => {
|
@load=${(ev: Event) => {
|
||||||
dispatchMediaShowEvent(this, e);
|
dispatchMediaLoadedEvent(this, ev);
|
||||||
}}
|
}}
|
||||||
.src=${typeof this._connected == 'undefined' || this._connected
|
.src=${typeof this._connected == 'undefined' || this._connected
|
||||||
? computeMJPEGStreamUrl(this.stateObj)
|
? computeMJPEGStreamUrl(this.stateObj)
|
||||||
@@ -138,6 +139,7 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
super.styles,
|
super.styles,
|
||||||
|
unsafeCSS(liveHAComponentsStyle),
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -146,7 +148,6 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@@ -155,7 +156,7 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"frigate-card-ha-camera-stream": FrigateCardHaCameraStream
|
'frigate-card-ha-camera-stream': FrigateCardHaCameraStream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,12 @@
|
|||||||
// available as compilation time.
|
// available as compilation time.
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
|
|
||||||
import { css, html, TemplateResult } from 'lit';
|
import { css, CSSResultGroup, html, unsafeCSS, TemplateResult } from 'lit';
|
||||||
import { customElement } from 'lit/decorators.js';
|
import { customElement } from 'lit/decorators.js';
|
||||||
import { query } from 'lit/decorators/query.js';
|
import { query } from 'lit/decorators/query.js';
|
||||||
import { dispatchErrorMessageEvent } from '../components/message.js';
|
import { dispatchErrorMessageEvent } from '../components/message.js';
|
||||||
import { dispatchMediaShowEvent } from '../utils/media-info.js';
|
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
||||||
|
import liveHAComponentsStyle from '../scss/live-ha-components.scss';
|
||||||
|
|
||||||
customElements.whenDefined('ha-hls-player').then(() => {
|
customElements.whenDefined('ha-hls-player').then(() => {
|
||||||
@customElement('frigate-card-ha-hls-player')
|
@customElement('frigate-card-ha-hls-player')
|
||||||
@@ -85,7 +86,7 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
|||||||
?playsinline=${this.playsInline}
|
?playsinline=${this.playsInline}
|
||||||
?controls=${this.controls}
|
?controls=${this.controls}
|
||||||
@loadeddata=${(e) => {
|
@loadeddata=${(e) => {
|
||||||
dispatchMediaShowEvent(this, e);
|
dispatchMediaLoadedEvent(this, e);
|
||||||
}}
|
}}
|
||||||
></video>
|
></video>
|
||||||
`;
|
`;
|
||||||
@@ -94,15 +95,15 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
super.styles,
|
super.styles,
|
||||||
|
unsafeCSS(liveHAComponentsStyle),
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
video {
|
video {
|
||||||
object-fit: contain;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -9,11 +9,12 @@
|
|||||||
// available as compilation time.
|
// available as compilation time.
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
|
|
||||||
import { html, TemplateResult } from 'lit';
|
import { css, CSSResultGroup, html, unsafeCSS, TemplateResult } from 'lit';
|
||||||
import { customElement } from 'lit/decorators.js';
|
import { customElement } from 'lit/decorators.js';
|
||||||
import { query } from 'lit/decorators/query.js';
|
import { query } from 'lit/decorators/query.js';
|
||||||
import { dispatchErrorMessageEvent } from '../components/message.js';
|
import { dispatchErrorMessageEvent } from '../components/message.js';
|
||||||
import { dispatchMediaShowEvent } from '../utils/media-info.js';
|
import { dispatchMediaLoadedEvent } from '../utils/media-info.js';
|
||||||
|
import liveHAComponentsStyle from '../scss/live-ha-components.scss';
|
||||||
|
|
||||||
customElements.whenDefined('ha-web-rtc-player').then(() => {
|
customElements.whenDefined('ha-web-rtc-player').then(() => {
|
||||||
@customElement('frigate-card-ha-web-rtc-player')
|
@customElement('frigate-card-ha-web-rtc-player')
|
||||||
@@ -86,11 +87,28 @@ customElements.whenDefined('ha-web-rtc-player').then(() => {
|
|||||||
?playsinline=${this.playsInline}
|
?playsinline=${this.playsInline}
|
||||||
?controls=${this.controls}
|
?controls=${this.controls}
|
||||||
@loadeddata=${(e) => {
|
@loadeddata=${(e) => {
|
||||||
dispatchMediaShowEvent(this, e);
|
dispatchMediaLoadedEvent(this, e);
|
||||||
}}
|
}}
|
||||||
></video>
|
></video>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
super.styles,
|
||||||
|
unsafeCSS(liveHAComponentsStyle),
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
:host {
|
:host {
|
||||||
--frigate-card-thumbnail-size-max: 175px;
|
--frigate-card-thumbnail-size-max: 175px;
|
||||||
--frigate-card-thumbnail-details-width: calc(var(--frigate-card-thumbnail-size) + 200px);
|
--frigate-card-thumbnail-details-width: calc(
|
||||||
|
var(--frigate-card-thumbnail-size) + 200px
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,11 @@
|
|||||||
background: var(--secondary-background-color);
|
background: var(--secondary-background-color);
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
// Space submenus from the next top-level options.
|
||||||
|
.values + .option,
|
||||||
|
.submenu + .option {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
div.upgrade {
|
div.upgrade {
|
||||||
width: auto;
|
width: auto;
|
||||||
border: 1px dotted var(--primary-color);
|
border: 1px dotted var(--primary-color);
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,9 @@
|
|||||||
|
@use 'media-layout.scss';
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
object-fit: scale-down;
|
|
||||||
|
@include media-layout.media-layout();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@use 'media-layout.scss';
|
||||||
|
|
||||||
|
img, video {
|
||||||
|
@include media-layout.media-layout();
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@use 'media-layout.scss';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -8,5 +10,6 @@ canvas {
|
|||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
|
||||||
}
|
@include media-layout.media-layout();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@use 'media-layout.scss';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -12,4 +14,8 @@
|
|||||||
// To get the WebRTC player to line up correctly with some themes.
|
// To get the WebRTC player to line up correctly with some themes.
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#webrtc #video {
|
||||||
|
@include media-layout.media-layout();
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@mixin media-layout {
|
||||||
|
object-fit: var(--frigate-card-media-layout-fit, contain);
|
||||||
|
object-position: var(--frigate-card-media-layout-position-x, 50%)
|
||||||
|
var(--frigate-card-media-layout-position-y, 50%);
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@use 'media-layout.scss';
|
||||||
|
|
||||||
.embla__slide {
|
.embla__slide {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100%;
|
||||||
@@ -8,7 +10,5 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
// Letterbox media. <frigate-card-ha-hls-player> has similar added directly in
|
@include media-layout.media-layout();
|
||||||
// its element.
|
}
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|||||||
+58
-30
@@ -165,9 +165,11 @@ const moreInfoActionSchema = schemaForType<
|
|||||||
const customActionSchema = schemaForType<
|
const customActionSchema = schemaForType<
|
||||||
CustomActionConfig & ExtendedConfirmationRestrictionConfig
|
CustomActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
>()(
|
>()(
|
||||||
actionBaseSchema.extend({
|
actionBaseSchema
|
||||||
action: z.literal('fire-dom-event'),
|
.extend({
|
||||||
}).passthrough(),
|
action: z.literal('fire-dom-event'),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
);
|
);
|
||||||
const noActionSchema = schemaForType<
|
const noActionSchema = schemaForType<
|
||||||
NoActionConfig & ExtendedConfirmationRestrictionConfig
|
NoActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
@@ -519,6 +521,18 @@ export type PictureElement = z.infer<typeof pictureElementSchema>;
|
|||||||
const pictureElementsSchema = pictureElementSchema.array().optional();
|
const pictureElementsSchema = pictureElementSchema.array().optional();
|
||||||
export type PictureElements = z.infer<typeof pictureElementsSchema>;
|
export type PictureElements = z.infer<typeof pictureElementsSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media layout configuration section.
|
||||||
|
*/
|
||||||
|
const mediaLayoutConfigSchema = z.object({
|
||||||
|
fit: z.enum(['contain', 'cover', 'fill']).optional(),
|
||||||
|
position: z.object({
|
||||||
|
x: z.number().min(0).max(100).optional(),
|
||||||
|
y: z.number().min(0).max(100).optional(),
|
||||||
|
}).optional(),
|
||||||
|
});
|
||||||
|
export type MediaLayoutConfig = z.infer<typeof mediaLayoutConfigSchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View configuration section.
|
* View configuration section.
|
||||||
*/
|
*/
|
||||||
@@ -580,6 +594,7 @@ const imageConfigSchema = z
|
|||||||
mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode),
|
mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds),
|
refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds),
|
||||||
|
layout: mediaLayoutConfigSchema.optional(),
|
||||||
})
|
})
|
||||||
.merge(actionsSchema)
|
.merge(actionsSchema)
|
||||||
.default(imageConfigDefault);
|
.default(imageConfigDefault);
|
||||||
@@ -717,12 +732,14 @@ const liveOverridableConfigSchema = z
|
|||||||
show_details: thumbnailsControlSchema.shape.show_details.default(
|
show_details: thumbnailsControlSchema.shape.show_details.default(
|
||||||
liveConfigDefault.controls.thumbnails.show_details,
|
liveConfigDefault.controls.thumbnails.show_details,
|
||||||
),
|
),
|
||||||
show_favorite_control: thumbnailsControlSchema.shape.show_favorite_control.default(
|
show_favorite_control:
|
||||||
liveConfigDefault.controls.thumbnails.show_favorite_control,
|
thumbnailsControlSchema.shape.show_favorite_control.default(
|
||||||
),
|
liveConfigDefault.controls.thumbnails.show_favorite_control,
|
||||||
show_timeline_control: thumbnailsControlSchema.shape.show_timeline_control.default(
|
),
|
||||||
liveConfigDefault.controls.thumbnails.show_timeline_control,
|
show_timeline_control:
|
||||||
),
|
thumbnailsControlSchema.shape.show_timeline_control.default(
|
||||||
|
liveConfigDefault.controls.thumbnails.show_timeline_control,
|
||||||
|
),
|
||||||
media: z
|
media: z
|
||||||
.enum(['clips', 'snapshots'])
|
.enum(['clips', 'snapshots'])
|
||||||
.default(liveConfigDefault.controls.thumbnails.media),
|
.default(liveConfigDefault.controls.thumbnails.media),
|
||||||
@@ -740,7 +757,10 @@ const liveOverridableConfigSchema = z
|
|||||||
.default(liveConfigDefault.controls.title),
|
.default(liveConfigDefault.controls.title),
|
||||||
})
|
})
|
||||||
.default(liveConfigDefault.controls),
|
.default(liveConfigDefault.controls),
|
||||||
show_image_during_load: z.boolean().default(liveConfigDefault.show_image_during_load),
|
show_image_during_load: z
|
||||||
|
.boolean()
|
||||||
|
.default(liveConfigDefault.show_image_during_load),
|
||||||
|
layout: mediaLayoutConfigSchema.optional(),
|
||||||
})
|
})
|
||||||
.merge(actionsSchema);
|
.merge(actionsSchema);
|
||||||
|
|
||||||
@@ -917,12 +937,14 @@ const viewerConfigSchema = z
|
|||||||
show_details: thumbnailsControlSchema.shape.show_details.default(
|
show_details: thumbnailsControlSchema.shape.show_details.default(
|
||||||
viewerConfigDefault.controls.thumbnails.show_details,
|
viewerConfigDefault.controls.thumbnails.show_details,
|
||||||
),
|
),
|
||||||
show_favorite_control: thumbnailsControlSchema.shape.show_favorite_control.default(
|
show_favorite_control:
|
||||||
viewerConfigDefault.controls.thumbnails.show_favorite_control,
|
thumbnailsControlSchema.shape.show_favorite_control.default(
|
||||||
),
|
viewerConfigDefault.controls.thumbnails.show_favorite_control,
|
||||||
show_timeline_control: thumbnailsControlSchema.shape.show_timeline_control.default(
|
),
|
||||||
viewerConfigDefault.controls.thumbnails.show_timeline_control,
|
show_timeline_control:
|
||||||
),
|
thumbnailsControlSchema.shape.show_timeline_control.default(
|
||||||
|
viewerConfigDefault.controls.thumbnails.show_timeline_control,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.default(viewerConfigDefault.controls.thumbnails),
|
.default(viewerConfigDefault.controls.thumbnails),
|
||||||
title: titleControlConfigSchema
|
title: titleControlConfigSchema
|
||||||
@@ -937,6 +959,7 @@ const viewerConfigSchema = z
|
|||||||
.default(viewerConfigDefault.controls.title),
|
.default(viewerConfigDefault.controls.title),
|
||||||
})
|
})
|
||||||
.default(viewerConfigDefault.controls),
|
.default(viewerConfigDefault.controls),
|
||||||
|
layout: mediaLayoutConfigSchema.optional(),
|
||||||
})
|
})
|
||||||
.merge(actionsSchema)
|
.merge(actionsSchema)
|
||||||
.default(viewerConfigDefault);
|
.default(viewerConfigDefault);
|
||||||
@@ -970,12 +993,14 @@ const galleryConfigSchema = z
|
|||||||
show_details: thumbnailsControlSchema.shape.show_details.default(
|
show_details: thumbnailsControlSchema.shape.show_details.default(
|
||||||
galleryConfigDefault.controls.thumbnails.show_details,
|
galleryConfigDefault.controls.thumbnails.show_details,
|
||||||
),
|
),
|
||||||
show_favorite_control: thumbnailsControlSchema.shape.show_favorite_control.default(
|
show_favorite_control:
|
||||||
galleryConfigDefault.controls.thumbnails.show_favorite_control,
|
thumbnailsControlSchema.shape.show_favorite_control.default(
|
||||||
),
|
galleryConfigDefault.controls.thumbnails.show_favorite_control,
|
||||||
show_timeline_control: thumbnailsControlSchema.shape.show_timeline_control.default(
|
),
|
||||||
galleryConfigDefault.controls.thumbnails.show_timeline_control,
|
show_timeline_control:
|
||||||
),
|
thumbnailsControlSchema.shape.show_timeline_control.default(
|
||||||
|
galleryConfigDefault.controls.thumbnails.show_timeline_control,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.default(galleryConfigDefault.controls.thumbnails),
|
.default(galleryConfigDefault.controls.thumbnails),
|
||||||
})
|
})
|
||||||
@@ -1062,12 +1087,14 @@ const timelineConfigSchema = z
|
|||||||
show_details: thumbnailsControlSchema.shape.show_details.default(
|
show_details: thumbnailsControlSchema.shape.show_details.default(
|
||||||
timelineConfigDefault.controls.thumbnails.show_details,
|
timelineConfigDefault.controls.thumbnails.show_details,
|
||||||
),
|
),
|
||||||
show_favorite_control: thumbnailsControlSchema.shape.show_favorite_control.default(
|
show_favorite_control:
|
||||||
timelineConfigDefault.controls.thumbnails.show_favorite_control,
|
thumbnailsControlSchema.shape.show_favorite_control.default(
|
||||||
),
|
timelineConfigDefault.controls.thumbnails.show_favorite_control,
|
||||||
show_timeline_control: thumbnailsControlSchema.shape.show_timeline_control.default(
|
),
|
||||||
timelineConfigDefault.controls.thumbnails.show_timeline_control,
|
show_timeline_control:
|
||||||
),
|
thumbnailsControlSchema.shape.show_timeline_control.default(
|
||||||
|
timelineConfigDefault.controls.thumbnails.show_timeline_control,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.default(timelineConfigDefault.controls.thumbnails),
|
.default(timelineConfigDefault.controls.thumbnails),
|
||||||
})
|
})
|
||||||
@@ -1086,6 +1113,7 @@ const overrideConfigurationSchema = z.object({
|
|||||||
menu: deepRemoveDefaults(menuConfigSchema).optional(),
|
menu: deepRemoveDefaults(menuConfigSchema).optional(),
|
||||||
image: deepRemoveDefaults(imageConfigSchema).optional(),
|
image: deepRemoveDefaults(imageConfigSchema).optional(),
|
||||||
view: deepRemoveDefaults(viewConfigSchema).optional(),
|
view: deepRemoveDefaults(viewConfigSchema).optional(),
|
||||||
|
dimensions: deepRemoveDefaults(dimensionsConfigSchema).optional(),
|
||||||
});
|
});
|
||||||
export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>;
|
export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>;
|
||||||
|
|
||||||
@@ -1213,13 +1241,13 @@ export const MESSAGE_TYPE_PRIORITIES = {
|
|||||||
error: 20,
|
error: 20,
|
||||||
connection: 30,
|
connection: 30,
|
||||||
diagnostics: 40,
|
diagnostics: 40,
|
||||||
}
|
};
|
||||||
|
|
||||||
export type MessageType = 'info' | 'error' | 'connection' | 'diagnostics';
|
export type MessageType = 'info' | 'error' | 'connection' | 'diagnostics';
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
message: string;
|
message: string;
|
||||||
type: MessageType,
|
type: MessageType;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
context?: unknown;
|
context?: unknown;
|
||||||
dotdotdot?: boolean;
|
dotdotdot?: boolean;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function createMediaLoadedInfo(
|
|||||||
* @param element The element to send the event.
|
* @param element The element to send the event.
|
||||||
* @param source An event or HTMLElement that should be used as a source.
|
* @param source An event or HTMLElement that should be used as a source.
|
||||||
*/
|
*/
|
||||||
export function dispatchMediaShowEvent(
|
export function dispatchMediaLoadedEvent(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
source: Event | HTMLElement,
|
source: Event | HTMLElement,
|
||||||
): void {
|
): void {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { MediaLayoutConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update element style from a media configuration.
|
||||||
|
* @param element The element to update the style for.
|
||||||
|
* @param mediaLayoutConfig The media config object.
|
||||||
|
*/
|
||||||
|
export const updateElementStyleFromMediaLayoutConfig = (
|
||||||
|
element: HTMLElement,
|
||||||
|
mediaLayoutConfig?: MediaLayoutConfig,
|
||||||
|
) => {
|
||||||
|
if (mediaLayoutConfig?.fit !== undefined) {
|
||||||
|
element.style.setProperty('--frigate-card-media-layout-fit', mediaLayoutConfig.fit);
|
||||||
|
} else {
|
||||||
|
element.style.removeProperty('--frigate-card-media-layout-fit');
|
||||||
|
}
|
||||||
|
for (const dimension of ['x', 'y']) {
|
||||||
|
if (mediaLayoutConfig?.position?.[dimension] !== undefined) {
|
||||||
|
element.style.setProperty(
|
||||||
|
`--frigate-card-media-layout-position-${dimension}`,
|
||||||
|
`${mediaLayoutConfig.position[dimension]}%`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
element.style.removeProperty(`--frigate-card-media-layout-position-${dimension}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user