Add support for precise layout cropping options.
This commit is contained in:
@@ -114,7 +114,8 @@ cameras:
|
|||||||
|
|
||||||
## `dimensions`
|
## `dimensions`
|
||||||
|
|
||||||
The `dimensions` block configures the dimensions and layout of media of a given camera.
|
The `dimensions` block configures the dimensions and layout of media of a given camera (see [Card Dimensions](../dimensions.md) to set the dimensions of the whole card and not just a single camera).
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
cameras:
|
cameras:
|
||||||
@@ -130,8 +131,7 @@ cameras:
|
|||||||
|
|
||||||
### Layout Configuration
|
### Layout Configuration
|
||||||
|
|
||||||
The `layout` block configures 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 configuration](../dimensions.md) ). 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.
|
The `layout` block configures 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 configuration](../dimensions.md) ).
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
cameras:
|
cameras:
|
||||||
@@ -143,11 +143,32 @@ cameras:
|
|||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| - | - | - |
|
| - | - | - |
|
||||||
| `fit` | `contain` | 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. |
|
| `fit` | `contain` | 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 camera/card dimensions are fully covered. If `fill`, the media is stretched to fill the camera/card dimensions (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` | | 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. |
|
| `position` | | A dictionary that may contain an `x` and `y` percentage (`0` - `100`) to control the position of the media when the fit is `cover` (for other values of `fit` this option has no effect). This can be effectively used to "pan"/cut the media shown. At any given time, only one of `x` and `y` will have an effect, depending on whether media width is larger than the camera/card dimensions (in which case `x` controls the position) or the media height is larger than the camera/card dimensions (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 technicals. See visualizations below. |
|
||||||
|
| `view_box` | | A dictionary that may contain a `top`, `bottom`, `left` and `right` percentage (`0` - `100`) to precisely crop what part of the media to show by specifying a % inset value from each side. Browsers apply this cropping after `position` and `fit` have been applied. See visualizations below. |
|
||||||
|
|
||||||
|
!> The `view_box` parameter only has effect on browsers that support [`object-view-box`](https://caniuse.com/mdn-css_properties_object-view-box). As of writing this is exclusively Chromium based browsers (e.g. Google Chrome or Microsoft Edge).
|
||||||
|
|
||||||
See [media layout examples](../../examples.md?id=media-layout).
|
See [media layout examples](../../examples.md?id=media-layout).
|
||||||
|
|
||||||
|
#### Layout **Visualizations**
|
||||||
|
|
||||||
|
##### `fit`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
##### `position`: When media is shorter than dimensions height
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
##### `position`: When media is thinner than dimensions width
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### `view_box`: Precise media cropping
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## `triggers`
|
## `triggers`
|
||||||
|
|
||||||
The `triggers` block configures what triggers a camera. Triggering can be used
|
The `triggers` block configures what triggers a camera. Triggering can be used
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
# `dimensions`
|
# `dimensions`
|
||||||
|
|
||||||
These options control the dimensions and aspect-ratio of the card. These options
|
These options control the dimensions and aspect-ratio of the card (See [Camera
|
||||||
configuration applies once to the entire card (including the menu, thumbnails,
|
Dimensions](./cameras/README.md?id=dimensions) to set the dimensions of a camera
|
||||||
etc), not just to displayed media. This only applies to the card in normal
|
and not the whole card). These options configuration applies once to the entire
|
||||||
render mode -- when in fullscreen, or when in expanded (popup/dialog mode) the
|
card (including the menu, thumbnails, etc), not just to displayed media. This
|
||||||
aspect ratio is chosen dynamically to maximize the amount of content shown.
|
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.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dimensions:
|
dimensions:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -35,6 +35,7 @@
|
|||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"masonry-layout": "^4.2.2",
|
"masonry-layout": "^4.2.2",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
|
"p-queue": "^8.0.1",
|
||||||
"propagating-hammerjs": "^2.0.1",
|
"propagating-hammerjs": "^2.0.1",
|
||||||
"quick-lru": "^6.1.0",
|
"quick-lru": "^6.1.0",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
@@ -89,6 +90,7 @@
|
|||||||
"build": "yarn run lint && yarn run rollup",
|
"build": "yarn run lint && yarn run rollup",
|
||||||
"docs": "docsify serve ./docs",
|
"docs": "docsify serve ./docs",
|
||||||
"docs-check-links": "find ./docs -name '*.md' -print0 | xargs -0 -n1 markdown-link-check -c ./markdown-link-check.json",
|
"docs-check-links": "find ./docs -name '*.md' -print0 | xargs -0 -n1 markdown-link-check -c ./markdown-link-check.json",
|
||||||
|
"docs-update-images": "./scripts/update-images.sh",
|
||||||
"lint": "eslint 'src/**/*.ts'",
|
"lint": "eslint 'src/**/*.ts'",
|
||||||
"rollup": "rollup -c",
|
"rollup": "rollup -c",
|
||||||
"prune": "ts-prune",
|
"prune": "ts-prune",
|
||||||
|
|||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
WIDTH=800
|
||||||
|
|
||||||
|
URL_FIT="https://docs.google.com/drawings/d/e/2PACX-1vTq0SVS8HWs3jGC0jjNpJoYfMbZS6P27CYyPlDhSa9OhdB_3jEb0HTLLYwu8Nv3J1TdjAJppcjTiVNy/pub?w=$WIDTH"
|
||||||
|
URL_THINNER_THAN_WIDTH="https://docs.google.com/drawings/d/e/2PACX-1vRkMd89N0tkZt5IghPKhR6gs8zMhB-5_hx5QfP6BCxbsSIga_h44IczP06Sj_YnKkxhe0lRdeR-uh04/pub?w=$WIDTH"
|
||||||
|
URL_SHORTER_THAN_HEIGHT="https://docs.google.com/drawings/d/e/2PACX-1vTKVsXEWIbj9lYKrCeugdLKcK_rOwAZZDK8IzhPdHH4wMwV2v7kEI0nsn2Qgugb00qDVHsE7kE8CBIC/pub?w=$WIDTH"
|
||||||
|
URL_VIEW_BOX="https://docs.google.com/drawings/d/e/2PACX-1vRpPNsaStxW2ENmv1kfUQg41cua9XQ2sQziq2PC8LdRCtkvjHSKYH3CyPO1Pz7kOdiQ2yQKrBX88-TF/pub?w=$WIDTH"
|
||||||
|
|
||||||
|
DIR_MEDIA_LAYOUT_IMAGES="./docs/images/media_layout"
|
||||||
|
|
||||||
|
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/fit.png" "$URL_FIT" && \
|
||||||
|
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/position-thinner-than-width.png" "$URL_THINNER_THAN_WIDTH" && \
|
||||||
|
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/position-shorter-than-height.png" "$URL_SHORTER_THAN_HEIGHT" && \
|
||||||
|
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/view-box.png" "$URL_VIEW_BOX"
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import add from 'date-fns/add';
|
import add from 'date-fns/add';
|
||||||
import cloneDeep from 'lodash-es/cloneDeep';
|
import cloneDeep from 'lodash-es/cloneDeep';
|
||||||
import sum from 'lodash-es/sum';
|
import sum from 'lodash-es/sum';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
import { CardCameraAPI } from '../card-controller/types.js';
|
import { CardCameraAPI } from '../card-controller/types.js';
|
||||||
import { CameraConfig, CamerasConfig, PTZAction, PTZPhase } from '../config/types.js';
|
import { CameraConfig, CamerasConfig, PTZAction, PTZPhase } from '../config/types.js';
|
||||||
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js';
|
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js';
|
||||||
@@ -108,6 +109,7 @@ export class CameraManager {
|
|||||||
protected _api: CardCameraAPI;
|
protected _api: CardCameraAPI;
|
||||||
protected _engineFactory: CameraManagerEngineFactory;
|
protected _engineFactory: CameraManagerEngineFactory;
|
||||||
protected _store: CameraManagerStore;
|
protected _store: CameraManagerStore;
|
||||||
|
protected _initializationLimit = new PQueue({ concurrency: 1 });
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
api: CardCameraAPI,
|
api: CardCameraAPI,
|
||||||
@@ -138,8 +140,6 @@ export class CameraManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.reset();
|
|
||||||
|
|
||||||
// For each camera merge the config (which has no defaults) into the camera
|
// For each camera merge the config (which has no defaults) into the camera
|
||||||
// global config (which does have defaults). The merging must happen in this
|
// global config (which does have defaults). The merging must happen in this
|
||||||
// order, to ensure that the defaults in the cameras global config do not
|
// order, to ensure that the defaults in the cameras global config do not
|
||||||
@@ -148,8 +148,16 @@ export class CameraManager {
|
|||||||
recursivelyMergeObjectsNotArrays({}, cloneDeep(config?.cameras_global), camera),
|
recursivelyMergeObjectsNotArrays({}, cloneDeep(config?.cameras_global), camera),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
const resetAndInitialize = async () => {
|
||||||
|
await this.reset();
|
||||||
await this._initializeCameras(cameras);
|
await this._initializeCameras(cameras);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This concurrency limit prevents multiple rapidly arriving configs from
|
||||||
|
// generating reset-n-initialize race conditions (e.g. changing values
|
||||||
|
// rapidly in the config editor).
|
||||||
|
await this._initializationLimit.add(resetAndInitialize);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
this._api.getMessageManager().setErrorIfHigherPriority(e);
|
this._api.getMessageManager().setErrorIfHigherPriority(e);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -618,6 +618,14 @@ const mediaLayoutConfigSchema = z.object({
|
|||||||
y: z.number().min(0).max(100).optional(),
|
y: z.number().min(0).max(100).optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
view_box: z
|
||||||
|
.object({
|
||||||
|
bottom: z.number().min(0).max(100).optional().default(0),
|
||||||
|
left: z.number().min(0).max(100).optional().default(0),
|
||||||
|
right: z.number().min(0).max(100).optional().default(0),
|
||||||
|
top: z.number().min(0).max(100).optional().default(0),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
export type MediaLayoutConfig = z.infer<typeof mediaLayoutConfigSchema>;
|
export type MediaLayoutConfig = z.infer<typeof mediaLayoutConfigSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,14 @@ export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X =
|
|||||||
`${CONF_CAMERAS}.#.dimensions.layout.position.x` as const;
|
`${CONF_CAMERAS}.#.dimensions.layout.position.x` as const;
|
||||||
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y =
|
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y =
|
||||||
`${CONF_CAMERAS}.#.dimensions.layout.position.y` as const;
|
`${CONF_CAMERAS}.#.dimensions.layout.position.y` as const;
|
||||||
|
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_TOP =
|
||||||
|
`${CONF_CAMERAS}.#.dimensions.layout.view_box.top` as const;
|
||||||
|
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_BOTTOM =
|
||||||
|
`${CONF_CAMERAS}.#.dimensions.layout.view_box.bottom` as const;
|
||||||
|
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_LEFT =
|
||||||
|
`${CONF_CAMERAS}.#.dimensions.layout.view_box.left` as const;
|
||||||
|
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_RIGHT =
|
||||||
|
`${CONF_CAMERAS}.#.dimensions.layout.view_box.right` as const;
|
||||||
export const CONF_CAMERAS_ARRAY_TRIGGERS_MOTION =
|
export const CONF_CAMERAS_ARRAY_TRIGGERS_MOTION =
|
||||||
`${CONF_CAMERAS}.#.triggers.motion` as const;
|
`${CONF_CAMERAS}.#.triggers.motion` as const;
|
||||||
export const CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY =
|
export const CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY =
|
||||||
|
|||||||
+68
-1
@@ -46,6 +46,10 @@ import {
|
|||||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
|
||||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
|
||||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_BOTTOM,
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_LEFT,
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_RIGHT,
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_TOP,
|
||||||
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
|
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
|
||||||
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
|
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
|
||||||
CONF_CAMERAS_ARRAY_FRIGATE_LABELS,
|
CONF_CAMERAS_ARRAY_FRIGATE_LABELS,
|
||||||
@@ -1226,6 +1230,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
configPathFit: string,
|
configPathFit: string,
|
||||||
configPathPositionX: string,
|
configPathPositionX: string,
|
||||||
configPathPositionY: string,
|
configPathPositionY: string,
|
||||||
|
configPathPositionTop: string,
|
||||||
|
configPathPositionBottom: string,
|
||||||
|
configPathPositionLeft: string,
|
||||||
|
configPathPositionRight: string,
|
||||||
): TemplateResult | void {
|
): TemplateResult | void {
|
||||||
return this._putInSubmenu(
|
return this._putInSubmenu(
|
||||||
domain,
|
domain,
|
||||||
@@ -1236,7 +1244,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
${this._renderOptionSelector(configPathFit, this._layoutFits, {
|
${this._renderOptionSelector(configPathFit, this._layoutFits, {
|
||||||
label: localize('config.cameras.dimensions.layout.fit'),
|
label: localize('config.cameras.dimensions.layout.fit'),
|
||||||
})}
|
})}
|
||||||
${this._renderNumberInput(configPathPositionX, {
|
${this._putInSubmenu(
|
||||||
|
`${domain}.position`,
|
||||||
|
true,
|
||||||
|
'config.cameras.dimensions.layout.position.editor_label',
|
||||||
|
{ name: 'mdi:aspect-ratio' },
|
||||||
|
html` ${this._renderNumberInput(configPathPositionX, {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
label: localize('config.cameras.dimensions.layout.position.x'),
|
label: localize('config.cameras.dimensions.layout.position.x'),
|
||||||
@@ -1245,7 +1258,36 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
label: localize('config.cameras.dimensions.layout.position.y'),
|
label: localize('config.cameras.dimensions.layout.position.y'),
|
||||||
|
})}`,
|
||||||
|
)}
|
||||||
|
${this._putInSubmenu(
|
||||||
|
`${domain}.view_box`,
|
||||||
|
true,
|
||||||
|
'config.cameras.dimensions.layout.view_box.editor_label',
|
||||||
|
{ name: 'mdi:crop' },
|
||||||
|
html`
|
||||||
|
${this._renderNumberInput(configPathPositionTop, {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
label: localize('config.cameras.dimensions.layout.view_box.top'),
|
||||||
})}
|
})}
|
||||||
|
${this._renderNumberInput(configPathPositionBottom, {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
label: localize('config.cameras.dimensions.layout.view_box.bottom'),
|
||||||
|
})}
|
||||||
|
${this._renderNumberInput(configPathPositionLeft, {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
label: localize('config.cameras.dimensions.layout.view_box.left'),
|
||||||
|
})}
|
||||||
|
${this._renderNumberInput(configPathPositionRight, {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
label: localize('config.cameras.dimensions.layout.view_box.right'),
|
||||||
|
})}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1971,9 +2013,34 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
${this._renderMediaLayout(
|
${this._renderMediaLayout(
|
||||||
MENU_CAMERAS_DIMENSIONS_LAYOUT,
|
MENU_CAMERAS_DIMENSIONS_LAYOUT,
|
||||||
'config.cameras.dimensions.layout.editor_label',
|
'config.cameras.dimensions.layout.editor_label',
|
||||||
|
getArrayConfigPath(
|
||||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
|
||||||
|
cameraIndex,
|
||||||
|
),
|
||||||
|
getArrayConfigPath(
|
||||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
|
||||||
|
cameraIndex,
|
||||||
|
),
|
||||||
|
getArrayConfigPath(
|
||||||
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
|
||||||
|
cameraIndex,
|
||||||
|
),
|
||||||
|
getArrayConfigPath(
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_TOP,
|
||||||
|
cameraIndex,
|
||||||
|
),
|
||||||
|
getArrayConfigPath(
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_BOTTOM,
|
||||||
|
cameraIndex,
|
||||||
|
),
|
||||||
|
getArrayConfigPath(
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_LEFT,
|
||||||
|
cameraIndex,
|
||||||
|
),
|
||||||
|
getArrayConfigPath(
|
||||||
|
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_RIGHT,
|
||||||
|
cameraIndex,
|
||||||
|
),
|
||||||
)}
|
)}
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -56,8 +56,16 @@
|
|||||||
"fill": "El suport s'estira per omplir la targeta"
|
"fill": "El suport s'estira per omplir la targeta"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
|
"editor_label": "",
|
||||||
"x": "Percentatge d'emplaçament horitzontal",
|
"x": "Percentatge d'emplaçament horitzontal",
|
||||||
"y": "Percentatge d'emplaçament vertical"
|
"y": "Percentatge d'emplaçament vertical"
|
||||||
|
},
|
||||||
|
"view_box": {
|
||||||
|
"bottom": "",
|
||||||
|
"editor_label": "",
|
||||||
|
"left": "",
|
||||||
|
"right": "",
|
||||||
|
"top": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,8 +56,16 @@
|
|||||||
"fill": "Media is stretched to fill the card"
|
"fill": "Media is stretched to fill the card"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
|
"editor_label": "Layout Cover Position",
|
||||||
"x": "Horizontal placement percentage",
|
"x": "Horizontal placement percentage",
|
||||||
"y": "Vertical placement percentage"
|
"y": "Vertical placement percentage"
|
||||||
|
},
|
||||||
|
"view_box": {
|
||||||
|
"bottom": "Bottom inset percentage",
|
||||||
|
"editor_label": "Layout View Box",
|
||||||
|
"left": "Left inset percentage",
|
||||||
|
"right": "Right inset percentage",
|
||||||
|
"top": "Top inset percentage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,8 +56,16 @@
|
|||||||
"fill": "Il supporto viene allungato per riempire la scheda"
|
"fill": "Il supporto viene allungato per riempire la scheda"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
|
"editor_label": "",
|
||||||
"x": "Percentuale di posizionamento orizzontale",
|
"x": "Percentuale di posizionamento orizzontale",
|
||||||
"y": "Percentuale di posizionamento verticale"
|
"y": "Percentuale di posizionamento verticale"
|
||||||
|
},
|
||||||
|
"view_box": {
|
||||||
|
"bottom": "",
|
||||||
|
"editor_label": "",
|
||||||
|
"left": "",
|
||||||
|
"right": "",
|
||||||
|
"top": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,8 +56,16 @@
|
|||||||
"fill": "Il supporto viene allungato per riempire la scheda"
|
"fill": "Il supporto viene allungato per riempire la scheda"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
|
"editor_label": "",
|
||||||
"x": "Percentuale di posizionamento orizzontale",
|
"x": "Percentuale di posizionamento orizzontale",
|
||||||
"y": "Percentuale di posizionamento verticale"
|
"y": "Percentuale di posizionamento verticale"
|
||||||
|
},
|
||||||
|
"view_box": {
|
||||||
|
"bottom": "",
|
||||||
|
"editor_label": "",
|
||||||
|
"left": "",
|
||||||
|
"right": "",
|
||||||
|
"top": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,8 +56,16 @@
|
|||||||
"fill": "A mídia é esticada para preencher o cartão"
|
"fill": "A mídia é esticada para preencher o cartão"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
|
"editor_label": "",
|
||||||
"x": "Porcentagem do posicionamento horizontal",
|
"x": "Porcentagem do posicionamento horizontal",
|
||||||
"y": "Porcentagem do posicionamento vertical"
|
"y": "Porcentagem do posicionamento vertical"
|
||||||
|
},
|
||||||
|
"view_box": {
|
||||||
|
"bottom": "",
|
||||||
|
"editor_label": "",
|
||||||
|
"left": "",
|
||||||
|
"right": "",
|
||||||
|
"top": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,8 +56,16 @@
|
|||||||
"fill": "Preencher"
|
"fill": "Preencher"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
|
"editor_label": "",
|
||||||
"x": "Percentagem da localização horizontal",
|
"x": "Percentagem da localização horizontal",
|
||||||
"y": "Percentagem da localização vertical"
|
"y": "Percentagem da localização vertical"
|
||||||
|
},
|
||||||
|
"view_box": {
|
||||||
|
"bottom": "",
|
||||||
|
"editor_label": "",
|
||||||
|
"left": "",
|
||||||
|
"right": "",
|
||||||
|
"top": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,4 +2,13 @@
|
|||||||
object-fit: var(--frigate-card-media-layout-fit, contain);
|
object-fit: var(--frigate-card-media-layout-fit, contain);
|
||||||
object-position: var(--frigate-card-media-layout-position-x, 50%)
|
object-position: var(--frigate-card-media-layout-position-x, 50%)
|
||||||
var(--frigate-card-media-layout-position-y, 50%);
|
var(--frigate-card-media-layout-position-y, 50%);
|
||||||
|
|
||||||
|
// This is supported on limited browsers:
|
||||||
|
// See: https://caniuse.com/?search=object-view-box
|
||||||
|
object-view-box: inset(
|
||||||
|
var(--frigate-card-media-layout-view-box-top, 0%)
|
||||||
|
var(--frigate-card-media-layout-view-box-right, 0%)
|
||||||
|
var(--frigate-card-media-layout-view-box-bottom, 0%)
|
||||||
|
var(--frigate-card-media-layout-view-box-left, 0%)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,14 @@ export const updateElementStyleFromMediaLayoutConfig = (
|
|||||||
element.style.removeProperty(`--frigate-card-media-layout-position-${dimension}`);
|
element.style.removeProperty(`--frigate-card-media-layout-position-${dimension}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const dimension of ['top', 'bottom', 'left', 'right']) {
|
||||||
|
if (mediaLayoutConfig?.view_box?.[dimension] !== undefined) {
|
||||||
|
element.style.setProperty(
|
||||||
|
`--frigate-card-media-layout-view-box-${dimension}`,
|
||||||
|
`${mediaLayoutConfig.view_box[dimension]}%`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
element.style.removeProperty(`--frigate-card-media-layout-view-box-${dimension}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3622,6 +3622,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"eventemitter3@npm:^5.0.1":
|
||||||
|
version: 5.0.1
|
||||||
|
resolution: "eventemitter3@npm:5.0.1"
|
||||||
|
checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"execa@npm:^8.0.1":
|
"execa@npm:^8.0.1":
|
||||||
version: 8.0.1
|
version: 8.0.1
|
||||||
resolution: "execa@npm:8.0.1"
|
resolution: "execa@npm:8.0.1"
|
||||||
@@ -3886,6 +3893,7 @@ __metadata:
|
|||||||
markdown-link-check: "npm:^3.12.1"
|
markdown-link-check: "npm:^3.12.1"
|
||||||
masonry-layout: "npm:^4.2.2"
|
masonry-layout: "npm:^4.2.2"
|
||||||
moment: "npm:^2.29.4"
|
moment: "npm:^2.29.4"
|
||||||
|
p-queue: "npm:^8.0.1"
|
||||||
prettier: "npm:^2.6.0"
|
prettier: "npm:^2.6.0"
|
||||||
propagating-hammerjs: "npm:^2.0.1"
|
propagating-hammerjs: "npm:^2.0.1"
|
||||||
quick-lru: "npm:^6.1.0"
|
quick-lru: "npm:^6.1.0"
|
||||||
@@ -6234,6 +6242,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"p-queue@npm:^8.0.1":
|
||||||
|
version: 8.0.1
|
||||||
|
resolution: "p-queue@npm:8.0.1"
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: "npm:^5.0.1"
|
||||||
|
p-timeout: "npm:^6.1.2"
|
||||||
|
checksum: 10c0/fe185bc8bbd32d17a5f6dba090077b1bb326b008b4ec9b0646c57a32a6984035aa8ece909a6d0de7f6c4640296dc288197f430e7394cdc76a26d862339494616
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"p-timeout@npm:^3.1.0, p-timeout@npm:^3.2.0":
|
"p-timeout@npm:^3.1.0, p-timeout@npm:^3.2.0":
|
||||||
version: 3.2.0
|
version: 3.2.0
|
||||||
resolution: "p-timeout@npm:3.2.0"
|
resolution: "p-timeout@npm:3.2.0"
|
||||||
@@ -6243,6 +6261,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"p-timeout@npm:^6.1.2":
|
||||||
|
version: 6.1.2
|
||||||
|
resolution: "p-timeout@npm:6.1.2"
|
||||||
|
checksum: 10c0/d46b90a9a5fb7c650a5c56dd5cf7102ea9ab6ce998defa2b3d4672789aaec4e2f45b3b0b5a4a3e17a0fb94301ad5dd26da7d8728402e48db2022ad1847594d19
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"p-try@npm:^2.0.0":
|
"p-try@npm:^2.0.0":
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
resolution: "p-try@npm:2.2.0"
|
resolution: "p-try@npm:2.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user