Update dimensions and media layout options to be per camera.
This commit is contained in:
@@ -146,6 +146,7 @@ See the [fully expanded cameras configuration example](#config-expanded-cameras)
|
||||
| `triggers` | | :white_check_mark: | Define what should cause this camera to update/trigger. See [camera triggers](#camera-trigger-configuration) below. |
|
||||
| `webrtc_card` | | :white_check_mark: | The WebRTC entity/URL to use for this camera with the `webrtc-card` live provider. See below. |
|
||||
| `cast` | | :white_check_mark: | Configuration that controls how this camera is "casted" / sent to media players. See below. |
|
||||
| `dimensions` | | :white_check_mark: | Controls the dimensions and layout for media from this camera. See below. |
|
||||
|
||||
<a name="live-providers"></a>
|
||||
|
||||
@@ -363,6 +364,39 @@ cameras:
|
||||
| `dashboard_path` | | :white_check_mark: | A required field that specifies the name of the dashboard to cast. You can see this name in your HA URL when you visit the dashboard. |
|
||||
| `view_path` | | :white_check_mark: | A required field that specifies view/"tab" on that dashboard to cast. This is the value you have specified in the `url` field of the view configuration on the dashboard. |
|
||||
|
||||
### Camera Dimensions Configuration
|
||||
|
||||
The `dimensions` block configures the dimensions and layout of media from a given camera.
|
||||
|
||||
```yaml
|
||||
cameras:
|
||||
- dimensions:
|
||||
```
|
||||
|
||||
| Option | Default | Overridable | Description |
|
||||
| - | - | - | - |
|
||||
| `aspect_ratio` | | :white_check_mark: | An optional aspect ratio for media from this camera which will be used in `live` or media viewer related views (e.g. `clip`, `snapshot` and `recording`). Format is the same as the parameter of the same name under the [dimensions block](#dimensions) (which controls dimensions for the whole card), e.g. `16 / 9`. |
|
||||
| `layout` | | :white_check_mark: | How the media should be laid out *within* the camera dimensions. See below. |
|
||||
|
||||
### Camera Dimensions Layout Configuration
|
||||
|
||||
The `layout` block configures the dimensions and layout of media from a given camera.
|
||||
|
||||
```yaml
|
||||
cameras:
|
||||
- dimensions:
|
||||
layout:
|
||||
```
|
||||
|
||||
This block is used to control the fit and position of the media _within_ the camera dimensions (in order to control the dimensions for the whole card see [the card dimensions parameter](#dimensions) ). As the default behavior is to always expand to fit the media precisely, these options only make sense if the camera `dimensions.aspect_ratio` is set to a static value that forces a particular aspect ratio that does not match the camera media.
|
||||
|
||||
| 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. |
|
||||
|
||||
See [media layout examples](#media-layout-examples).
|
||||
|
||||
<a name="camera-ids"></a>
|
||||
|
||||
#### Camera IDs: Referring to cameras in card configuration
|
||||
@@ -533,7 +567,6 @@ See the [fully expanded live configuration example](#config-expanded-live) for h
|
||||
| `show_image_during_load` | `true` | :white_check_mark: | If `true`, during the initial stream load, the `image` live provider will be shown instead of the loading video stream. This still image will auto-refresh and is replaced with the live stream once loaded. |
|
||||
| `actions` | | :white_check_mark: | Actions to use for the `live` view. See [actions](#actions) below.|
|
||||
| `controls` | | :white_check_mark: | Configuration for the `live` view controls. See below. |
|
||||
| `layout` | | :white_check_mark: | See [media layout](#media-layout) below.|
|
||||
| `microphone` | | :white_check_mark: | See [microphone](#microphone) below.|
|
||||
| `display` | | :white_check_mark: | See [display](#live-display) below.|
|
||||
|
||||
@@ -721,7 +754,6 @@ 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`. |
|
||||
| `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.|
|
||||
| `layout` | | :white_check_mark: | See [media layout](#media-layout) below.|
|
||||
|
||||
#### Media Viewer Controls
|
||||
|
||||
@@ -939,8 +971,6 @@ timeline:
|
||||
| `show_favorite_control` | `true` | :heavy_multiplication_x: | Whether to show the favorite ('star') control on each thumbnail.|
|
||||
| `show_timeline_control` | `true` | :heavy_multiplication_x: | Whether to show the timeline ('target') control on each thumbnail.|
|
||||
|
||||
<a name="dimensions"></a>
|
||||
|
||||
#### Timeline Seek Behavior
|
||||
|
||||
The behavior of the timeline during seeking/dragging can be controlled by means of the icon on the bottom-right of the timeline.
|
||||
@@ -952,6 +982,8 @@ The behavior of the timeline during seeking/dragging can be controlled by means
|
||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>camera-lock</title><path d="M4 4H7L9 2H15L17 4H20C21.11 4 22 4.89 22 6V12C21.16 11.37 20.13 11 19 11C18.21 11 17.46 11.18 16.79 11.5C16.18 9.22 14.27 7 12 7C9.24 7 7 9.24 7 12C7 14.76 9.24 17 12 17C12.42 17 12.84 16.95 13.23 16.85C13.08 17.2 13 17.59 13 18V20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.9 4 4 4M12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9M23 18.3V21.8C23 22.4 22.4 23 21.7 23H16.2C15.6 23 15 22.4 15 21.7V18.2C15 17.6 15.6 17 16.2 17V15.5C16.2 14.1 17.6 13 19 13C20.4 13 21.8 14.1 21.8 15.5V17C22.4 17 23 17.6 23 18.3M20.5 15.5C20.5 14.7 19.8 14.2 19 14.2C18.2 14.2 17.5 14.7 17.5 15.5V17H20.5V15.5Z" /></svg> | Dragging the timeline will seek / select across all available media from the selected camera only, selecting the media item with the longest duration. |
|
||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pan-horizontal</title><path d="M7,8L2.5,12L7,16V8M17,8V16L21.5,12L17,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></svg> | Dragging the timeline will pan only without selected or seeking any media. |
|
||||
|
||||
<a name="dimensions"></a>
|
||||
|
||||
### Dimensions Options
|
||||
|
||||
These options control the aspect-ratio of the entire card to make placement in
|
||||
@@ -961,7 +993,6 @@ media. This only applies to the card in normal render mode -- when in
|
||||
fullscreen, or when in expanded (popup/dialog mode) the aspect ratio is chosen
|
||||
dynamically to maximize the amount of content shown.
|
||||
|
||||
|
||||
All configuration is under:
|
||||
|
||||
```yaml
|
||||
@@ -1139,34 +1170,6 @@ automations:
|
||||
| `actions` | | :heavy_multiplication_x: | An optional list of actions that will be run when the conditions evaluate `true`. Actions can be [stock Home Assistant actions](https://www.home-assistant.io/dashboards/actions/) or [Frigate card actions](#frigate-card-actions).|
|
||||
| `actions_not` | | :heavy_multiplication_x: | An optional list of actions that will be run when the conditions evaluate `false`. Actions can be [stock Home Assistant actions](https://www.home-assistant.io/dashboards/actions/) or [Frigate card actions](#frigate-card-actions).|
|
||||
|
||||
<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="other-options"></a>
|
||||
|
||||
### Other Options
|
||||
@@ -1782,6 +1785,13 @@ cameras:
|
||||
- binary_sensor.front_door_sensor
|
||||
cast:
|
||||
method: standard
|
||||
dimensions:
|
||||
aspect_ratio: 16:9
|
||||
layout:
|
||||
fit: contain
|
||||
position:
|
||||
x: 50
|
||||
y: 50
|
||||
- camera_entity: camera.entrance
|
||||
live_provider: webrtc-card
|
||||
engine: auto
|
||||
@@ -2114,11 +2124,6 @@ live:
|
||||
title:
|
||||
mode: popup-bottom-right
|
||||
duration_seconds: 2
|
||||
layout:
|
||||
fit: contain
|
||||
position:
|
||||
x: 50
|
||||
y: 50
|
||||
microphone:
|
||||
always_connected: false
|
||||
disconnect_seconds: 90
|
||||
@@ -2212,11 +2217,6 @@ media_viewer:
|
||||
title:
|
||||
mode: popup-bottom-right
|
||||
duration_seconds: 2
|
||||
layout:
|
||||
fit: contain
|
||||
position:
|
||||
x: 50
|
||||
y: 50
|
||||
display:
|
||||
mode: single
|
||||
grid_selected_width_factor: 2
|
||||
@@ -2281,11 +2281,6 @@ image:
|
||||
mode: url
|
||||
refresh_seconds: 0
|
||||
zoomable: true
|
||||
layout:
|
||||
fit: contain
|
||||
position:
|
||||
x: 50
|
||||
y: 50
|
||||
actions:
|
||||
entity: light.office_main_lights
|
||||
tap_action:
|
||||
@@ -3738,20 +3733,16 @@ 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.
|
||||
Stretch a camera 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
|
||||
dimensions:
|
||||
aspect_ratio: '4:4'
|
||||
layout:
|
||||
fit: fill
|
||||
```
|
||||
</details>
|
||||
|
||||
@@ -3764,56 +3755,12 @@ Take the left-hand side (position with x == `0` and use that as the basis of a `
|
||||
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
|
||||
dimensions:
|
||||
aspect_ratio: '9:16'
|
||||
layout:
|
||||
fit: cover
|
||||
position:
|
||||
x: 0
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { StyleInfo } from 'lit/directives/style-map';
|
||||
import { FrigateCardConfig } from '../config/types';
|
||||
import { setPerformanceCSSStyles } from '../performance';
|
||||
import { aspectRatioToStyle, setOrRemoveAttribute } from '../utils/basic';
|
||||
import { View } from '../view/view';
|
||||
import { setOrRemoveAttribute } from '../utils/basic';
|
||||
import { CardStyleAPI } from './types';
|
||||
|
||||
export class StyleManager {
|
||||
@@ -103,24 +104,23 @@ export class StyleManager {
|
||||
* required).
|
||||
* @returns A padding percentage.
|
||||
*/
|
||||
public getAspectRatioStyle(): string {
|
||||
public getAspectRatioStyle(): StyleInfo {
|
||||
const config = this._api.getConfigManager().getConfig();
|
||||
const view = this._api.getViewManager().getView();
|
||||
|
||||
if (config) {
|
||||
if (!this._isAspectRatioEnforced(config, view)) {
|
||||
return 'auto';
|
||||
return aspectRatioToStyle();
|
||||
}
|
||||
|
||||
const aspectRatioMode = config.dimensions.aspect_ratio_mode;
|
||||
|
||||
const lastKnown = this._api.getMediaLoadedInfoManager().getLastKnown();
|
||||
if (lastKnown && aspectRatioMode === 'dynamic') {
|
||||
return `${lastKnown.width} / ${lastKnown.height}`;
|
||||
return aspectRatioToStyle({ ratio: [lastKnown.width, lastKnown.height] });
|
||||
}
|
||||
|
||||
return `${config.dimensions.aspect_ratio[0]} / ${config.dimensions.aspect_ratio[1]}`;
|
||||
return aspectRatioToStyle({ ratio: config.dimensions.aspect_ratio });
|
||||
}
|
||||
return '16 / 9';
|
||||
return aspectRatioToStyle({ defaultStatic: true });
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -242,9 +242,6 @@ class FrigateCard extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const cardStyle = {
|
||||
'aspect-ratio': this._controller.getStyleManager().getAspectRatioStyle(),
|
||||
};
|
||||
const mainClasses = {
|
||||
main: true,
|
||||
'curve-top':
|
||||
@@ -266,7 +263,7 @@ class FrigateCard extends LitElement {
|
||||
hasHold: frigateCardHasAction(actions.hold_action),
|
||||
hasDoubleClick: frigateCardHasAction(actions.double_tap_action),
|
||||
})}
|
||||
style="${styleMap(cardStyle)}"
|
||||
style="${styleMap(this._controller.getStyleManager().getAspectRatioStyle())}"
|
||||
@frigate-card:message=${(ev: CustomEvent<Message>) =>
|
||||
this._controller.getMessageManager().setMessageIfHigherPriority(ev.detail)}
|
||||
@frigate-card:view:change=${(ev: CustomEvent<View>) =>
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
Message,
|
||||
} from '../../types.js';
|
||||
import { stopEventFromActivatingCardWideActions } from '../../utils/action.js';
|
||||
import { contentsChanged } from '../../utils/basic.js';
|
||||
import { aspectRatioToString, contentsChanged } from '../../utils/basic.js';
|
||||
import { CarouselSelected } from '../../utils/embla/carousel-controller.js';
|
||||
import { AutoLazyLoad } from '../../utils/embla/plugins/auto-lazy-load/auto-lazy-load.js';
|
||||
import { AutoMediaActions } from '../../utils/embla/plugins/auto-media-actions/auto-media-actions.js';
|
||||
@@ -900,7 +900,6 @@ export class FrigateCardLiveProvider
|
||||
}
|
||||
}
|
||||
if (changedProps.has('liveConfig')) {
|
||||
updateElementStyleFromMediaLayoutConfig(this, this.liveConfig?.layout);
|
||||
if (this.liveConfig?.show_image_during_load) {
|
||||
this._importPromises.push(import('./live-image.js'));
|
||||
}
|
||||
@@ -908,6 +907,7 @@ export class FrigateCardLiveProvider
|
||||
this._importPromises.push(import('./../zoomer.js'));
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has('cameraConfig')) {
|
||||
const provider = this._getResolvedProvider();
|
||||
if (provider === 'jsmpeg') {
|
||||
@@ -921,6 +921,14 @@ export class FrigateCardLiveProvider
|
||||
} else if (provider === 'go2rtc') {
|
||||
this._importPromises.push(import('./live-go2rtc.js'));
|
||||
}
|
||||
|
||||
updateElementStyleFromMediaLayoutConfig(
|
||||
this,
|
||||
this.cameraConfig?.dimensions?.layout,
|
||||
);
|
||||
this.style.aspectRatio = aspectRatioToString({
|
||||
ratio: this.cameraConfig?.dimensions?.aspect_ratio,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||
import { mayHaveAudio } from '../utils/audio.js';
|
||||
import {
|
||||
aspectRatioToString,
|
||||
contentsChanged,
|
||||
errorToConsole,
|
||||
setOrRemoveAttribute,
|
||||
@@ -396,10 +397,6 @@ export class FrigateCardViewerCarousel extends LitElement {
|
||||
* @param changedProps The changed properties
|
||||
*/
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has('viewerConfig')) {
|
||||
updateElementStyleFromMediaLayoutConfig(this, this.viewerConfig?.layout);
|
||||
}
|
||||
|
||||
if (changedProps.has('view')) {
|
||||
const newMedia =
|
||||
this.view?.queryResults?.getResults(this.viewFilterCameraID) ?? null;
|
||||
@@ -853,6 +850,18 @@ export class FrigateCardViewerProvider
|
||||
if (changedProps.has('viewerConfig') && this.viewerConfig?.zoomable) {
|
||||
import('./zoomer.js');
|
||||
}
|
||||
|
||||
if (changedProps.has('media') || changedProps.has('cameraManager')) {
|
||||
const cameraID = this.media?.getCameraID();
|
||||
const cameraConfig = cameraID
|
||||
? this.cameraManager?.getStore().getCameraConfig(cameraID)
|
||||
: null;
|
||||
updateElementStyleFromMediaLayoutConfig(this, cameraConfig?.dimensions?.layout);
|
||||
|
||||
this.style.aspectRatio = aspectRatioToString({
|
||||
ratio: cameraConfig?.dimensions?.aspect_ratio,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected _useZoomIfRequired(template: TemplateResult): TemplateResult {
|
||||
|
||||
@@ -6,6 +6,7 @@ import unset from 'lodash-es/unset';
|
||||
import { RawFrigateCardConfig, RawFrigateCardConfigArray } from './config/types';
|
||||
import {
|
||||
CONF_CAMERAS,
|
||||
CONF_CAMERAS_GLOBAL_DIMENSIONS_LAYOUT,
|
||||
CONF_CAMERAS_GLOBAL_IMAGE,
|
||||
CONF_CAMERAS_GLOBAL_JSMPEG,
|
||||
CONF_CAMERAS_GLOBAL_WEBRTC_CARD,
|
||||
@@ -565,4 +566,7 @@ const UPGRADES = [
|
||||
transform: (val) => (val ? 'default' : null),
|
||||
},
|
||||
),
|
||||
upgradeMoveToWithOverrides('live.layout', CONF_CAMERAS_GLOBAL_DIMENSIONS_LAYOUT),
|
||||
deleteWithOverrides('media_viewer.layout'),
|
||||
deleteWithOverrides('image.layout'),
|
||||
];
|
||||
|
||||
+31
-21
@@ -524,19 +524,19 @@ export type FrigateConditional = z.infer<typeof frigateConditionalSchema>;
|
||||
// Cannot use discriminatedUnion since customSchema uses a superRefine, which
|
||||
// causes false rejections.
|
||||
const pictureElementSchema = z.union([
|
||||
menuStateIconSchema,
|
||||
conditionalSchema,
|
||||
customSchema,
|
||||
frigateConditionalSchema,
|
||||
iconSchema,
|
||||
imageSchema,
|
||||
menuIconSchema,
|
||||
menuStateIconSchema,
|
||||
menuSubmenuSchema,
|
||||
menuSubmenuSelectSchema,
|
||||
frigateConditionalSchema,
|
||||
serviceCallButtonSchema,
|
||||
stateBadgeIconSchema,
|
||||
stateIconSchema,
|
||||
stateLabelSchema,
|
||||
serviceCallButtonSchema,
|
||||
iconSchema,
|
||||
imageSchema,
|
||||
conditionalSchema,
|
||||
customSchema,
|
||||
]);
|
||||
const pictureElementsSchema = pictureElementSchema.array().optional();
|
||||
export type PictureElements = z.infer<typeof pictureElementsSchema>;
|
||||
@@ -556,6 +556,21 @@ const mediaLayoutConfigSchema = z.object({
|
||||
});
|
||||
export type MediaLayoutConfig = z.infer<typeof mediaLayoutConfigSchema>;
|
||||
|
||||
// *************************************************************************
|
||||
// Aspect Ratio Configuration
|
||||
// *************************************************************************
|
||||
|
||||
const aspectRatioSchema = z
|
||||
.number()
|
||||
.array()
|
||||
.length(2)
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.regex(/^\s*\d+\.?\d*\s*[:/]\s*\d+\.?\d*\s*$/)
|
||||
.transform((input) => input.split(/[:\/]/).map((d) => Number(d))),
|
||||
);
|
||||
|
||||
// *************************************************************************
|
||||
// Image Configuration
|
||||
// Image config base options are used both for the `image` live provider and the
|
||||
@@ -581,7 +596,6 @@ const imageConfigDefault = {
|
||||
const imageConfigSchema = imageBaseConfigSchema
|
||||
.extend({
|
||||
mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode),
|
||||
layout: mediaLayoutConfigSchema.optional(),
|
||||
zoomable: z.boolean().default(imageConfigDefault.zoomable),
|
||||
})
|
||||
.merge(actionsSchema)
|
||||
@@ -711,6 +725,7 @@ export type TimelineConfig = z.infer<typeof timelineConfigSchema>;
|
||||
// *************************************************************************
|
||||
// Next/Previous Control Configuration
|
||||
// *************************************************************************
|
||||
|
||||
const nextPreviousControlConfigSchema = z.object({
|
||||
style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']),
|
||||
size: z.number().min(BUTTON_SIZE_MIN),
|
||||
@@ -950,7 +965,6 @@ const liveOverridableConfigSchema = z
|
||||
show_image_during_load: z
|
||||
.boolean()
|
||||
.default(liveConfigDefault.show_image_during_load),
|
||||
layout: mediaLayoutConfigSchema.optional(),
|
||||
microphone: microphoneConfigSchema.default(liveConfigDefault.microphone),
|
||||
zoomable: z.boolean().default(liveConfigDefault.zoomable),
|
||||
display: viewDisplaySchema,
|
||||
@@ -1138,6 +1152,13 @@ export const cameraConfigSchema = z
|
||||
webrtc_card: webrtcCardConfigSchema.optional(),
|
||||
|
||||
cast: castSchema.optional(),
|
||||
|
||||
dimensions: z
|
||||
.object({
|
||||
aspect_ratio: aspectRatioSchema.optional(),
|
||||
layout: mediaLayoutConfigSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.default(cameraConfigDefault);
|
||||
export type CameraConfig = z.infer<typeof cameraConfigSchema>;
|
||||
@@ -1412,7 +1433,6 @@ const viewerConfigSchema = z
|
||||
title: titleControlConfigSchema.optional(),
|
||||
})
|
||||
.default(viewerConfigDefault.controls),
|
||||
layout: mediaLayoutConfigSchema.optional(),
|
||||
})
|
||||
.merge(actionsSchema)
|
||||
.default(viewerConfigDefault);
|
||||
@@ -1477,17 +1497,7 @@ export const dimensionsConfigSchema = z
|
||||
aspect_ratio_mode: z
|
||||
.enum(['dynamic', 'static', 'unconstrained'])
|
||||
.default(dimensionsConfigDefault.aspect_ratio_mode),
|
||||
aspect_ratio: z
|
||||
.number()
|
||||
.array()
|
||||
.length(2)
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.regex(/^\s*\d+\s*[:/]\s*\d+\s*$/)
|
||||
.transform((input) => input.split(/[:\/]/).map((d) => Number(d))),
|
||||
)
|
||||
.default(dimensionsConfigDefault.aspect_ratio),
|
||||
aspect_ratio: aspectRatioSchema.default(dimensionsConfigDefault.aspect_ratio),
|
||||
max_height: z.string().default(dimensionsConfigDefault.max_height),
|
||||
min_height: z.string().default(dimensionsConfigDefault.min_height),
|
||||
})
|
||||
|
||||
+10
-11
@@ -11,6 +11,8 @@ export const CONF_CAMERAS_ARRAY_CAST_DASHBOARD_DASHBOARD_PATH =
|
||||
`${CONF_CAMERAS}.#.cast.dashboard.dashboard_path` as const;
|
||||
export const CONF_CAMERAS_ARRAY_CAST_DASHBOARD_VIEW_PATH =
|
||||
`${CONF_CAMERAS}.#.cast.dashboard.view_path` as const;
|
||||
export const CONF_CAMERAS_ARRAY_DIMENSIONS_ASPECT_RATIO =
|
||||
`${CONF_CAMERAS}.#.dimensions.aspect_ratio` as const;
|
||||
export const CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID =
|
||||
`${CONF_CAMERAS}.#.frigate.client_id` as const;
|
||||
export const CONF_CAMERAS_ARRAY_FRIGATE_LABELS =
|
||||
@@ -48,6 +50,12 @@ export const CONF_CAMERAS_ARRAY_DEPENDENCIES_CAMERAS =
|
||||
`${CONF_CAMERAS}.#.dependencies.cameras` as const;
|
||||
export const CONF_CAMERAS_ARRAY_DEPENDENCIES_ALL_CAMERAS =
|
||||
`${CONF_CAMERAS}.#.dependencies.all_cameras` as const;
|
||||
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT =
|
||||
`${CONF_CAMERAS}.#.dimensions.layout.fit` as const;
|
||||
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X =
|
||||
`${CONF_CAMERAS}.#.dimensions.layout.position.x` as const;
|
||||
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y =
|
||||
`${CONF_CAMERAS}.#.dimensions.layout.position.y` as const;
|
||||
export const CONF_CAMERAS_ARRAY_TRIGGERS_MOTION =
|
||||
`${CONF_CAMERAS}.#.triggers.motion` as const;
|
||||
export const CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY =
|
||||
@@ -66,6 +74,8 @@ export const CONF_CAMERAS_GLOBAL_TRIGGERS_OCCUPANCY =
|
||||
`${CONF_CAMERAS_GLOBAL}.triggers.occupancy` as const;
|
||||
export const CONF_CAMERAS_GLOBAL_IMAGE_REFRESH_SECONDS =
|
||||
`${CONF_CAMERAS_GLOBAL}.image.refresh_seconds` as const;
|
||||
export const CONF_CAMERAS_GLOBAL_DIMENSIONS_LAYOUT =
|
||||
`${CONF_CAMERAS_GLOBAL}.dimensions.layout` as const;
|
||||
|
||||
export const CONF_ELEMENTS = 'elements' as const;
|
||||
|
||||
@@ -163,11 +173,6 @@ export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE =
|
||||
`${CONF_MEDIA_VIEWER}.controls.title.mode` as const;
|
||||
export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
|
||||
`${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;
|
||||
|
||||
const CONF_LIVE = 'live' as const;
|
||||
export const CONF_LIVE_AUTO_PLAY = `${CONF_LIVE}.auto_play` as const;
|
||||
@@ -228,9 +233,6 @@ export const CONF_LIVE_DISPLAY_GRID_MAX_COLUMNS =
|
||||
`${CONF_LIVE}.display.grid_max_columns` as const;
|
||||
export const CONF_LIVE_DISPLAY_GRID_SELECTED_WIDTH_FACTOR =
|
||||
`${CONF_LIVE}.display.grid_selected_width_factor` 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_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const;
|
||||
export const CONF_LIVE_LAZY_UNLOAD = `${CONF_LIVE}.lazy_unload` as const;
|
||||
@@ -247,9 +249,6 @@ export const CONF_LIVE_MICROPHONE_ALWAYS_CONNECTED =
|
||||
export const CONF_LIVE_ZOOMABLE = `${CONF_LIVE}.zoomable` as const;
|
||||
|
||||
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_REFRESH_SECONDS = `${CONF_IMAGE}.refresh_seconds` as const;
|
||||
export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const;
|
||||
|
||||
+39
-45
@@ -22,9 +22,9 @@ import {
|
||||
} from './config-mgmt.js';
|
||||
import {
|
||||
BUTTON_SIZE_MIN,
|
||||
FRIGATE_MENU_PRIORITY_MAX,
|
||||
FrigateCardConfig,
|
||||
frigateCardConfigDefaults,
|
||||
FRIGATE_MENU_PRIORITY_MAX,
|
||||
RawFrigateCardConfig,
|
||||
RawFrigateCardConfigArray,
|
||||
THUMBNAIL_WIDTH_MAX,
|
||||
@@ -38,6 +38,10 @@ import {
|
||||
CONF_CAMERAS_ARRAY_CAST_METHOD,
|
||||
CONF_CAMERAS_ARRAY_DEPENDENCIES_ALL_CAMERAS,
|
||||
CONF_CAMERAS_ARRAY_DEPENDENCIES_CAMERAS,
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_ASPECT_RATIO,
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
|
||||
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
|
||||
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
|
||||
CONF_CAMERAS_ARRAY_FRIGATE_LABELS,
|
||||
@@ -67,9 +71,6 @@ import {
|
||||
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
||||
CONF_DIMENSIONS_MAX_HEIGHT,
|
||||
CONF_DIMENSIONS_MIN_HEIGHT,
|
||||
CONF_IMAGE_LAYOUT_FIT,
|
||||
CONF_IMAGE_LAYOUT_POSITION_X,
|
||||
CONF_IMAGE_LAYOUT_POSITION_Y,
|
||||
CONF_IMAGE_MODE,
|
||||
CONF_IMAGE_REFRESH_SECONDS,
|
||||
CONF_IMAGE_URL,
|
||||
@@ -108,9 +109,6 @@ import {
|
||||
CONF_LIVE_DISPLAY_GRID_SELECTED_WIDTH_FACTOR,
|
||||
CONF_LIVE_DISPLAY_MODE,
|
||||
CONF_LIVE_DRAGGABLE,
|
||||
CONF_LIVE_LAYOUT_FIT,
|
||||
CONF_LIVE_LAYOUT_POSITION_X,
|
||||
CONF_LIVE_LAYOUT_POSITION_Y,
|
||||
CONF_LIVE_LAZY_LOAD,
|
||||
CONF_LIVE_LAZY_UNLOAD,
|
||||
CONF_LIVE_MICROPHONE_ALWAYS_CONNECTED,
|
||||
@@ -152,16 +150,13 @@ import {
|
||||
CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_WIDTH_FACTOR,
|
||||
CONF_MEDIA_VIEWER_DISPLAY_MODE,
|
||||
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_SNAPSHOT_CLICK_PLAYS_CLIP,
|
||||
CONF_MEDIA_VIEWER_TRANSITION_EFFECT,
|
||||
CONF_MEDIA_VIEWER_ZOOMABLE,
|
||||
CONF_MENU_ALIGNMENT,
|
||||
CONF_MENU_BUTTON_SIZE,
|
||||
CONF_MENU_BUTTONS,
|
||||
CONF_MENU_BUTTON_SIZE,
|
||||
CONF_MENU_POSITION,
|
||||
CONF_MENU_STYLE,
|
||||
CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR,
|
||||
@@ -213,6 +208,8 @@ const MENU_BUTTONS = 'buttons';
|
||||
const MENU_CAMERAS = 'cameras';
|
||||
const MENU_CAMERAS_CAST = 'cameras.cast';
|
||||
const MENU_CAMERAS_DEPENDENCIES = 'cameras.dependencies';
|
||||
const MENU_CAMERAS_DIMENSIONS = 'cameras.dimensions';
|
||||
const MENU_CAMERAS_DIMENSIONS_LAYOUT = 'cameras.dimensions.layout';
|
||||
const MENU_CAMERAS_ENGINE = 'cameras.engine';
|
||||
const MENU_CAMERAS_FRIGATE = 'cameras.frigate';
|
||||
const MENU_CAMERAS_GO2RTC = 'cameras.go2rtc';
|
||||
@@ -221,7 +218,6 @@ const MENU_CAMERAS_LIVE_PROVIDER = 'cameras.live_provider';
|
||||
const MENU_CAMERAS_MOTIONEYE = 'cameras.motioneye';
|
||||
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
|
||||
const MENU_CAMERAS_WEBRTC_CARD = 'cameras.webrtc_card';
|
||||
const MENU_IMAGE_LAYOUT = 'image.layout';
|
||||
const MENU_LIVE_CONTROLS = 'live.controls';
|
||||
const MENU_LIVE_CONTROLS_NEXT_PREVIOUS = 'live.controls.next_previous';
|
||||
const MENU_LIVE_CONTROLS_PTZ = 'live.controls.ptz';
|
||||
@@ -229,7 +225,6 @@ const MENU_LIVE_CONTROLS_THUMBNAILS = 'live.controls.thumbnails';
|
||||
const MENU_LIVE_CONTROLS_TIMELINE = 'live.controls.timeline';
|
||||
const MENU_LIVE_CONTROLS_TITLE = 'live.controls.title';
|
||||
const MENU_LIVE_DISPLAY = 'live.display';
|
||||
const MENU_LIVE_LAYOUT = 'live.layout';
|
||||
const MENU_LIVE_MICROPHONE = 'live.microphone';
|
||||
const MENU_MEDIA_GALLERY_CONTROLS_FILTER = 'media_gallery.controls.filter';
|
||||
const MENU_MEDIA_GALLERY_CONTROLS_THUMBNAILS = 'media_gallery.controls.thumbnails';
|
||||
@@ -239,7 +234,6 @@ const MENU_MEDIA_VIEWER_CONTROLS_THUMBNAILS = 'media_viewer.controls.thumbnails'
|
||||
const MENU_MEDIA_VIEWER_CONTROLS_TIMELINE = 'media_viewer.controls.timeline';
|
||||
const MENU_MEDIA_VIEWER_CONTROLS_TITLE = 'media_viewer.controls.title';
|
||||
const MENU_MEDIA_VIEWER_DISPLAY = 'media_viewer.display';
|
||||
const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout';
|
||||
const MENU_OPTIONS = 'options';
|
||||
const MENU_PERFORMANCE_FEATURES = 'performance.features';
|
||||
const MENU_PERFORMANCE_STYLE = 'performance.style';
|
||||
@@ -576,9 +570,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
|
||||
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') },
|
||||
{
|
||||
value: 'contain',
|
||||
label: localize('config.cameras.dimensions.layout.fits.contain'),
|
||||
},
|
||||
{ value: 'cover', label: localize('config.cameras.dimensions.layout.fits.cover') },
|
||||
{ value: 'fill', label: localize('config.cameras.dimensions.layout.fits.fill') },
|
||||
];
|
||||
|
||||
protected _miniTimelineModes: EditorSelectOption[] = [
|
||||
@@ -1167,17 +1164,17 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
{ name: 'mdi:page-layout-body' },
|
||||
html`
|
||||
${this._renderOptionSelector(configPathFit, this._layoutFits, {
|
||||
label: localize('config.common.layout.fit'),
|
||||
label: localize('config.cameras.dimensions.layout.fit'),
|
||||
})}
|
||||
${this._renderNumberInput(configPathPositionX, {
|
||||
min: 0,
|
||||
max: 100,
|
||||
label: localize('config.common.layout.position.x'),
|
||||
label: localize('config.cameras.dimensions.layout.position.x'),
|
||||
})}
|
||||
${this._renderNumberInput(configPathPositionY, {
|
||||
min: 0,
|
||||
max: 100,
|
||||
label: localize('config.common.layout.position.y'),
|
||||
label: localize('config.cameras.dimensions.layout.position.y'),
|
||||
})}
|
||||
`,
|
||||
);
|
||||
@@ -1854,10 +1851,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
},
|
||||
)}
|
||||
${this._renderOptionSelector(
|
||||
getArrayConfigPath(
|
||||
CONF_CAMERAS_ARRAY_TRIGGERS_EVENTS,
|
||||
cameraIndex,
|
||||
),
|
||||
getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGERS_EVENTS, cameraIndex),
|
||||
this._triggersEvents,
|
||||
{
|
||||
multiple: true,
|
||||
@@ -1890,6 +1884,27 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
)}
|
||||
`,
|
||||
)}
|
||||
${this._putInSubmenu(
|
||||
MENU_CAMERAS_DIMENSIONS,
|
||||
cameraIndex,
|
||||
'config.cameras.dimensions.editor_label',
|
||||
{ name: 'mdi:aspect-ratio' },
|
||||
html`
|
||||
${this._renderStringInput(
|
||||
getArrayConfigPath(
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_ASPECT_RATIO,
|
||||
cameraIndex,
|
||||
),
|
||||
)}
|
||||
${this._renderMediaLayout(
|
||||
MENU_CAMERAS_DIMENSIONS_LAYOUT,
|
||||
'config.cameras.dimensions.layout.editor_label',
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
|
||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
|
||||
)}
|
||||
`,
|
||||
)}
|
||||
</div>`
|
||||
: ``}
|
||||
</div>
|
||||
@@ -2237,13 +2252,6 @@ 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,
|
||||
)}
|
||||
${this._putInSubmenu(
|
||||
MENU_LIVE_MICROPHONE,
|
||||
true,
|
||||
@@ -2389,13 +2397,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
)}
|
||||
`,
|
||||
)}
|
||||
${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>`
|
||||
: ''}
|
||||
${this._renderOptionSetHeader('image')}
|
||||
@@ -2405,13 +2406,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
${this._renderStringInput(CONF_IMAGE_URL)}
|
||||
${this._renderNumberInput(CONF_IMAGE_REFRESH_SECONDS)}
|
||||
${this._renderSwitch(CONF_IMAGE_ZOOMABLE, this._defaults.image.zoomable)}
|
||||
${this._renderMediaLayout(
|
||||
MENU_IMAGE_LAYOUT,
|
||||
'config.image.layout',
|
||||
CONF_IMAGE_LAYOUT_FIT,
|
||||
CONF_IMAGE_LAYOUT_POSITION_X,
|
||||
CONF_IMAGE_LAYOUT_POSITION_Y,
|
||||
)}
|
||||
</div>`
|
||||
: ''}
|
||||
${this._renderOptionSetHeader('timeline')}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"dashboard_path": "Dashboard path",
|
||||
"view_path": "View path"
|
||||
},
|
||||
"editor_label": "Cast Options",
|
||||
"editor_label": "Cast options",
|
||||
"method": "Cast method",
|
||||
"methods": {
|
||||
"dashboard": "Dashboard",
|
||||
@@ -25,7 +25,24 @@
|
||||
"dependencies": {
|
||||
"all_cameras": "Show events for all cameras with this camera",
|
||||
"cameras": "Show events for specific cameras with this camera",
|
||||
"editor_label": "Dependency Options"
|
||||
"editor_label": "Dependency options"
|
||||
},
|
||||
"dimensions": {
|
||||
"aspect_ratio": "Static aspect ratio",
|
||||
"editor_label": "Camera dimensions",
|
||||
"layout": {
|
||||
"editor_label": "Camera Layout",
|
||||
"fit": "Layout fit",
|
||||
"fits": {
|
||||
"contain": "Media is contained/letterboxed",
|
||||
"cover": "Media expands proportionally to cover the card",
|
||||
"fill": "Media is stretched to fill the card"
|
||||
},
|
||||
"position": {
|
||||
"x": "Horizontal placement percentage",
|
||||
"y": "Vertical placement percentage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"editor_label": "Camera engine options"
|
||||
@@ -83,7 +100,7 @@
|
||||
},
|
||||
"title": "Title for this camera (Autodetected from entity)",
|
||||
"triggers": {
|
||||
"editor_label": "Trigger Options",
|
||||
"editor_label": "Trigger options",
|
||||
"entities": "Trigger from other entities",
|
||||
"events": {
|
||||
"clips": "Events with new clips",
|
||||
@@ -178,18 +195,6 @@
|
||||
"grid_selected_width_factor": "Increase selected media width by this factor",
|
||||
"mode": "Mode"
|
||||
},
|
||||
"layout": {
|
||||
"fit": "Layout fit",
|
||||
"fits": {
|
||||
"contain": "Media is contained/letterboxed",
|
||||
"cover": "Media expands proportionally to cover the card",
|
||||
"fill": "Media is stretched to fill the card"
|
||||
},
|
||||
"position": {
|
||||
"x": "Horizontal placement percentage",
|
||||
"y": "Vertical placement percentage"
|
||||
}
|
||||
},
|
||||
"media_action_conditions": {
|
||||
"all": "All opportunities",
|
||||
"hidden": "On browser/tab hiding",
|
||||
@@ -229,7 +234,6 @@
|
||||
"min_height": "Minimum card height in CSS units (e.g. '100px')"
|
||||
},
|
||||
"image": {
|
||||
"layout": "Image Layout",
|
||||
"mode": "Image view mode",
|
||||
"modes": {
|
||||
"camera": "Home Assistant camera snapshot of camera entity",
|
||||
@@ -272,7 +276,6 @@
|
||||
}
|
||||
},
|
||||
"draggable": "Live cameras view can be dragged/swiped",
|
||||
"layout": "Live Layout",
|
||||
"lazy_load": "Live cameras are lazily loaded",
|
||||
"lazy_unload": "Live cameras are lazily unloaded",
|
||||
"microphone": {
|
||||
@@ -296,7 +299,6 @@
|
||||
"editor_label": "Media Viewer Controls"
|
||||
},
|
||||
"draggable": "Media Viewer can be dragged/swiped",
|
||||
"layout": "Media Viewer Layout",
|
||||
"lazy_load": "Media Viewer media is lazily loaded in carousel",
|
||||
"snapshot_click_plays_clip": "Clicking on a snapshot plays a related clip",
|
||||
"transition_effect": "Media Viewer transition effect",
|
||||
@@ -454,8 +456,8 @@
|
||||
"cameras": "Cameras",
|
||||
"cameras_secondary": "What cameras to render on this card",
|
||||
"delete": "Delete",
|
||||
"dimensions": "Dimensions",
|
||||
"dimensions_secondary": "Dimensions & shape options",
|
||||
"dimensions": "Card dimensions",
|
||||
"dimensions_secondary": "Card dimensions & shape options",
|
||||
"image": "Image",
|
||||
"image_secondary": "Static image view options",
|
||||
"live": "Live",
|
||||
|
||||
@@ -27,6 +27,22 @@
|
||||
"cameras": "Mostra eventi per telecamere specifiche con questa telecamera",
|
||||
"editor_label": "Opzioni di dipendenza"
|
||||
},
|
||||
"dimensions": {
|
||||
"editor_label": "",
|
||||
"aspect_ratio": "",
|
||||
"layout": {
|
||||
"fit": "Adatta al layout",
|
||||
"fits": {
|
||||
"contain": "Il supporto è contenuto/in cassetta delle lettere",
|
||||
"cover": "Il supporto si espande proporzionalmente per coprire la scheda",
|
||||
"fill": "Il supporto viene allungato per riempire la scheda"
|
||||
},
|
||||
"position": {
|
||||
"x": "Percentuale di posizionamento orizzontale",
|
||||
"y": "Percentuale di posizionamento verticale"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"editor_label": "Opzioni del motore della fotocamera"
|
||||
},
|
||||
@@ -178,18 +194,6 @@
|
||||
"grid_selected_width_factor": "",
|
||||
"mode": ""
|
||||
},
|
||||
"layout": {
|
||||
"fit": "Adatta al layout",
|
||||
"fits": {
|
||||
"contain": "Il supporto è contenuto/in cassetta delle lettere",
|
||||
"cover": "Il supporto si espande proporzionalmente per coprire la scheda",
|
||||
"fill": "Il supporto viene allungato per riempire la scheda"
|
||||
},
|
||||
"position": {
|
||||
"x": "Percentuale di posizionamento orizzontale",
|
||||
"y": "Percentuale di posizionamento verticale"
|
||||
}
|
||||
},
|
||||
"media_action_conditions": {
|
||||
"all": "Tutte le opportunità",
|
||||
"hidden": "Sul browser/nascondere le schede",
|
||||
@@ -229,7 +233,6 @@
|
||||
"min_height": ""
|
||||
},
|
||||
"image": {
|
||||
"layout": "Disposizione dell'immagine",
|
||||
"mode": "Modalità Visualizza immagine",
|
||||
"modes": {
|
||||
"camera": "Istantanea della telecamera di Home Assistant dell'entità telecamera",
|
||||
@@ -272,7 +275,6 @@
|
||||
}
|
||||
},
|
||||
"draggable": "Il Visualizzatore eventi può essere trascinato oppure puoi scorrere",
|
||||
"layout": "Disposizione dal vivo",
|
||||
"lazy_load": "Le telecamere dal vivo sono pigramente cariche",
|
||||
"lazy_unload": "Le telecamere dal vivo sono pigramente non caricate",
|
||||
"microphone": {
|
||||
@@ -296,7 +298,6 @@
|
||||
"editor_label": "Controlli di visualizzatore multimediale"
|
||||
},
|
||||
"draggable": "Il visualizzatore multimediale può essere trascinato oppure può scorrere",
|
||||
"layout": "Layout del visualizzatore multimediale",
|
||||
"lazy_load": "Il media Viewer viene caricato pigramente nel carosello",
|
||||
"transition_effect": "Effetto di transizione del visualizzatore multimediale",
|
||||
"transition_effects": {
|
||||
@@ -448,8 +449,8 @@
|
||||
"cameras": "Telecamere",
|
||||
"cameras_secondary": "Quali telecamere visualizzare su questa card",
|
||||
"delete": "Elimina",
|
||||
"dimensions": "Dimensioni",
|
||||
"dimensions_secondary": "Dimensioni e opzioni di forma",
|
||||
"dimensions": "",
|
||||
"dimensions_secondary": "",
|
||||
"image": "Immagine",
|
||||
"image_secondary": "Opzioni di visualizzazione dell'immagine statica",
|
||||
"live": "Live",
|
||||
@@ -584,4 +585,4 @@
|
||||
},
|
||||
"select_date": "Scegli la data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,22 @@
|
||||
"cameras": "Mostrar eventos para câmeras específicas nesta câmera",
|
||||
"editor_label": "Opções de dependência"
|
||||
},
|
||||
"dimensions": {
|
||||
"editor_label": "",
|
||||
"aspect_ratio": "",
|
||||
"layout": {
|
||||
"fit": "Ajuste de layout",
|
||||
"fits": {
|
||||
"contain": "A mídia é contida no cartão",
|
||||
"cover": "A mídia se expande proporcionalmente para cobrir o cartão",
|
||||
"fill": "A mídia é esticada para preencher o cartão"
|
||||
},
|
||||
"position": {
|
||||
"x": "Porcentagem do posicionamento horizontal",
|
||||
"y": "Porcentagem do posicionamento vertical"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"editor_label": "Opções do motor da câmera"
|
||||
},
|
||||
@@ -178,18 +194,6 @@
|
||||
"grid_selected_width_factor": "",
|
||||
"mode": ""
|
||||
},
|
||||
"layout": {
|
||||
"fit": "Ajuste de layout",
|
||||
"fits": {
|
||||
"contain": "A mídia é contida no cartão",
|
||||
"cover": "A mídia se expande proporcionalmente para cobrir o cartão",
|
||||
"fill": "A mídia é esticada para preencher o cartão"
|
||||
},
|
||||
"position": {
|
||||
"x": "Porcentagem do posicionamento horizontal",
|
||||
"y": "Porcentagem do posicionamento vertical"
|
||||
}
|
||||
},
|
||||
"media_action_conditions": {
|
||||
"all": "Todas as oportunidades",
|
||||
"hidden": "Ao ocultar o navegador/aba",
|
||||
@@ -229,7 +233,6 @@
|
||||
"min_height": ""
|
||||
},
|
||||
"image": {
|
||||
"layout": "Layout da imagem",
|
||||
"mode": "Modo de visualização de imagem",
|
||||
"modes": {
|
||||
"camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera",
|
||||
@@ -272,7 +275,6 @@
|
||||
}
|
||||
},
|
||||
"draggable": "A visualização ao vivo das câmeras pode ser arrastada/deslizada",
|
||||
"layout": "Layout dinâmico",
|
||||
"lazy_load": "As câmeras ao vivo são carregadas lentamente",
|
||||
"lazy_unload": "As câmeras ao vivo são descarregadas preguiçosamente",
|
||||
"microphone": {
|
||||
@@ -296,7 +298,6 @@
|
||||
"editor_label": "Controles do visualizador de mídia"
|
||||
},
|
||||
"draggable": "Visualizador de eventos pode ser arrastado/deslizado",
|
||||
"layout": "Layout do visualizador de mídia",
|
||||
"lazy_load": "A mídia do Visualizador de eventos é carregada lentamente no carrossel",
|
||||
"snapshot_click_plays_clip": "Clicar em um instantâneo reproduz um clipe relacionado",
|
||||
"transition_effect": "Efeito de transição do Visualizador de eventos",
|
||||
@@ -453,8 +454,8 @@
|
||||
"cameras": "Câmeras",
|
||||
"cameras_secondary": "Quais câmeras renderizar neste cartão",
|
||||
"delete": "Excluir",
|
||||
"dimensions": "Dimensões",
|
||||
"dimensions_secondary": "Dimensões e opções de forma",
|
||||
"dimensions": "",
|
||||
"dimensions_secondary": "",
|
||||
"image": "Imagem",
|
||||
"image_secondary": "Opções de visualização de imagem estática",
|
||||
"live": "Ao vivo",
|
||||
@@ -594,4 +595,4 @@
|
||||
},
|
||||
"select_date": "Escolha a data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,22 @@
|
||||
"cameras": "Mostrar eventos para câmeras específicas nesta câmera",
|
||||
"editor_label": "Opções de dependência"
|
||||
},
|
||||
"dimensions": {
|
||||
"editor_label": "",
|
||||
"aspect_ratio": "",
|
||||
"layout": {
|
||||
"fit": "Fit",
|
||||
"fits": {
|
||||
"contain": "Conter",
|
||||
"cover": "Tapar",
|
||||
"fill": "Preencher"
|
||||
},
|
||||
"position": {
|
||||
"x": "Percentagem da localização horizontal",
|
||||
"y": "Percentagem da localização vertical"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"editor_label": "Editor de etiquetas"
|
||||
},
|
||||
@@ -178,18 +194,6 @@
|
||||
"grid_selected_width_factor": "",
|
||||
"mode": ""
|
||||
},
|
||||
"layout": {
|
||||
"fit": "Fit",
|
||||
"fits": {
|
||||
"contain": "Conter",
|
||||
"cover": "Tapar",
|
||||
"fill": "Preencher"
|
||||
},
|
||||
"position": {
|
||||
"x": "Percentagem da localização horizontal",
|
||||
"y": "Percentagem da localização vertical"
|
||||
}
|
||||
},
|
||||
"media_action_conditions": {
|
||||
"all": "Todas as oportunidades",
|
||||
"hidden": "Ao ocultar o navegador/aba",
|
||||
@@ -222,7 +226,6 @@
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"layout": "Layout",
|
||||
"mode": "Modo de visualização de imagem",
|
||||
"modes": {
|
||||
"camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera",
|
||||
@@ -265,7 +268,6 @@
|
||||
}
|
||||
},
|
||||
"draggable": "A visualização ao vivo das câmeras pode ser arrastada/deslizada",
|
||||
"layout": "layout",
|
||||
"lazy_load": "As câmeras ao vivo são carregadas lentamente",
|
||||
"lazy_unload": "As câmeras ao vivo são descarregadas preguiçosamente",
|
||||
"microphone": {
|
||||
@@ -289,7 +291,6 @@
|
||||
"editor_label": "Controles do visualizador de mídia"
|
||||
},
|
||||
"draggable": "Visualizador de eventos pode ser arrastado/deslizado",
|
||||
"layout": "Layout",
|
||||
"lazy_load": "A mídia do Visualizador de eventos é carregada lentamente no carrossel",
|
||||
"transition_effect": "Efeito de transição do Visualizador de eventos",
|
||||
"transition_effects": {
|
||||
@@ -441,8 +442,8 @@
|
||||
"cameras": "Câmeras",
|
||||
"cameras_secondary": "Câmeras para renderizar neste cartão",
|
||||
"delete": "Excluir",
|
||||
"dimensions": "Dimensões",
|
||||
"dimensions_secondary": "Dimensões e opções de forma",
|
||||
"dimensions": "",
|
||||
"dimensions_secondary": "",
|
||||
"image": "Imagem",
|
||||
"image_secondary": "Opções de visualização de imagem estática",
|
||||
"live": "Ao vivo",
|
||||
@@ -576,4 +577,4 @@
|
||||
},
|
||||
"select_date": "Selecionar a data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
--video-max-height: none;
|
||||
}
|
||||
|
||||
// When the carousel is not part of a grid ensure its height matches its
|
||||
// container.
|
||||
:host(:not([grid-id])) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// If the carousel has an unselected attribute set on it, do not let the
|
||||
// pointer interact (e.g. hover, scroll) with underlying elements. This is used
|
||||
// when the carousel is part of a media-grid. Without this next/prev controls
|
||||
@@ -13,6 +19,11 @@
|
||||
}
|
||||
|
||||
.embla__slide {
|
||||
// Center the content horizontally (for cases where the configured aspect
|
||||
// ratio is a mismatch with the card).
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
height: 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
||||
@@ -2,6 +2,7 @@ import differenceInHours from 'date-fns/differenceInHours';
|
||||
import differenceInMinutes from 'date-fns/differenceInMinutes';
|
||||
import differenceInSeconds from 'date-fns/differenceInSeconds';
|
||||
import format from 'date-fns/format';
|
||||
import { StyleInfo } from 'lit/directives/style-map';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import mergeWith from 'lodash-es/mergeWith';
|
||||
import { FrigateCardError } from '../types';
|
||||
@@ -245,3 +246,25 @@ export const getChildrenFromElement = (parent: HTMLElement): HTMLElement[] => {
|
||||
export const recursivelyMergeObjectsNotArrays = <T>(src1: T, src2: T): T => {
|
||||
return mergeWith({}, src1, src2, (_a, b) => (Array.isArray(b) ? b : undefined));
|
||||
};
|
||||
|
||||
export const aspectRatioToString = (options?: {
|
||||
ratio?: number[];
|
||||
defaultStatic?: boolean;
|
||||
}): string => {
|
||||
if (options?.ratio && options.ratio.length === 2) {
|
||||
return `${options.ratio[0]} / ${options.ratio[1]}`;
|
||||
} else if (options?.defaultStatic) {
|
||||
return '16 / 9';
|
||||
} else {
|
||||
return 'auto';
|
||||
}
|
||||
};
|
||||
|
||||
export const aspectRatioToStyle = (options?: {
|
||||
ratio?: number[];
|
||||
defaultStatic?: boolean;
|
||||
}): StyleInfo => {
|
||||
return {
|
||||
'aspect-ratio': aspectRatioToString(options),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -245,7 +245,7 @@ describe('StyleManager', () => {
|
||||
it('without config or view', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new StyleManager(api);
|
||||
expect(manager.getAspectRatioStyle()).toBe('16 / 9');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': '16 / 9' });
|
||||
});
|
||||
|
||||
it('should be auto with unconstrained aspect ratio', () => {
|
||||
@@ -260,7 +260,7 @@ describe('StyleManager', () => {
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
||||
});
|
||||
|
||||
it('should be auto in fullscreen', () => {
|
||||
@@ -271,7 +271,7 @@ describe('StyleManager', () => {
|
||||
vi.mocked(api.getFullscreenManager().isInFullscreen).mockReturnValue(true);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
||||
});
|
||||
|
||||
it('should be auto when expanded', () => {
|
||||
@@ -282,7 +282,7 @@ describe('StyleManager', () => {
|
||||
vi.mocked(api.getExpandManager().isExpanded).mockReturnValue(true);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
||||
});
|
||||
|
||||
it('should be auto when there is yet to be a view', () => {
|
||||
@@ -291,7 +291,7 @@ describe('StyleManager', () => {
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
||||
});
|
||||
|
||||
describe('should be auto when dynamic in certain views', () => {
|
||||
@@ -317,7 +317,7 @@ describe('StyleManager', () => {
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -337,7 +337,7 @@ describe('StyleManager', () => {
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('16 / 9');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': '16 / 9' });
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -362,7 +362,9 @@ describe('StyleManager', () => {
|
||||
});
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('800 / 600');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({
|
||||
'aspect-ratio': '800 / 600',
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -381,7 +383,7 @@ describe('StyleManager', () => {
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('4 / 3');
|
||||
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': '4 / 3' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1738,5 +1738,59 @@ describe('should handle version specific upgrades', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle media layout changes', () => {
|
||||
it('from live.layout to camera_global.dimensions', () => {
|
||||
const config = {
|
||||
live: {
|
||||
layout: {
|
||||
fit: 'cover',
|
||||
position: {
|
||||
x: 42,
|
||||
y: 43,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(upgradeConfig(config)).toBeTruthy();
|
||||
expect(config).toEqual({
|
||||
live: {},
|
||||
cameras_global: {
|
||||
dimensions: {
|
||||
layout: {
|
||||
fit: 'cover',
|
||||
position: {
|
||||
x: 42,
|
||||
y: 43,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('from delete old media layouts', () => {
|
||||
it.each([['media_viewer' as const], ['image' as const]])(
|
||||
'%s',
|
||||
(section: string) => {
|
||||
const config = {
|
||||
[section]: {
|
||||
layout: {
|
||||
fit: 'cover',
|
||||
position: {
|
||||
x: 42,
|
||||
y: 43,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(upgradeConfig(config)).toBeTruthy();
|
||||
expect(config).toEqual({
|
||||
[section]: {},
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,9 @@ import { afterAll, describe, expect, it, vi } from 'vitest';
|
||||
import { FrigateCardError } from '../../src/types';
|
||||
import {
|
||||
allPromises,
|
||||
arrayMove,
|
||||
arrayify,
|
||||
arrayMove,
|
||||
aspectRatioToStyle,
|
||||
contentsChanged,
|
||||
dayToDate,
|
||||
dispatchFrigateCardEvent,
|
||||
@@ -12,16 +13,16 @@ import {
|
||||
formatDateAndTime,
|
||||
getChildrenFromElement,
|
||||
getDurationString,
|
||||
isHTMLElement,
|
||||
isHoverableDevice,
|
||||
isHTMLElement,
|
||||
isSuperset,
|
||||
isTruthy,
|
||||
isValidDate,
|
||||
prettifyTitle,
|
||||
recursivelyMergeObjectsNotArrays,
|
||||
runWhenIdleIfSupported,
|
||||
setOrRemoveAttribute,
|
||||
setify,
|
||||
setOrRemoveAttribute,
|
||||
sleep,
|
||||
} from '../../src/utils/basic';
|
||||
import { createSlot, createSlotHost } from '../test-utils';
|
||||
@@ -338,3 +339,20 @@ describe('recursivelyMergeObjectsNotArrays', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('aspectRatioToStyle', () => {
|
||||
it('default', () => {
|
||||
expect(aspectRatioToStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
||||
});
|
||||
it('default static', () => {
|
||||
expect(aspectRatioToStyle({ defaultStatic: true })).toEqual({
|
||||
'aspect-ratio': '16 / 9',
|
||||
});
|
||||
});
|
||||
it('valid ratio', () => {
|
||||
expect(aspectRatioToStyle({ ratio: [4, 3] })).toEqual({ 'aspect-ratio': '4 / 3' });
|
||||
});
|
||||
it('invalid ratio', () => {
|
||||
expect(aspectRatioToStyle({ ratio: [4] })).toEqual({ 'aspect-ratio': 'auto' });
|
||||
});
|
||||
});
|
||||
|
||||
+6
-6
@@ -20,7 +20,6 @@ const FULL_COVERAGE_FILES_RELATIVE = [
|
||||
'config-mgmt.ts',
|
||||
'config/types.ts',
|
||||
'const.ts',
|
||||
'src/view/*.ts',
|
||||
'types.ts',
|
||||
'utils/action.ts',
|
||||
'utils/audio.ts',
|
||||
@@ -40,6 +39,7 @@ const FULL_COVERAGE_FILES_RELATIVE = [
|
||||
'utils/substream.ts',
|
||||
'utils/timer.ts',
|
||||
'utils/zod.ts',
|
||||
'view/*.ts',
|
||||
];
|
||||
|
||||
interface Threshold {
|
||||
@@ -80,13 +80,13 @@ export default defineConfig({
|
||||
|
||||
// Expected thresholds for anything that does not have 100% coverage
|
||||
// yet.
|
||||
statements: 15.54,
|
||||
branches: 10.38,
|
||||
functions: 16.18,
|
||||
lines: 15.84,
|
||||
statements: 10.67,
|
||||
branches: 4.9,
|
||||
functions: 7.81,
|
||||
lines: 10.92,
|
||||
|
||||
...calculateFullCoverageThresholds(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user