Merge pull request #526 from dermotduffy/menu-priority
Improve menu versatility: support ordering and alignment per button
This commit is contained in:
@@ -184,19 +184,31 @@ All configuration is under:
|
|||||||
```yaml
|
```yaml
|
||||||
menu:
|
menu:
|
||||||
buttons:
|
buttons:
|
||||||
|
[button]:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Buttons
|
||||||
|
|
||||||
|
| Option | Overridable | Description |
|
||||||
|
| - | - | - |
|
||||||
|
| `frigate` | :white_check_mark: | The `Frigate` menu button: brings the user to the default configured view (`view.default`), or collapses/expands the menu if the `menu.style` is `hidden` . |
|
||||||
|
| `cameras` | :white_check_mark: | The camera selection submenu. Will only appear if multiple cameras are configured. |
|
||||||
|
| `live` | :white_check_mark: | The `live` view menu button: brings the user to the `live` view. See [views](#views) below.|
|
||||||
|
| `clips` | :white_check_mark: | The `clips` view menu button: brings the user to the `clips` view on tap and the most-recent `clip` view on hold. See [views](#views) below. This button will never be shown if the `camera_name` for the selected camera is not auto-detected/specified (e.g. non-Frigate cameras), or if the `camera_name` is `birdseye`.|
|
||||||
|
| `snapshots` | :white_check_mark: | The `snapshots` view menu button: brings the user to the `clips` view on tap and the most-recent `snapshot` view on hold. See [views](#views) below. This button will never be shown if the `camera_name` for the selected camera is not auto-detected/specified (e.g. non-Frigate cameras), or if the `camera_name` is `birdseye`.|
|
||||||
|
| `image` | :white_check_mark: | The `image` view menu button: brings the user to the static `image` view. See [views](#views) below.|
|
||||||
|
| `download` | :white_check_mark: | The `download` menu button: allow direct download of the media being displayed.|
|
||||||
|
| `frigate_ui` | :white_check_mark: | The `frigate_ui` menu button: brings the user to a context-appropriate page on the Frigate UI (e.g. the camera homepage). Will only appear if the `frigate.url` option is set.|
|
||||||
|
| `fullscreen` | :white_check_mark: | The `fullscreen` menu button: expand the card to consume the fullscreen. |
|
||||||
|
|
||||||
|
##### Configuration on each button
|
||||||
|
|
||||||
| Option | Default | Overridable | Description |
|
| Option | Default | Overridable | Description |
|
||||||
| - | - | - | - |
|
| - | - | - | - |
|
||||||
| `frigate` | `true` | :white_check_mark: | Whether to show the `Frigate` menu button: brings the user to the default configured view (`view.default`), or collapses/expands the menu if the `menu.style` is `hidden` . |
|
| `enabled` | `true` for all buttons except `image` | :white_check_mark: | Whether or not to show the button. |
|
||||||
| `cameras` | `true` | :white_check_mark: | Whether to show the camera selection submenu. Will only appear if multiple cameras are configured. |
|
| `priority` | `50` | :white_check_mark: | The button priority. Higher priority buttons are ordered closer to the start of the menu alignment (i.e. a button with priority `70` will order further to the left than a button with priority `60`, when the menu alignment is `left`). Minimum `0`, maximum `100`.|
|
||||||
| `live` | `true` | :white_check_mark: | Whether to show the `live` view menu button: brings the user to the `live` view. See [views](#views) below.|
|
| `icon` | | :white_check_mark: | An icon to overriding the default for that button, e.g. `mdi:camera-front`. |
|
||||||
| `clips` | `true` | :white_check_mark: | Whether to show the `clips` view menu button: brings the user to the `clips` view on tap and the most-recent `clip` view on hold. See [views](#views) below. This button will never be shown if the `camera_name` for the selected camera is not auto-detected/specified (e.g. non-Frigate cameras), or if the `camera_name` is `birdseye`.|
|
| `alignment` | `matching` | :white_check_mark: | Whether this button should have an alignment that is `matching` the menu alignment or `opposing` the menu. Can be used to create two separate groups of buttons on the menu. `priority` orders buttons within a given `alignment`. |
|
||||||
| `snapshots` | `true` | :white_check_mark: | Whether to show the `snapshots` view menu button: brings the user to the `clips` view on tap and the most-recent `snapshot` view on hold. See [views](#views) below. This button will never be shown if the `camera_name` for the selected camera is not auto-detected/specified (e.g. non-Frigate cameras), or if the `camera_name` is `birdseye`.|
|
|
||||||
| `image` | `false` | :white_check_mark: | Whether to show the `image` view menu button: brings the user to the static `image` view. See [views](#views) below.|
|
|
||||||
| `download` | `true` | :white_check_mark: | Whether to show the `download` menu button: allow direct download of the media being displayed.|
|
|
||||||
| `frigate_ui` | `true` | :white_check_mark: | Whether to show the `frigate_ui` menu button: brings the user to a context-appropriate page on the Frigate UI (e.g. the camera homepage). Will only appear if the `frigate.url` option is set.|
|
|
||||||
| `fullscreen` | `true` | :white_check_mark: | Whether to show the `fullscreen` menu button: expand the card to consume the fullscreen. |
|
|
||||||
|
|
||||||
### Live Options
|
### Live Options
|
||||||
|
|
||||||
@@ -1289,6 +1301,26 @@ card_mod:
|
|||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
### Overriding default menu behavior
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand: Overriding default menu behavior</summary>
|
||||||
|
|
||||||
|
This example moves the fullscreen button into its own group aligned to the `left`, enables the `image` button and orders it furthest to the `right`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
[...]
|
||||||
|
menu:
|
||||||
|
alignment: right
|
||||||
|
buttons:
|
||||||
|
image:
|
||||||
|
enabled: true
|
||||||
|
priority: 100
|
||||||
|
fullscreen:
|
||||||
|
alignment: opposing
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Using a dependent camera
|
### Using a dependent camera
|
||||||
|
|
||||||
`dependent_cameras` allows events for other cameras to be shown along with the currently selected camera. For example, this can be used to show events with the `birdseye` camera (since it will not have events of its own).
|
`dependent_cameras` allows events for other cameras to be shown along with the currently selected camera. For example, this can be used to show events with the `birdseye` camera (since it will not have events of its own).
|
||||||
|
|||||||
+32
-33
@@ -278,25 +278,19 @@ export class FrigateCard extends LitElement {
|
|||||||
protected _getMenuButtons(): MenuButton[] {
|
protected _getMenuButtons(): MenuButton[] {
|
||||||
const buttons: MenuButton[] = [];
|
const buttons: MenuButton[] = [];
|
||||||
|
|
||||||
if (this._getConfig().menu.buttons.frigate) {
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
// Use a magic icon value that the menu will use to render the custom
|
||||||
|
// Frigate icon.
|
||||||
|
icon: FRIGATE_BUTTON_MENU_ICON,
|
||||||
|
...this._getConfig().menu.buttons.frigate,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.menu.buttons.frigate'),
|
title: localize('config.menu.buttons.frigate'),
|
||||||
// Use a magic icon value that the menu will use to render the icon as
|
|
||||||
// it deems appropriate (certain menu configurations change the menu
|
|
||||||
// icon for the 'Frigate' button).
|
|
||||||
icon: FRIGATE_BUTTON_MENU_ICON,
|
|
||||||
tap_action: FrigateCardMenu.isHidingMenu(this._getConfig().menu)
|
tap_action: FrigateCardMenu.isHidingMenu(this._getConfig().menu)
|
||||||
? (createFrigateCardCustomAction('menu_toggle') as FrigateCardCustomAction)
|
? (createFrigateCardCustomAction('menu_toggle') as FrigateCardCustomAction)
|
||||||
: (createFrigateCardCustomAction('default') as FrigateCardCustomAction),
|
: (createFrigateCardCustomAction('default') as FrigateCardCustomAction),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (this._cameras && this._cameras.size > 1) {
|
||||||
this._getConfig().menu.buttons.cameras &&
|
|
||||||
this._cameras &&
|
|
||||||
this._cameras.size > 1
|
|
||||||
) {
|
|
||||||
const menuItems = Array.from(this._cameras, ([camera, config]) => {
|
const menuItems = Array.from(this._cameras, ([camera, config]) => {
|
||||||
return {
|
return {
|
||||||
icon: getCameraIcon(this._hass, config),
|
icon: getCameraIcon(this._hass, config),
|
||||||
@@ -309,37 +303,37 @@ export class FrigateCard extends LitElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:video-switch',
|
||||||
|
...this._getConfig().menu.buttons.cameras,
|
||||||
type: 'custom:frigate-card-menu-submenu',
|
type: 'custom:frigate-card-menu-submenu',
|
||||||
title: localize('config.menu.buttons.cameras'),
|
title: localize('config.menu.buttons.cameras'),
|
||||||
icon: 'mdi:video-switch',
|
|
||||||
items: menuItems,
|
items: menuItems,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._getConfig().menu.buttons.live) {
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:cctv',
|
||||||
|
...this._getConfig().menu.buttons.live,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.view.views.live'),
|
title: localize('config.view.views.live'),
|
||||||
icon: 'mdi:cctv',
|
|
||||||
style: this._view?.is('live') ? this._getEmphasizedStyle() : {},
|
style: this._view?.is('live') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createFrigateCardCustomAction('live') as FrigateCardCustomAction,
|
tap_action: createFrigateCardCustomAction('live') as FrigateCardCustomAction,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const cameraConfig = this._getSelectedCameraConfig();
|
const cameraConfig = this._getSelectedCameraConfig();
|
||||||
|
|
||||||
// Don't show `clips` button if there's no `camera_name` (e.g. non-Frigate
|
// Don't show `clips` button if there's no `camera_name` (e.g. non-Frigate
|
||||||
// cameras), or is birdseye (unless there are dependent cameras).
|
// cameras), or is birdseye (unless there are dependent cameras).
|
||||||
if (
|
if (
|
||||||
this._getConfig().menu.buttons.clips &&
|
|
||||||
cameraConfig?.camera_name &&
|
cameraConfig?.camera_name &&
|
||||||
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
|
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
|
||||||
cameraConfig?.dependent_cameras?.length)
|
cameraConfig?.dependent_cameras?.length)
|
||||||
) {
|
) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:filmstrip',
|
||||||
|
...this._getConfig().menu.buttons.clips,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.view.views.clips'),
|
title: localize('config.view.views.clips'),
|
||||||
icon: 'mdi:filmstrip',
|
|
||||||
style: this._view?.is('clips') ? this._getEmphasizedStyle() : {},
|
style: this._view?.is('clips') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createFrigateCardCustomAction('clips') as FrigateCardCustomAction,
|
tap_action: createFrigateCardCustomAction('clips') as FrigateCardCustomAction,
|
||||||
hold_action: createFrigateCardCustomAction('clip') as FrigateCardCustomAction,
|
hold_action: createFrigateCardCustomAction('clip') as FrigateCardCustomAction,
|
||||||
@@ -349,15 +343,15 @@ export class FrigateCard extends LitElement {
|
|||||||
// Don't show `snapshots` button if there's no `camera_name` (e.g. non-Frigate
|
// Don't show `snapshots` button if there's no `camera_name` (e.g. non-Frigate
|
||||||
// cameras), or is birdseye (unless there are dependent cameras).
|
// cameras), or is birdseye (unless there are dependent cameras).
|
||||||
if (
|
if (
|
||||||
this._getConfig().menu.buttons.snapshots &&
|
|
||||||
cameraConfig?.camera_name &&
|
cameraConfig?.camera_name &&
|
||||||
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
|
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
|
||||||
cameraConfig?.dependent_cameras?.length)
|
cameraConfig?.dependent_cameras?.length)
|
||||||
) {
|
) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:camera',
|
||||||
|
...this._getConfig().menu.buttons.snapshots,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.view.views.snapshots'),
|
title: localize('config.view.views.snapshots'),
|
||||||
icon: 'mdi:camera',
|
|
||||||
style: this._view?.is('snapshots') ? this._getEmphasizedStyle() : {},
|
style: this._view?.is('snapshots') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createFrigateCardCustomAction(
|
tap_action: createFrigateCardCustomAction(
|
||||||
'snapshots',
|
'snapshots',
|
||||||
@@ -368,54 +362,55 @@ export class FrigateCard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._getConfig().menu.buttons.image) {
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:image',
|
||||||
|
...this._getConfig().menu.buttons.image,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.view.views.image'),
|
title: localize('config.view.views.image'),
|
||||||
icon: 'mdi:image',
|
|
||||||
style: this._view?.is('image') ? this._getEmphasizedStyle() : {},
|
style: this._view?.is('image') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createFrigateCardCustomAction('image') as FrigateCardCustomAction,
|
tap_action: createFrigateCardCustomAction('image') as FrigateCardCustomAction,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (this._getConfig().menu.buttons.timeline) {
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:chart-gantt',
|
||||||
|
...this._getConfig().menu.buttons.timeline,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.view.views.timeline'),
|
title: localize('config.view.views.timeline'),
|
||||||
icon: 'mdi:chart-gantt',
|
|
||||||
style: this._view?.is('timeline') ? this._getEmphasizedStyle() : {},
|
style: this._view?.is('timeline') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createFrigateCardCustomAction('timeline') as FrigateCardCustomAction,
|
tap_action: createFrigateCardCustomAction('timeline') as FrigateCardCustomAction,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this._getConfig().menu.buttons.download &&
|
this._view?.isViewerView() ||
|
||||||
(this._view?.isViewerView() || (this._view?.is('timeline') && !!this._view?.media))
|
(this._view?.is('timeline') && !!this._view?.media)
|
||||||
) {
|
) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:download',
|
||||||
|
...this._getConfig().menu.buttons.download,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.menu.buttons.download'),
|
title: localize('config.menu.buttons.download'),
|
||||||
icon: 'mdi:download',
|
|
||||||
tap_action: createFrigateCardCustomAction('download') as FrigateCardCustomAction,
|
tap_action: createFrigateCardCustomAction('download') as FrigateCardCustomAction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._getConfig().menu.buttons.frigate_ui && cameraConfig?.frigate_url) {
|
if (cameraConfig?.frigate_url) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: 'mdi:web',
|
||||||
|
...this._getConfig().menu.buttons.frigate_ui,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.menu.buttons.frigate_ui'),
|
title: localize('config.menu.buttons.frigate_ui'),
|
||||||
icon: 'mdi:web',
|
|
||||||
tap_action: createFrigateCardCustomAction(
|
tap_action: createFrigateCardCustomAction(
|
||||||
'frigate_ui',
|
'frigate_ui',
|
||||||
) as FrigateCardCustomAction,
|
) as FrigateCardCustomAction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._getConfig().menu.buttons.fullscreen && screenfull.isEnabled) {
|
if (screenfull.isEnabled) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
icon: screenfull.isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen',
|
||||||
|
...this._getConfig().menu.buttons.fullscreen,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.menu.buttons.fullscreen'),
|
title: localize('config.menu.buttons.fullscreen'),
|
||||||
icon: screenfull.isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen',
|
|
||||||
tap_action: createFrigateCardCustomAction(
|
tap_action: createFrigateCardCustomAction(
|
||||||
'fullscreen',
|
'fullscreen',
|
||||||
) as FrigateCardCustomAction,
|
) as FrigateCardCustomAction,
|
||||||
@@ -489,7 +484,11 @@ export class FrigateCard extends LitElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this._getConfig().cameras && Array.isArray(this._getConfig().cameras)) {
|
if (this._getConfig().cameras && Array.isArray(this._getConfig().cameras)) {
|
||||||
await Promise.all(this._getConfig().cameras.map(addCameraConfig.bind(this)));
|
// Cameras are loaded sequentially rather than in parallel to preserve the
|
||||||
|
// order of the input camera array.
|
||||||
|
for (const camera of this._getConfig().cameras) {
|
||||||
|
await addCameraConfig(camera);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cameras.size) {
|
if (!cameras.size) {
|
||||||
|
|||||||
+72
-4
@@ -1,4 +1,11 @@
|
|||||||
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
unsafeCSS,
|
||||||
|
PropertyValues,
|
||||||
|
} from 'lit';
|
||||||
import { HASSDomEvent, HomeAssistant } from 'custom-card-helpers';
|
import { HASSDomEvent, HomeAssistant } from 'custom-card-helpers';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
@@ -14,6 +21,7 @@ import type {
|
|||||||
ActionType,
|
ActionType,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuConfig,
|
MenuConfig,
|
||||||
|
MenuItem,
|
||||||
StateParameters,
|
StateParameters,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import {
|
import {
|
||||||
@@ -198,12 +206,52 @@ export class FrigateCardMenu extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure menu buttons are sorted before the render.
|
||||||
|
* @param changedProps The changed properties
|
||||||
|
*/
|
||||||
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
|
const style = this._menuConfig?.style;
|
||||||
|
const sortButtons = (a: MenuItem, b: MenuItem): number => {
|
||||||
|
// If the menu is hidden, the Frigate button must come first.
|
||||||
|
if (style === 'hidden') {
|
||||||
|
if (a.icon === FRIGATE_BUTTON_MENU_ICON) {
|
||||||
|
return -1;
|
||||||
|
} else if (b.icon === FRIGATE_BUTTON_MENU_ICON) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise sort by priority.
|
||||||
|
if (
|
||||||
|
a.priority === undefined ||
|
||||||
|
(b.priority !== undefined && b.priority > a.priority)
|
||||||
|
) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
b.priority === undefined ||
|
||||||
|
(a.priority !== undefined && b.priority < a.priority)
|
||||||
|
) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (changedProps.has('_menuConfig') || changedProps.has('buttons')) {
|
||||||
|
this.buttons.sort(sortButtons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a button.
|
* Render a button.
|
||||||
* @param button The button configuration to render.
|
* @param button The button configuration to render.
|
||||||
* @returns A rendered template or void.
|
* @returns A rendered template or void.
|
||||||
*/
|
*/
|
||||||
protected _renderButton(button: MenuButton): TemplateResult | void {
|
protected _renderButton(button: MenuButton): TemplateResult | void {
|
||||||
|
if (button.enabled === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (button.type == 'custom:frigate-card-menu-submenu') {
|
if (button.type == 'custom:frigate-card-menu-submenu') {
|
||||||
return html` <frigate-card-submenu
|
return html` <frigate-card-submenu
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -274,11 +322,31 @@ export class FrigateCardMenu extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the hidden menu isn't expanded, only show the Frigate button.
|
// If the hidden menu isn't expanded, only show the Frigate button.
|
||||||
const buttons =
|
const matchingButtons =
|
||||||
style !== 'hidden' || this.expanded
|
style !== 'hidden' || this.expanded
|
||||||
? this.buttons
|
? this.buttons.filter(
|
||||||
|
(button) => !button.alignment || button.alignment === 'matching',
|
||||||
|
)
|
||||||
: this.buttons.filter((button) => button.icon === FRIGATE_BUTTON_MENU_ICON);
|
: this.buttons.filter((button) => button.icon === FRIGATE_BUTTON_MENU_ICON);
|
||||||
return html` ${buttons.map((button) => this._renderButton(button))} `;
|
|
||||||
|
const opposingButtons =
|
||||||
|
style !== 'hidden' || this.expanded
|
||||||
|
? this.buttons.filter((button) => button.alignment === 'opposing')
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const matchingStyle = {
|
||||||
|
flex: String(matchingButtons.length),
|
||||||
|
};
|
||||||
|
const opposingStyle = {
|
||||||
|
flex: String(opposingButtons.length),
|
||||||
|
};
|
||||||
|
|
||||||
|
return html` <div class="matching" style="${styleMap(matchingStyle)}">
|
||||||
|
${matchingButtons.map((button) => this._renderButton(button))}
|
||||||
|
</div>
|
||||||
|
<div class="opposing" style="${styleMap(opposingStyle)}">
|
||||||
|
${opposingButtons.map((button) => this._renderButton(button))}
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ import {
|
|||||||
CONF_LIVE_PRELOAD,
|
CONF_LIVE_PRELOAD,
|
||||||
CONF_LIVE_WEBRTC_CARD,
|
CONF_LIVE_WEBRTC_CARD,
|
||||||
CONF_MENU,
|
CONF_MENU,
|
||||||
|
CONF_MENU_BUTTONS_FRIGATE,
|
||||||
|
CONF_MENU_BUTTONS_CAMERAS,
|
||||||
|
CONF_MENU_BUTTONS_LIVE,
|
||||||
|
CONF_MENU_BUTTONS_CLIPS,
|
||||||
|
CONF_MENU_BUTTONS_SNAPSHOTS,
|
||||||
|
CONF_MENU_BUTTONS_IMAGE,
|
||||||
|
CONF_MENU_BUTTONS_DOWNLOAD,
|
||||||
|
CONF_MENU_BUTTONS_FRIGATE_UI,
|
||||||
|
CONF_MENU_BUTTONS_FULLSCREEN,
|
||||||
CONF_MENU_BUTTON_SIZE,
|
CONF_MENU_BUTTON_SIZE,
|
||||||
CONF_MENU_POSITION,
|
CONF_MENU_POSITION,
|
||||||
CONF_MENU_STYLE,
|
CONF_MENU_STYLE,
|
||||||
@@ -476,6 +485,24 @@ const upgradeMenuConditionToMenuOverride = (): ((
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a menu button from a boolean to a priority.
|
||||||
|
* @param value The boolean true/false for show/hide the switch.
|
||||||
|
* @returns A priority value.
|
||||||
|
*/
|
||||||
|
const menuButtonBooleanToObject = function (
|
||||||
|
value: unknown,
|
||||||
|
): { enabled: boolean } | null | undefined {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// If it's not a boolean, or it's just true, remove it.
|
||||||
|
if (typeof value !== 'boolean' || value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { enabled: value };
|
||||||
|
};
|
||||||
|
|
||||||
const UPGRADES = [
|
const UPGRADES = [
|
||||||
// v1.2.1 -> v2.0.0
|
// v1.2.1 -> v2.0.0
|
||||||
upgradeMoveTo('frigate_url', 'frigate.url'),
|
upgradeMoveTo('frigate_url', 'frigate.url'),
|
||||||
@@ -539,4 +566,13 @@ const UPGRADES = [
|
|||||||
),
|
),
|
||||||
upgradeWithOverrides('event_gallery.min_columns', deleteProperty),
|
upgradeWithOverrides('event_gallery.min_columns', deleteProperty),
|
||||||
upgradeMenuModeToStyleAndPosition(),
|
upgradeMenuModeToStyleAndPosition(),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_FRIGATE, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_CAMERAS, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_LIVE, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_CLIPS, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_SNAPSHOTS, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_IMAGE, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_DOWNLOAD, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_FRIGATE_UI, menuButtonBooleanToObject),
|
||||||
|
upgradeWithOverrides(CONF_MENU_BUTTONS_FULLSCREEN, menuButtonBooleanToObject),
|
||||||
];
|
];
|
||||||
|
|||||||
+26
-20
@@ -17,7 +17,8 @@ export const CONF_CAMERAS_ARRAY_TITLE = `${CONF_CAMERAS}.#.title` as const;
|
|||||||
export const CONF_CAMERAS_ARRAY_ICON = `${CONF_CAMERAS}.#.icon` as const;
|
export const CONF_CAMERAS_ARRAY_ICON = `${CONF_CAMERAS}.#.icon` as const;
|
||||||
export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY =
|
export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY =
|
||||||
`${CONF_CAMERAS}.#.webrtc_card.entity` as const;
|
`${CONF_CAMERAS}.#.webrtc_card.entity` as const;
|
||||||
export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL = `${CONF_CAMERAS}.#.webrtc_card.url` as const;
|
export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL =
|
||||||
|
`${CONF_CAMERAS}.#.webrtc_card.url` as const;
|
||||||
export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER =
|
export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER =
|
||||||
`${CONF_CAMERAS}.#.live_provider` as const;
|
`${CONF_CAMERAS}.#.live_provider` as const;
|
||||||
export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS =
|
export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS =
|
||||||
@@ -35,9 +36,9 @@ export const CONF_VIEW_UPDATE_SECONDS = `${CONF_VIEW}.update_seconds` as const;
|
|||||||
|
|
||||||
export const CONF_EVENT_GALLERY = 'event_gallery' as const;
|
export const CONF_EVENT_GALLERY = 'event_gallery' as const;
|
||||||
export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS =
|
export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS =
|
||||||
`${CONF_EVENT_GALLERY}.controls.thumbnails.show_details` as const;
|
`${CONF_EVENT_GALLERY}.controls.thumbnails.show_details` as const;
|
||||||
export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
||||||
`${CONF_EVENT_GALLERY}.controls.thumbnails.show_controls` as const;
|
`${CONF_EVENT_GALLERY}.controls.thumbnails.show_controls` as const;
|
||||||
export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE =
|
export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE =
|
||||||
`${CONF_EVENT_GALLERY}.controls.thumbnails.size` as const;
|
`${CONF_EVENT_GALLERY}.controls.thumbnails.size` as const;
|
||||||
|
|
||||||
@@ -55,9 +56,9 @@ export const CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE =
|
|||||||
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE =
|
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE =
|
||||||
`${CONF_EVENT_VIEWER}.controls.thumbnails.mode` as const;
|
`${CONF_EVENT_VIEWER}.controls.thumbnails.mode` as const;
|
||||||
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS =
|
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS =
|
||||||
`${CONF_EVENT_VIEWER}.controls.thumbnails.show_details` as const;
|
`${CONF_EVENT_VIEWER}.controls.thumbnails.show_details` as const;
|
||||||
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
||||||
`${CONF_EVENT_VIEWER}.controls.thumbnails.show_controls` as const;
|
`${CONF_EVENT_VIEWER}.controls.thumbnails.show_controls` as const;
|
||||||
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE =
|
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE =
|
||||||
`${CONF_EVENT_VIEWER}.controls.thumbnails.size` as const;
|
`${CONF_EVENT_VIEWER}.controls.thumbnails.size` as const;
|
||||||
export const CONF_EVENT_VIEWER_CONTROLS_TITLE_MODE =
|
export const CONF_EVENT_VIEWER_CONTROLS_TITLE_MODE =
|
||||||
@@ -78,9 +79,9 @@ export const CONF_LIVE_CONTROLS_THUMBNAILS_MODE =
|
|||||||
export const CONF_LIVE_CONTROLS_THUMBNAILS_SIZE =
|
export const CONF_LIVE_CONTROLS_THUMBNAILS_SIZE =
|
||||||
`${CONF_LIVE}.controls.thumbnails.size` as const;
|
`${CONF_LIVE}.controls.thumbnails.size` as const;
|
||||||
export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DETAILS =
|
export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DETAILS =
|
||||||
`${CONF_LIVE}.controls.thumbnails.show_details` as const;
|
`${CONF_LIVE}.controls.thumbnails.show_details` as const;
|
||||||
export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
||||||
`${CONF_LIVE}.controls.thumbnails.show_controls` as const;
|
`${CONF_LIVE}.controls.thumbnails.show_controls` as const;
|
||||||
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;
|
||||||
@@ -99,29 +100,34 @@ export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const;
|
|||||||
|
|
||||||
export const CONF_TIMELINE = 'timeline' as const;
|
export const CONF_TIMELINE = 'timeline' as const;
|
||||||
export const CONF_TIMELINE_WINDOW_SECONDS = `${CONF_TIMELINE}.window_seconds` as const;
|
export const CONF_TIMELINE_WINDOW_SECONDS = `${CONF_TIMELINE}.window_seconds` as const;
|
||||||
export const CONF_TIMELINE_CLUSTERING_THRESHOLD = `${CONF_TIMELINE}.clustering_threshold` as const;
|
export const CONF_TIMELINE_CLUSTERING_THRESHOLD =
|
||||||
|
`${CONF_TIMELINE}.clustering_threshold` as const;
|
||||||
export const CONF_TIMELINE_MEDIA = `${CONF_TIMELINE}.media` as const;
|
export const CONF_TIMELINE_MEDIA = `${CONF_TIMELINE}.media` as const;
|
||||||
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE = `${CONF_TIMELINE}.controls.thumbnails.mode` as const;
|
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE =
|
||||||
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE = `${CONF_TIMELINE}.controls.thumbnails.size` as const;
|
`${CONF_TIMELINE}.controls.thumbnails.mode` as const;
|
||||||
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS = `${CONF_TIMELINE}.controls.thumbnails.show_details` as const;
|
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE =
|
||||||
|
`${CONF_TIMELINE}.controls.thumbnails.size` as const;
|
||||||
|
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS =
|
||||||
|
`${CONF_TIMELINE}.controls.thumbnails.show_details` as const;
|
||||||
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_CONTROLS =
|
||||||
`${CONF_TIMELINE}.controls.thumbnails.show_controls` as const;
|
`${CONF_TIMELINE}.controls.thumbnails.show_controls` as const;
|
||||||
|
|
||||||
export const CONF_MENU = 'menu' as const;
|
export const CONF_MENU = 'menu' as const;
|
||||||
export const CONF_MENU_ALIGNMENT = `${CONF_MENU}.alignment` as const;
|
export const CONF_MENU_ALIGNMENT = `${CONF_MENU}.alignment` as const;
|
||||||
export const CONF_MENU_POSITION = `${CONF_MENU}.position` as const;
|
export const CONF_MENU_POSITION = `${CONF_MENU}.position` as const;
|
||||||
export const CONF_MENU_STYLE = `${CONF_MENU}.style` as const;
|
export const CONF_MENU_STYLE = `${CONF_MENU}.style` as const;
|
||||||
|
export const CONF_MENU_BUTTON_SIZE = `${CONF_MENU}.button_size` as const;
|
||||||
|
export const CONF_MENU_BUTTONS = `${CONF_MENU}.buttons` as const;
|
||||||
|
|
||||||
|
export const CONF_MENU_BUTTONS_CAMERAS = `${CONF_MENU}.buttons.cameras` as const;
|
||||||
|
export const CONF_MENU_BUTTONS_CLIPS = `${CONF_MENU}.buttons.clips` as const;
|
||||||
|
export const CONF_MENU_BUTTONS_DOWNLOAD = `${CONF_MENU}.buttons.download` as const;
|
||||||
export const CONF_MENU_BUTTONS_FRIGATE = `${CONF_MENU}.buttons.frigate` as const;
|
export const CONF_MENU_BUTTONS_FRIGATE = `${CONF_MENU}.buttons.frigate` as const;
|
||||||
export const CONF_MENU_BUTTONS_FRIGATE_UI = `${CONF_MENU}.buttons.frigate_ui` as const;
|
export const CONF_MENU_BUTTONS_FRIGATE_UI = `${CONF_MENU}.buttons.frigate_ui` as const;
|
||||||
export const CONF_MENU_BUTTONS_FRIGATE_FULLSCREEN =
|
export const CONF_MENU_BUTTONS_FULLSCREEN = `${CONF_MENU}.buttons.fullscreen` as const;
|
||||||
`${CONF_MENU}.buttons.fullscreen` as const;
|
|
||||||
export const CONF_MENU_BUTTONS_FRIGATE_DOWNLOAD =
|
|
||||||
`${CONF_MENU}.buttons.download` as const;
|
|
||||||
export const CONF_MENU_BUTTONS_LIVE = `${CONF_MENU}.buttons.live` as const;
|
|
||||||
export const CONF_MENU_BUTTONS_CLIPS = `${CONF_MENU}.buttons.clips` as const;
|
|
||||||
export const CONF_MENU_BUTTONS_SNAPSHOTS = `${CONF_MENU}.buttons.snapshots` as const;
|
|
||||||
export const CONF_MENU_BUTTONS_IMAGE = `${CONF_MENU}.buttons.image` as const;
|
export const CONF_MENU_BUTTONS_IMAGE = `${CONF_MENU}.buttons.image` as const;
|
||||||
export const CONF_MENU_BUTTON_SIZE = `${CONF_MENU}.button_size` as const;
|
export const CONF_MENU_BUTTONS_LIVE = `${CONF_MENU}.buttons.live` as const;
|
||||||
|
export const CONF_MENU_BUTTONS_SNAPSHOTS = `${CONF_MENU}.buttons.snapshots` as const;
|
||||||
|
|
||||||
export const CONF_DIMENSIONS = 'dimensions' as const;
|
export const CONF_DIMENSIONS = 'dimensions' as const;
|
||||||
export const CONF_DIMENSIONS_ASPECT_RATIO = `${CONF_DIMENSIONS}.aspect_ratio` as const;
|
export const CONF_DIMENSIONS_ASPECT_RATIO = `${CONF_DIMENSIONS}.aspect_ratio` as const;
|
||||||
|
|||||||
+219
-178
@@ -6,6 +6,7 @@ import { HomeAssistant, LovelaceCardEditor, fireEvent } from 'custom-card-helper
|
|||||||
import { localize } from './localize/localize.js';
|
import { localize } from './localize/localize.js';
|
||||||
import {
|
import {
|
||||||
BUTTON_SIZE_MIN,
|
BUTTON_SIZE_MIN,
|
||||||
|
FRIGATE_MENU_PRIORITY_MAX,
|
||||||
RawFrigateCardConfig,
|
RawFrigateCardConfig,
|
||||||
RawFrigateCardConfigArray,
|
RawFrigateCardConfigArray,
|
||||||
THUMBNAIL_WIDTH_MAX,
|
THUMBNAIL_WIDTH_MAX,
|
||||||
@@ -65,14 +66,7 @@ import {
|
|||||||
CONF_LIVE_PRELOAD,
|
CONF_LIVE_PRELOAD,
|
||||||
CONF_LIVE_TRANSITION_EFFECT,
|
CONF_LIVE_TRANSITION_EFFECT,
|
||||||
CONF_MENU_ALIGNMENT,
|
CONF_MENU_ALIGNMENT,
|
||||||
CONF_MENU_BUTTONS_CLIPS,
|
CONF_MENU_BUTTONS,
|
||||||
CONF_MENU_BUTTONS_FRIGATE,
|
|
||||||
CONF_MENU_BUTTONS_FRIGATE_DOWNLOAD,
|
|
||||||
CONF_MENU_BUTTONS_FRIGATE_FULLSCREEN,
|
|
||||||
CONF_MENU_BUTTONS_FRIGATE_UI,
|
|
||||||
CONF_MENU_BUTTONS_IMAGE,
|
|
||||||
CONF_MENU_BUTTONS_LIVE,
|
|
||||||
CONF_MENU_BUTTONS_SNAPSHOTS,
|
|
||||||
CONF_MENU_BUTTON_SIZE,
|
CONF_MENU_BUTTON_SIZE,
|
||||||
CONF_MENU_POSITION,
|
CONF_MENU_POSITION,
|
||||||
CONF_MENU_STYLE,
|
CONF_MENU_STYLE,
|
||||||
@@ -104,89 +98,79 @@ import {
|
|||||||
|
|
||||||
import frigate_card_editor_style from './scss/editor.scss';
|
import frigate_card_editor_style from './scss/editor.scss';
|
||||||
|
|
||||||
|
const MENU_BUTTONS = 'buttons';
|
||||||
|
const MENU_CAMERAS = 'cameras';
|
||||||
|
const MENU_OPTIONS = 'options';
|
||||||
|
|
||||||
interface EditorOptionsSet {
|
interface EditorOptionsSet {
|
||||||
icon: string;
|
icon: string;
|
||||||
name: string;
|
name: string;
|
||||||
secondary: string;
|
secondary: string;
|
||||||
show: boolean;
|
|
||||||
}
|
}
|
||||||
interface EditorOptions {
|
interface EditorOptions {
|
||||||
[setName: string]: EditorOptionsSet;
|
[setName: string]: EditorOptionsSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EditorCameraTarget {
|
|
||||||
cameraIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditorOptionSetTarget {
|
|
||||||
optionSetName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditorSelectOption {
|
interface EditorSelectOption {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditorMenuTarget {
|
||||||
|
domain: string;
|
||||||
|
key: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
const options: EditorOptions = {
|
const options: EditorOptions = {
|
||||||
cameras: {
|
cameras: {
|
||||||
icon: 'video',
|
icon: 'video',
|
||||||
name: localize('editor.cameras'),
|
name: localize('editor.cameras'),
|
||||||
secondary: localize('editor.cameras_secondary'),
|
secondary: localize('editor.cameras_secondary'),
|
||||||
show: true,
|
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
icon: 'eye',
|
icon: 'eye',
|
||||||
name: localize('editor.view'),
|
name: localize('editor.view'),
|
||||||
secondary: localize('editor.view_secondary'),
|
secondary: localize('editor.view_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
icon: 'menu',
|
icon: 'menu',
|
||||||
name: localize('editor.menu'),
|
name: localize('editor.menu'),
|
||||||
secondary: localize('editor.menu_secondary'),
|
secondary: localize('editor.menu_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
live: {
|
live: {
|
||||||
icon: 'cctv',
|
icon: 'cctv',
|
||||||
name: localize('editor.live'),
|
name: localize('editor.live'),
|
||||||
secondary: localize('editor.live_secondary'),
|
secondary: localize('editor.live_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
event_viewer: {
|
event_viewer: {
|
||||||
icon: 'filmstrip',
|
icon: 'filmstrip',
|
||||||
name: localize('editor.event_viewer'),
|
name: localize('editor.event_viewer'),
|
||||||
secondary: localize('editor.event_viewer_secondary'),
|
secondary: localize('editor.event_viewer_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
event_gallery: {
|
event_gallery: {
|
||||||
icon: 'grid',
|
icon: 'grid',
|
||||||
name: localize('editor.event_gallery'),
|
name: localize('editor.event_gallery'),
|
||||||
secondary: localize('editor.event_gallery_secondary'),
|
secondary: localize('editor.event_gallery_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
icon: 'image',
|
icon: 'image',
|
||||||
name: localize('editor.image'),
|
name: localize('editor.image'),
|
||||||
secondary: localize('editor.image_secondary'),
|
secondary: localize('editor.image_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
timeline: {
|
timeline: {
|
||||||
icon: 'chart-gantt',
|
icon: 'chart-gantt',
|
||||||
name: localize('editor.timeline'),
|
name: localize('editor.timeline'),
|
||||||
secondary: localize('editor.timeline_secondary'),
|
secondary: localize('editor.timeline_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
dimensions: {
|
dimensions: {
|
||||||
icon: 'aspect-ratio',
|
icon: 'aspect-ratio',
|
||||||
name: localize('editor.dimensions'),
|
name: localize('editor.dimensions'),
|
||||||
secondary: localize('editor.dimensions_secondary'),
|
secondary: localize('editor.dimensions_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
overrides: {
|
overrides: {
|
||||||
icon: 'file-replace',
|
icon: 'file-replace',
|
||||||
name: localize('editor.overrides'),
|
name: localize('editor.overrides'),
|
||||||
secondary: localize('editor.overrides_secondary'),
|
secondary: localize('editor.overrides_secondary'),
|
||||||
show: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,7 +183,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
protected _configUpgradeable = false;
|
protected _configUpgradeable = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
protected _expandedCameraIndex: number | null = null;
|
protected _expandedMenus: Record<string, string | number> = {};
|
||||||
|
|
||||||
protected _viewModes: EditorSelectOption[] = [
|
protected _viewModes: EditorSelectOption[] = [
|
||||||
{ value: '', label: '' },
|
{ value: '', label: '' },
|
||||||
@@ -408,8 +392,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="option option-${optionSetName}"
|
class="option option-${optionSetName}"
|
||||||
@click=${this._toggleOptionHandler}
|
@click=${this._toggleMenu}
|
||||||
.optionSetName=${optionSetName}
|
.domain=${'options'}
|
||||||
|
.key=${optionSetName}
|
||||||
>
|
>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-icon .icon=${`mdi:${optionSet.icon}`}></ha-icon>
|
<ha-icon .icon=${`mdi:${optionSet.icon}`}></ha-icon>
|
||||||
@@ -465,12 +450,16 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
* Render an option/"select" selector.
|
* Render an option/"select" selector.
|
||||||
* @param configPath The configuration path to set/read.
|
* @param configPath The configuration path to set/read.
|
||||||
* @param options The options to show in the selector.
|
* @param options The options to show in the selector.
|
||||||
|
* @param params Option parameters to control the selector.
|
||||||
* @returns A rendered template.
|
* @returns A rendered template.
|
||||||
*/
|
*/
|
||||||
protected _renderOptionSelector(
|
protected _renderOptionSelector(
|
||||||
configPath: string,
|
configPath: string,
|
||||||
options: string[] | { value: string; label: string }[],
|
options: string[] | { value: string; label: string }[],
|
||||||
multiple?: boolean,
|
params?: {
|
||||||
|
multiple?: boolean;
|
||||||
|
label?: string;
|
||||||
|
},
|
||||||
): TemplateResult | void {
|
): TemplateResult | void {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return;
|
return;
|
||||||
@@ -480,9 +469,40 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
<ha-selector
|
<ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${{
|
.selector=${{
|
||||||
select: { mode: 'dropdown', multiple: !!multiple, options: options },
|
select: { mode: 'dropdown', multiple: !!params?.multiple, options: options },
|
||||||
}}
|
}}
|
||||||
.label=${this._getLabel(configPath)}
|
.label=${params?.label || this._getLabel(configPath)}
|
||||||
|
.value=${getConfigValue(this._config, configPath, '')}
|
||||||
|
.required=${false}
|
||||||
|
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
|
||||||
|
>
|
||||||
|
</ha-selector>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render an icon selector.
|
||||||
|
* @param configPath The configuration path to set/read.
|
||||||
|
* @param params Optional parameters to control the selector.
|
||||||
|
* @returns A rendered template.
|
||||||
|
*/
|
||||||
|
protected _renderIconSelector(
|
||||||
|
configPath: string,
|
||||||
|
params?: {
|
||||||
|
label?: string;
|
||||||
|
},
|
||||||
|
): TemplateResult | void {
|
||||||
|
if (!this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${{
|
||||||
|
icon: {},
|
||||||
|
}}
|
||||||
|
.label=${params?.label || this._getLabel(configPath)}
|
||||||
.value=${getConfigValue(this._config, configPath, '')}
|
.value=${getConfigValue(this._config, configPath, '')}
|
||||||
.required=${false}
|
.required=${false}
|
||||||
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
|
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
|
||||||
@@ -494,29 +514,30 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
/**
|
/**
|
||||||
* Render a number slider.
|
* Render a number slider.
|
||||||
* @param configPath Configuration path of the variable.
|
* @param configPath Configuration path of the variable.
|
||||||
* @param valueDefault The default value.
|
* @param params Optional parameters to control the selector.
|
||||||
* @param icon The icon to use on the slider.
|
|
||||||
* @param min The minimum value.
|
|
||||||
* @param max The maximum value.
|
|
||||||
* @returns A rendered template.
|
* @returns A rendered template.
|
||||||
*/
|
*/
|
||||||
protected _renderNumberInput(
|
protected _renderNumberInput(
|
||||||
configPath: string,
|
configPath: string,
|
||||||
min?: number,
|
params?: {
|
||||||
max?: number,
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
label?: string;
|
||||||
|
default?: number;
|
||||||
|
},
|
||||||
): TemplateResult | void {
|
): TemplateResult | void {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const value = getConfigValue(this._config, configPath);
|
const value = getConfigValue(this._config, configPath);
|
||||||
const mode = max === undefined ? 'box' : 'slider';
|
const mode = params?.max === undefined ? 'box' : 'slider';
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-selector
|
<ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${{ number: { min: min || 0, max: max, mode: mode } }}
|
.selector=${{ number: { min: params?.min || 0, max: params?.max, mode: mode } }}
|
||||||
.label=${this._getLabel(configPath)}
|
.label=${params?.label || this._getLabel(configPath)}
|
||||||
.value=${value}
|
.value=${value ?? params?.default}
|
||||||
.required=${false}
|
.required=${false}
|
||||||
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
|
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
|
||||||
>
|
>
|
||||||
@@ -549,6 +570,62 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render an editor menu for the card menu buttons.
|
||||||
|
* @param button The name of the button.
|
||||||
|
* @returns A rendered template.
|
||||||
|
*/
|
||||||
|
protected _renderMenuButton(button: string): TemplateResult {
|
||||||
|
const menuButtonAlignments: EditorSelectOption[] = [
|
||||||
|
{ value: '', label: '' },
|
||||||
|
{ value: 'matching', label: localize('config.menu.buttons.alignments.matching') },
|
||||||
|
{ value: 'opposing', label: localize('config.menu.buttons.alignments.opposing') },
|
||||||
|
];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="submenu-header"
|
||||||
|
@click=${this._toggleMenu}
|
||||||
|
.domain=${MENU_BUTTONS}
|
||||||
|
.key=${button}
|
||||||
|
>
|
||||||
|
<ha-icon .icon=${'mdi:gesture-tap-button'}></ha-icon>
|
||||||
|
<span
|
||||||
|
>${localize('editor.button') +
|
||||||
|
': ' +
|
||||||
|
localize(`config.${CONF_MENU_BUTTONS}.${button}`)}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this._expandedMenus[MENU_BUTTONS] === button
|
||||||
|
? html` <div class="values">
|
||||||
|
${this._renderSwitch(
|
||||||
|
`${CONF_MENU_BUTTONS}.${button}.enabled`,
|
||||||
|
frigateCardConfigDefaults.menu.buttons[button]?.enabled ?? true,
|
||||||
|
{
|
||||||
|
label: localize('config.menu.buttons.enabled'),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
${this._renderOptionSelector(
|
||||||
|
`${CONF_MENU_BUTTONS}.${button}.alignment`,
|
||||||
|
menuButtonAlignments,
|
||||||
|
{
|
||||||
|
label: localize('config.menu.buttons.alignment'),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
${this._renderNumberInput(`${CONF_MENU_BUTTONS}.${button}.priority`, {
|
||||||
|
max: FRIGATE_MENU_PRIORITY_MAX,
|
||||||
|
default: frigateCardConfigDefaults.menu.buttons[button]?.priority,
|
||||||
|
label: localize('config.menu.buttons.priority'),
|
||||||
|
})}
|
||||||
|
${this._renderIconSelector(`${CONF_MENU_BUTTONS}.${button}.icon`, {
|
||||||
|
label: localize('config.menu.buttons.icon'),
|
||||||
|
})}
|
||||||
|
</div>`
|
||||||
|
: ''}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a camera header.
|
* Render a camera header.
|
||||||
* @param cameraIndex The index of the camera to edit/add.
|
* @param cameraIndex The index of the camera to edit/add.
|
||||||
@@ -563,9 +640,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
): TemplateResult {
|
): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="camera-header"
|
class="submenu-header"
|
||||||
@click=${this._toggleCameraHandler}
|
@click=${this._toggleMenu}
|
||||||
.cameraIndex=${cameraIndex}
|
.domain=${MENU_CAMERAS}
|
||||||
|
.key=${cameraIndex}
|
||||||
>
|
>
|
||||||
<ha-icon .icon=${addNewCamera ? 'mdi:video-plus' : 'mdi:video'}></ha-icon>
|
<ha-icon .icon=${addNewCamera ? 'mdi:video-plus' : 'mdi:video'}></ha-icon>
|
||||||
<span>
|
<span>
|
||||||
@@ -629,7 +707,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this._renderCameraHeader(cameraIndex, cameras[cameraIndex], addNewCamera)}
|
${this._renderCameraHeader(cameraIndex, cameras[cameraIndex], addNewCamera)}
|
||||||
${this._expandedCameraIndex === cameraIndex
|
${this._expandedMenus[MENU_CAMERAS] === cameraIndex
|
||||||
? html` <div class="values">
|
? html` <div class="values">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
@@ -644,7 +722,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
modifyConfig((config: RawFrigateCardConfig): boolean => {
|
modifyConfig((config: RawFrigateCardConfig): boolean => {
|
||||||
if (Array.isArray(config.cameras) && cameraIndex > 0) {
|
if (Array.isArray(config.cameras) && cameraIndex > 0) {
|
||||||
arrayMove(config.cameras, cameraIndex, cameraIndex - 1);
|
arrayMove(config.cameras, cameraIndex, cameraIndex - 1);
|
||||||
this._expandedCameraIndex = cameraIndex - 1;
|
this._openMenu(MENU_CAMERAS, cameraIndex - 1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -667,7 +745,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
cameraIndex < config.cameras.length - 1
|
cameraIndex < config.cameras.length - 1
|
||||||
) {
|
) {
|
||||||
arrayMove(config.cameras, cameraIndex, cameraIndex + 1);
|
arrayMove(config.cameras, cameraIndex, cameraIndex + 1);
|
||||||
this._expandedCameraIndex = cameraIndex + 1;
|
this._openMenu(MENU_CAMERAS, cameraIndex + 1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -683,7 +761,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
modifyConfig((config: RawFrigateCardConfig): boolean => {
|
modifyConfig((config: RawFrigateCardConfig): boolean => {
|
||||||
if (Array.isArray(config.cameras)) {
|
if (Array.isArray(config.cameras)) {
|
||||||
config.cameras.splice(cameraIndex, 1);
|
config.cameras.splice(cameraIndex, 1);
|
||||||
this._expandedCameraIndex = null;
|
this._closeMenu(MENU_CAMERAS);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -735,7 +813,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
${this._renderOptionSelector(
|
${this._renderOptionSelector(
|
||||||
getArrayConfigPath(CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS, cameraIndex),
|
getArrayConfigPath(CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS, cameraIndex),
|
||||||
dependentCameras,
|
dependentCameras,
|
||||||
true,
|
{
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
</div>`
|
</div>`
|
||||||
: ``}
|
: ``}
|
||||||
@@ -786,13 +866,15 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
* Render a boolean selector.
|
* Render a boolean selector.
|
||||||
* @param configPath The configuration path to set/read.
|
* @param configPath The configuration path to set/read.
|
||||||
* @param valueDefault The default switch value if unset.
|
* @param valueDefault The default switch value if unset.
|
||||||
* @param label An optional switch label.
|
* @param params Optional parameters to control the selector.
|
||||||
* @returns A rendered template.
|
* @returns A rendered template.
|
||||||
*/
|
*/
|
||||||
protected _renderSwitch(
|
protected _renderSwitch(
|
||||||
configPath: string,
|
configPath: string,
|
||||||
valueDefault: boolean,
|
valueDefault: boolean,
|
||||||
label?: string,
|
params?: {
|
||||||
|
label?: string;
|
||||||
|
},
|
||||||
): TemplateResult | void {
|
): TemplateResult | void {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return;
|
return;
|
||||||
@@ -802,7 +884,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
<ha-selector
|
<ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${{ boolean: {} }}
|
.selector=${{ boolean: {} }}
|
||||||
.label=${label || this._getLabel(configPath)}
|
.label=${params?.label || this._getLabel(configPath)}
|
||||||
.value=${getConfigValue(this._config, configPath, valueDefault)}
|
.value=${getConfigValue(this._config, configPath, valueDefault)}
|
||||||
.required=${false}
|
.required=${false}
|
||||||
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
|
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
|
||||||
@@ -823,9 +905,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
|
|
||||||
const defaults = frigateCardConfigDefaults;
|
const defaults = frigateCardConfigDefaults;
|
||||||
|
|
||||||
const getShowButtonLabel = (configPath: string) =>
|
|
||||||
localize('editor.show_button') + ': ' + localize(`config.${configPath}`);
|
|
||||||
|
|
||||||
const cameras = (getConfigValue(this._config, CONF_CAMERAS) ||
|
const cameras = (getConfigValue(this._config, CONF_CAMERAS) ||
|
||||||
[]) as RawFrigateCardConfigArray;
|
[]) as RawFrigateCardConfigArray;
|
||||||
|
|
||||||
@@ -852,14 +931,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
: html``}
|
: html``}
|
||||||
<div class="card-config">
|
<div class="card-config">
|
||||||
${this._renderOptionSetHeader('cameras')}
|
${this._renderOptionSetHeader('cameras')}
|
||||||
${options.cameras.show
|
${this._expandedMenus[MENU_OPTIONS] === 'cameras'
|
||||||
? html` <div class="cameras">
|
? html` <div class="submenu">
|
||||||
${cameras.map((_, index) => this._renderCamera(cameras, index))}
|
${cameras.map((_, index) => this._renderCamera(cameras, index))}
|
||||||
${this._renderCamera(cameras, cameras.length, true)}
|
${this._renderCamera(cameras, cameras.length, true)}
|
||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('view')}
|
${this._renderOptionSetHeader('view')}
|
||||||
${options.view.show
|
${this._expandedMenus[MENU_OPTIONS] === 'view'
|
||||||
? html`
|
? html`
|
||||||
<div class="values">
|
<div class="values">
|
||||||
${this._renderOptionSelector(CONF_VIEW_DEFAULT, this._viewModes)}
|
${this._renderOptionSelector(CONF_VIEW_DEFAULT, this._viewModes)}
|
||||||
@@ -879,58 +958,32 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('menu')}
|
${this._renderOptionSetHeader('menu')}
|
||||||
${options.menu.show
|
${this._expandedMenus[MENU_OPTIONS] === 'menu'
|
||||||
? html`
|
? html`
|
||||||
<div class="values">
|
<div class="values">
|
||||||
${this._renderOptionSelector(CONF_MENU_STYLE, this._menuStyles)}
|
${this._renderOptionSelector(CONF_MENU_STYLE, this._menuStyles)}
|
||||||
${this._renderOptionSelector(CONF_MENU_POSITION, this._menuPositions)}
|
${this._renderOptionSelector(CONF_MENU_POSITION, this._menuPositions)}
|
||||||
${this._renderOptionSelector(CONF_MENU_ALIGNMENT, this._menuAlignments)}
|
${this._renderOptionSelector(CONF_MENU_ALIGNMENT, this._menuAlignments)}
|
||||||
${this._renderNumberInput(CONF_MENU_BUTTON_SIZE, BUTTON_SIZE_MIN)}
|
${this._renderNumberInput(CONF_MENU_BUTTON_SIZE, {
|
||||||
${this._renderSwitch(
|
min: BUTTON_SIZE_MIN,
|
||||||
CONF_MENU_BUTTONS_FRIGATE,
|
})}
|
||||||
defaults.menu.buttons.frigate,
|
</div>
|
||||||
getShowButtonLabel(CONF_MENU_BUTTONS_FRIGATE),
|
<div class="submenu">
|
||||||
)}
|
${this._renderMenuButton('frigate')}
|
||||||
${this._renderSwitch(
|
${this._renderMenuButton('live')}
|
||||||
CONF_MENU_BUTTONS_LIVE,
|
${this._renderMenuButton('clips')}
|
||||||
defaults.menu.buttons.live,
|
${this._renderMenuButton('snapshots')}
|
||||||
getShowButtonLabel('view.views.live'),
|
${this._renderMenuButton('image')}
|
||||||
)}
|
${this._renderMenuButton('download')}
|
||||||
${this._renderSwitch(
|
${this._renderMenuButton('frigate_ui')}
|
||||||
CONF_MENU_BUTTONS_CLIPS,
|
${this._renderMenuButton('fullscreen')}
|
||||||
defaults.menu.buttons.clips,
|
${this._renderMenuButton('timeline')}
|
||||||
getShowButtonLabel('view.views.clips'),
|
</div>
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
|
||||||
CONF_MENU_BUTTONS_SNAPSHOTS,
|
|
||||||
defaults.menu.buttons.snapshots,
|
|
||||||
getShowButtonLabel('view.views.snapshots'),
|
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
|
||||||
CONF_MENU_BUTTONS_IMAGE,
|
|
||||||
defaults.menu.buttons.image,
|
|
||||||
getShowButtonLabel('view.views.image'),
|
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
|
||||||
CONF_MENU_BUTTONS_FRIGATE_DOWNLOAD,
|
|
||||||
defaults.menu.buttons.download,
|
|
||||||
getShowButtonLabel(CONF_MENU_BUTTONS_FRIGATE_DOWNLOAD),
|
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
|
||||||
CONF_MENU_BUTTONS_FRIGATE_UI,
|
|
||||||
defaults.menu.buttons.frigate_ui,
|
|
||||||
getShowButtonLabel(CONF_MENU_BUTTONS_FRIGATE_UI),
|
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
|
||||||
CONF_MENU_BUTTONS_FRIGATE_FULLSCREEN,
|
|
||||||
defaults.menu.buttons.fullscreen,
|
|
||||||
getShowButtonLabel(CONF_MENU_BUTTONS_FRIGATE_FULLSCREEN),
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('live')}
|
${this._renderOptionSetHeader('live')}
|
||||||
${options.live.show
|
${this._expandedMenus[MENU_OPTIONS] === 'live'
|
||||||
? html`
|
? html`
|
||||||
<div class="values">
|
<div class="values">
|
||||||
${this._renderSwitch(CONF_LIVE_PRELOAD, defaults.live.preload)}
|
${this._renderSwitch(CONF_LIVE_PRELOAD, defaults.live.preload)}
|
||||||
@@ -942,10 +995,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||||
this._liveNextPreviousControlStyles,
|
this._liveNextPreviousControlStyles,
|
||||||
)}
|
)}
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE, {
|
||||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
|
min: BUTTON_SIZE_MIN,
|
||||||
BUTTON_SIZE_MIN,
|
})}
|
||||||
)}
|
|
||||||
${this._renderOptionSelector(
|
${this._renderOptionSelector(
|
||||||
CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
|
CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
|
||||||
this._thumbnailModes,
|
this._thumbnailModes,
|
||||||
@@ -954,11 +1006,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
|
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
|
||||||
this._thumbnailMedias,
|
this._thumbnailMedias,
|
||||||
)}
|
)}
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(CONF_LIVE_CONTROLS_THUMBNAILS_SIZE, {
|
||||||
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
|
min: THUMBNAIL_WIDTH_MIN,
|
||||||
THUMBNAIL_WIDTH_MIN,
|
max: THUMBNAIL_WIDTH_MAX,
|
||||||
THUMBNAIL_WIDTH_MAX,
|
})}
|
||||||
)}
|
|
||||||
${this._renderOptionSelector(
|
${this._renderOptionSelector(
|
||||||
CONF_LIVE_CONTROLS_TITLE_MODE,
|
CONF_LIVE_CONTROLS_TITLE_MODE,
|
||||||
this._titleModes,
|
this._titleModes,
|
||||||
@@ -971,11 +1022,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_CONTROLS,
|
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_CONTROLS,
|
||||||
defaults.live.controls.thumbnails.show_controls,
|
defaults.live.controls.thumbnails.show_controls,
|
||||||
)}
|
)}
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS, {
|
||||||
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
|
min: 0,
|
||||||
0,
|
max: 60,
|
||||||
60,
|
})}
|
||||||
)}
|
|
||||||
${this._renderOptionSelector(
|
${this._renderOptionSelector(
|
||||||
CONF_LIVE_TRANSITION_EFFECT,
|
CONF_LIVE_TRANSITION_EFFECT,
|
||||||
this._transitionEffects,
|
this._transitionEffects,
|
||||||
@@ -984,13 +1034,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('event_gallery')}
|
${this._renderOptionSetHeader('event_gallery')}
|
||||||
${options.event_gallery.show
|
${this._expandedMenus[MENU_OPTIONS] === 'event_gallery'
|
||||||
? html` <div class="values">
|
? html` <div class="values">
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE, {
|
||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE,
|
min: THUMBNAIL_WIDTH_MIN,
|
||||||
THUMBNAIL_WIDTH_MIN,
|
max: THUMBNAIL_WIDTH_MAX,
|
||||||
THUMBNAIL_WIDTH_MAX,
|
})}
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
${this._renderSwitch(
|
||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
||||||
defaults.event_viewer.controls.thumbnails.show_details,
|
defaults.event_viewer.controls.thumbnails.show_details,
|
||||||
@@ -1002,7 +1051,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('event_viewer')}
|
${this._renderOptionSetHeader('event_viewer')}
|
||||||
${options.event_viewer.show
|
${this._expandedMenus[MENU_OPTIONS] === 'event_viewer'
|
||||||
? html` <div class="values">
|
? html` <div class="values">
|
||||||
${this._renderSwitch(
|
${this._renderSwitch(
|
||||||
CONF_EVENT_VIEWER_AUTO_PLAY,
|
CONF_EVENT_VIEWER_AUTO_PLAY,
|
||||||
@@ -1024,19 +1073,17 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||||
this._eventViewerNextPreviousControlStyles,
|
this._eventViewerNextPreviousControlStyles,
|
||||||
)}
|
)}
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE, {
|
||||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
min: BUTTON_SIZE_MIN,
|
||||||
BUTTON_SIZE_MIN,
|
})}
|
||||||
)}
|
|
||||||
${this._renderOptionSelector(
|
${this._renderOptionSelector(
|
||||||
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE,
|
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE,
|
||||||
this._thumbnailModes,
|
this._thumbnailModes,
|
||||||
)}
|
)}
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE, {
|
||||||
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE,
|
min: THUMBNAIL_WIDTH_MIN,
|
||||||
THUMBNAIL_WIDTH_MIN,
|
max: THUMBNAIL_WIDTH_MAX,
|
||||||
THUMBNAIL_WIDTH_MAX,
|
})}
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
${this._renderSwitch(
|
||||||
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
||||||
defaults.event_viewer.controls.thumbnails.show_details,
|
defaults.event_viewer.controls.thumbnails.show_details,
|
||||||
@@ -1051,8 +1098,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
)}
|
)}
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(
|
||||||
CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
|
CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
|
||||||
0,
|
{ min: 0, max: 60 },
|
||||||
60,
|
|
||||||
)}
|
)}
|
||||||
${this._renderOptionSelector(
|
${this._renderOptionSelector(
|
||||||
CONF_EVENT_VIEWER_TRANSITION_EFFECT,
|
CONF_EVENT_VIEWER_TRANSITION_EFFECT,
|
||||||
@@ -1061,7 +1107,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('image')}
|
${this._renderOptionSetHeader('image')}
|
||||||
${options.image.show
|
${this._expandedMenus[MENU_OPTIONS] === 'image'
|
||||||
? html` <div class="values">
|
? html` <div class="values">
|
||||||
${this._renderOptionSelector(CONF_IMAGE_MODE, this._imageModes)}
|
${this._renderOptionSelector(CONF_IMAGE_MODE, this._imageModes)}
|
||||||
${this._renderStringInput(CONF_IMAGE_URL)}
|
${this._renderStringInput(CONF_IMAGE_URL)}
|
||||||
@@ -1069,7 +1115,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('timeline')}
|
${this._renderOptionSetHeader('timeline')}
|
||||||
${options.timeline.show
|
${this._expandedMenus[MENU_OPTIONS] === 'timeline'
|
||||||
? html` <div class="values">
|
? html` <div class="values">
|
||||||
${this._renderNumberInput(CONF_TIMELINE_WINDOW_SECONDS)}
|
${this._renderNumberInput(CONF_TIMELINE_WINDOW_SECONDS)}
|
||||||
${this._renderNumberInput(CONF_TIMELINE_CLUSTERING_THRESHOLD)}
|
${this._renderNumberInput(CONF_TIMELINE_CLUSTERING_THRESHOLD)}
|
||||||
@@ -1081,11 +1127,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE,
|
CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE,
|
||||||
this._thumbnailModes,
|
this._thumbnailModes,
|
||||||
)}
|
)}
|
||||||
${this._renderNumberInput(
|
${this._renderNumberInput(CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE, {
|
||||||
CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE,
|
min: THUMBNAIL_WIDTH_MIN,
|
||||||
THUMBNAIL_WIDTH_MIN,
|
max: THUMBNAIL_WIDTH_MAX,
|
||||||
THUMBNAIL_WIDTH_MAX,
|
})}
|
||||||
)}
|
|
||||||
${this._renderSwitch(
|
${this._renderSwitch(
|
||||||
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
||||||
defaults.timeline.controls.thumbnails.show_details,
|
defaults.timeline.controls.thumbnails.show_details,
|
||||||
@@ -1097,7 +1142,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('dimensions')}
|
${this._renderOptionSetHeader('dimensions')}
|
||||||
${options.dimensions.show
|
${this._expandedMenus[MENU_OPTIONS] === 'dimensions'
|
||||||
? html` <div class="values">
|
? html` <div class="values">
|
||||||
${this._renderOptionSelector(
|
${this._renderOptionSelector(
|
||||||
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
||||||
@@ -1108,7 +1153,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
: ''}
|
: ''}
|
||||||
${this._config['overrides'] !== undefined
|
${this._config['overrides'] !== undefined
|
||||||
? html` ${this._renderOptionSetHeader('overrides')}
|
? html` ${this._renderOptionSetHeader('overrides')}
|
||||||
${options.overrides.show
|
${this._expandedMenus[MENU_OPTIONS] === 'overrides'
|
||||||
? html` <div class="values">
|
? html` <div class="values">
|
||||||
${this._renderInfo(localize('config.overrides.info'))}
|
${this._renderInfo(localize('config.overrides.info'))}
|
||||||
</div>`
|
</div>`
|
||||||
@@ -1149,43 +1194,39 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display/hide a camera section.
|
* Close the editor menu with the given domain.
|
||||||
* @param ev The event triggering the change.
|
* @param targetDomain The menu domain to close.
|
||||||
*/
|
*/
|
||||||
protected _toggleCameraHandler(ev: { target: EditorCameraTarget | null }): void {
|
protected _closeMenu(targetDomain: string) {
|
||||||
if (ev && ev.target) {
|
delete this._expandedMenus[targetDomain];
|
||||||
this._expandedCameraIndex =
|
|
||||||
this._expandedCameraIndex == ev.target.cameraIndex
|
|
||||||
? null
|
|
||||||
: ev.target.cameraIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a toggled set of options.
|
|
||||||
* @param ev The event triggering the change.
|
|
||||||
*/
|
|
||||||
protected _toggleOptionHandler(ev: { target: EditorOptionSetTarget | null }): void {
|
|
||||||
this._toggleOptionSet(ev, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle display of a set of options (e.g. 'Live')
|
|
||||||
* @param ev The event triggering the change.
|
|
||||||
* @param options The EditorOptions object.
|
|
||||||
*/
|
|
||||||
protected _toggleOptionSet(
|
|
||||||
ev: { target: EditorOptionSetTarget | null },
|
|
||||||
options: EditorOptions,
|
|
||||||
): void {
|
|
||||||
if (ev && ev.target) {
|
|
||||||
const show = !options[ev.target.optionSetName].show;
|
|
||||||
for (const [key] of Object.entries(options)) {
|
|
||||||
options[key].show = false;
|
|
||||||
}
|
|
||||||
options[ev.target.optionSetName].show = show;
|
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open an editor menu.
|
||||||
|
* @param targetDomain The menu domain to open.
|
||||||
|
* @param key The menu object key to open.
|
||||||
|
*/
|
||||||
|
protected _openMenu(targetDomain: string, key: number | string) {
|
||||||
|
this._expandedMenus[targetDomain] = key;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle an editor menu.
|
||||||
|
* @param ev An event.
|
||||||
|
*/
|
||||||
|
protected _toggleMenu(ev: { target: EditorMenuTarget | null }): void {
|
||||||
|
if (ev && ev.target) {
|
||||||
|
const domain = ev.target.domain;
|
||||||
|
const key = ev.target.key;
|
||||||
|
|
||||||
|
if (this._expandedMenus[domain] == key) {
|
||||||
|
this._closeMenu(domain);
|
||||||
|
} else {
|
||||||
|
this._openMenu(domain, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -159,10 +159,22 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"frigate": "Frigate menu / Default view",
|
"frigate": "Frigate menu / Default view",
|
||||||
"frigate_ui": "Frigate user Interface",
|
"live": "Live",
|
||||||
|
"clips": "Clips",
|
||||||
|
"snapshots": "Snapshots",
|
||||||
|
"image": "Image",
|
||||||
|
"download": "Download",
|
||||||
|
"frigate_ui": "Frigate user interface",
|
||||||
"fullscreen": "Fullscreen",
|
"fullscreen": "Fullscreen",
|
||||||
"download": "Download event media",
|
"timeline": "Timeline",
|
||||||
"cameras": "Select camera"
|
"icon": "Icon",
|
||||||
|
"priority": "Priority",
|
||||||
|
"alignment": "Button alignment",
|
||||||
|
"alignments": {
|
||||||
|
"matching": "Matching the menu alignment",
|
||||||
|
"opposing": "Opposing the menu alignment"
|
||||||
|
},
|
||||||
|
"enabled": "Button enabled"
|
||||||
},
|
},
|
||||||
"style": "Menu style",
|
"style": "Menu style",
|
||||||
"styles": {
|
"styles": {
|
||||||
@@ -236,7 +248,7 @@
|
|||||||
"image_secondary": "Static image view options",
|
"image_secondary": "Static image view options",
|
||||||
"dimensions": "Dimensions",
|
"dimensions": "Dimensions",
|
||||||
"dimensions_secondary": "Dimensions & shape options",
|
"dimensions_secondary": "Dimensions & shape options",
|
||||||
"show_button": "Show button",
|
"button": "Button",
|
||||||
"upgrade": "Upgrade",
|
"upgrade": "Upgrade",
|
||||||
"upgrade_available": "An automatic card configuration upgrade is available",
|
"upgrade_available": "An automatic card configuration upgrade is available",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
|||||||
@@ -40,35 +40,35 @@ div.upgrade span {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.camera-header {
|
.submenu-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.camera-header * {
|
.submenu-header * {
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
|
|
||||||
// Only allow clicks on the outermost header.
|
// Only allow clicks on the header.
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.camera-header ha-icon {
|
.submenu-header ha-icon {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
.camera-header .new-camera {
|
.submenu-header .new-camera {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: var(--secondary-text-color, 'black');
|
color: var(--secondary-text-color, 'black');
|
||||||
}
|
}
|
||||||
.cameras {
|
.submenu {
|
||||||
margin: 5px 5px 10px 20px;
|
margin: 5px 5px 10px 20px;
|
||||||
}
|
}
|
||||||
.cameras .controls {
|
.submenu .controls {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
.cameras .controls ha-icon-button.button {
|
.submenu .controls ha-icon-button.button {
|
||||||
--mdc-icon-button-size: 32px;
|
--mdc-icon-button-size: 32px;
|
||||||
--mdc-icon-size: calc(var(--mdc-icon-button-size) / 2);
|
--mdc-icon-size: calc(var(--mdc-icon-button-size) / 2);
|
||||||
}
|
}
|
||||||
|
|||||||
+83
-32
@@ -11,44 +11,78 @@
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************
|
||||||
|
* Aligned divs: matching & opposing
|
||||||
|
***********************************/
|
||||||
|
|
||||||
|
div.matching,
|
||||||
|
div.opposing {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
// Allow the divs to be resized.
|
||||||
|
min-width: 0px;
|
||||||
|
min-height: 0px;
|
||||||
|
}
|
||||||
|
div.matching {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
div.opposing {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************
|
||||||
|
* Outside menu style
|
||||||
|
********************/
|
||||||
|
|
||||||
:host([data-style='outside']) {
|
:host([data-style='outside']) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--secondary-background-color);
|
background: var(--secondary-background-color);
|
||||||
}
|
}
|
||||||
:host([data-mode='outside'][data-position='top']) {
|
:host([data-style='outside'][data-position='top']) {
|
||||||
border-top-left-radius: var(--ha-card-border-radius, 4px);
|
border-top-left-radius: var(--ha-card-border-radius, 4px);
|
||||||
border-top-right-radius: var(--ha-card-border-radius, 4px);
|
border-top-right-radius: var(--ha-card-border-radius, 4px);
|
||||||
}
|
}
|
||||||
:host([data-mode='outside'][data-position='below']) {
|
:host([data-style='outside']:not([data-position='top'])) {
|
||||||
border-bottom-left-radius: var(--ha-card-border-radius, 4px);
|
border-bottom-left-radius: var(--ha-card-border-radius, 4px);
|
||||||
border-bottom-right-radius: var(--ha-card-border-radius, 4px);
|
border-bottom-right-radius: var(--ha-card-border-radius, 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([data-position='top']),
|
/**************************************
|
||||||
:host([data-position='left'][data-alignment='top']),
|
* Positioning for absolute menu styles
|
||||||
:host([data-position='right'][data-alignment='top']) {
|
**************************************/
|
||||||
|
|
||||||
|
:host(:not([data-style='outside'])[data-position='top']),
|
||||||
|
:host(:not([data-style='outside'])[data-position='left'][data-alignment='top']),
|
||||||
|
:host(:not([data-style='outside'])[data-position='right'][data-alignment='top']) {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
:host([data-position='bottom']),
|
:host(:not([data-style='outside'])[data-position='bottom']),
|
||||||
:host([data-position='left'][data-alignment='bottom']),
|
:host(:not([data-style='outside'])[data-position='left'][data-alignment='bottom']),
|
||||||
:host([data-position='right'][data-alignment='bottom']) {
|
:host(:not([data-style='outside'])[data-position='right'][data-alignment='bottom']) {
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
:host([data-position='left']),
|
:host(:not([data-style='outside'])[data-position='left']),
|
||||||
:host([data-position='top'][data-alignment='left']),
|
:host(:not([data-style='outside'])[data-position='top'][data-alignment='left']),
|
||||||
:host([data-position='bottom'][data-alignment='left']) {
|
:host(:not([data-style='outside'])[data-position='bottom'][data-alignment='left']) {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
:host([data-position='right']),
|
:host(:not([data-style='outside'])[data-position='right']),
|
||||||
:host([data-position='top'][data-alignment='right']),
|
:host(:not([data-style='outside'])[data-position='top'][data-alignment='right']),
|
||||||
:host([data-position='bottom'][data-alignment='right']) {
|
:host(:not([data-style='outside'])[data-position='bottom'][data-alignment='right']) {
|
||||||
right: 0px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([data-position='left']) {
|
/********************************************************
|
||||||
|
* Hack: Ensure host & div expand for column flex layouts
|
||||||
|
********************************************************/
|
||||||
|
|
||||||
|
:host(:not([data-style='outside'])[data-position='left']) {
|
||||||
// Awful hack: Flexbox column wrapping doesn't work properly in most major
|
// Awful hack: Flexbox column wrapping doesn't work properly in most major
|
||||||
// browsers -- the element boundary does not expand to cover the full wrapped
|
// browsers -- the element boundary does not expand to cover the full wrapped
|
||||||
// content as it should. This results in the wrapped 'content' appearing
|
// content as it should. This results in the wrapped 'content' appearing
|
||||||
@@ -61,24 +95,51 @@
|
|||||||
// - https://bugs.chromium.org/p/chromium/issues/detail?id=507397
|
// - https://bugs.chromium.org/p/chromium/issues/detail?id=507397
|
||||||
writing-mode: vertical-lr;
|
writing-mode: vertical-lr;
|
||||||
}
|
}
|
||||||
:host([data-position='right']) {
|
:host(:not([data-style='outside'])[data-position='right']) {
|
||||||
// See "Awful hack" above.
|
// See "Awful hack" above.
|
||||||
writing-mode: vertical-rl;
|
writing-mode: vertical-rl;
|
||||||
}
|
}
|
||||||
|
:host(:not([data-style='outside'])[data-style='overlay'][data-position='left']) div > *,
|
||||||
|
:host(:not([data-style='outside'])[data-style='overlay'][data-position='right']) div > *,
|
||||||
|
:host(:not([data-style='outside'])[data-style='hover'][data-position='left']) div > *,
|
||||||
|
:host(:not([data-style='outside'])[data-style='hover'][data-position='right']) div > *,
|
||||||
|
:host(:not([data-style='outside'])[data-style='hidden'][data-position='left']) div > *,
|
||||||
|
:host(:not([data-style='outside'])[data-style='hidden'][data-position='right']) div > * {
|
||||||
|
// See "Awful hack" above. Note that this "cancelation" of writing mode only
|
||||||
|
// affects from beyond the divs, i.e. the writing mode hack applies to host
|
||||||
|
// and divs both.
|
||||||
|
writing-mode: horizontal-tb;
|
||||||
|
}
|
||||||
|
|
||||||
:host([data-position='left'][data-alignment='bottom']),
|
/**********************
|
||||||
:host([data-position='right'][data-alignment='bottom']),
|
* "Reverse" alignments
|
||||||
:host([data-position='top'][data-alignment='right']),
|
**********************/
|
||||||
:host([data-position='bottom'][data-alignment='right']) {
|
|
||||||
|
:host(:not([data-style='outside'])[data-position='left'][data-alignment='bottom']),
|
||||||
|
:host(:not([data-style='outside'])[data-position='right'][data-alignment='bottom']),
|
||||||
|
:host(:not([data-style='outside'])[data-position='top'][data-alignment='right']),
|
||||||
|
:host(:not([data-style='outside'])[data-position='bottom'][data-alignment='right']),
|
||||||
|
:host(:not([data-style='outside'])[data-position='left'][data-alignment='bottom']) div,
|
||||||
|
:host(:not([data-style='outside'])[data-position='right'][data-alignment='bottom']) div,
|
||||||
|
:host(:not([data-style='outside'])[data-position='top'][data-alignment='right']) div,
|
||||||
|
:host(:not([data-style='outside'])[data-position='bottom'][data-alignment='right']) div {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([data-position='bottom']) {
|
/****************************
|
||||||
|
* Wrap upwards on the bottom
|
||||||
|
****************************/
|
||||||
|
|
||||||
|
:host(:not([data-style='outside'])[data-position='bottom']) div {
|
||||||
// If the menu has more content that allows, "wrap upwards" to keep the
|
// If the menu has more content that allows, "wrap upwards" to keep the
|
||||||
// Frigate button in the same place.
|
// Frigate button in the same place.
|
||||||
flex-wrap: wrap-reverse;
|
flex-wrap: wrap-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/********************************************
|
||||||
|
* Positioning for absolute based menu styles
|
||||||
|
********************************************/
|
||||||
|
|
||||||
:host([data-style='overlay']),
|
:host([data-style='overlay']),
|
||||||
:host([data-style='hover']),
|
:host([data-style='hover']),
|
||||||
:host([data-style='hidden']) {
|
:host([data-style='hidden']) {
|
||||||
@@ -111,13 +172,3 @@
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0));
|
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([data-style='overlay'][data-position='left']) > *,
|
|
||||||
:host([data-style='overlay'][data-position='right']) > *,
|
|
||||||
:host([data-style='hover'][data-position='left']) > *,
|
|
||||||
:host([data-style='hover'][data-position='right']) > *,
|
|
||||||
:host([data-style='hidden'][data-position='left']) > *,
|
|
||||||
:host([data-style='hidden'][data-position='right']) > * {
|
|
||||||
// See "Awful hack" above.
|
|
||||||
writing-mode: horizontal-tb;
|
|
||||||
}
|
|
||||||
|
|||||||
+63
-37
@@ -53,21 +53,13 @@ const FRIGATE_CARD_VIEWS = [
|
|||||||
|
|
||||||
export type FrigateCardView = typeof FRIGATE_CARD_VIEWS[number];
|
export type FrigateCardView = typeof FRIGATE_CARD_VIEWS[number];
|
||||||
|
|
||||||
const FRIGATE_MENU_STYLES = [
|
const FRIGATE_MENU_STYLES = ['none', 'hidden', 'overlay', 'hover', 'outside'] as const;
|
||||||
'none',
|
const FRIGATE_MENU_POSITIONS = ['left', 'right', 'top', 'bottom'] as const;
|
||||||
'hidden',
|
|
||||||
'overlay',
|
|
||||||
'hover',
|
|
||||||
'outside',
|
|
||||||
] as const;
|
|
||||||
const FRIGATE_MENU_POSITIONS = [
|
|
||||||
'left',
|
|
||||||
'right',
|
|
||||||
'top',
|
|
||||||
'bottom',
|
|
||||||
] as const;
|
|
||||||
const FRIGATE_MENU_ALIGNMENTS = FRIGATE_MENU_POSITIONS;
|
const FRIGATE_MENU_ALIGNMENTS = FRIGATE_MENU_POSITIONS;
|
||||||
|
|
||||||
|
export const FRIGATE_MENU_PRIORITY_DEFAULT = 50;
|
||||||
|
export const FRIGATE_MENU_PRIORITY_MAX = 100;
|
||||||
|
|
||||||
const LIVE_PROVIDERS = ['auto', 'ha', 'frigate-jsmpeg', 'webrtc-card'] as const;
|
const LIVE_PROVIDERS = ['auto', 'ha', 'frigate-jsmpeg', 'webrtc-card'] as const;
|
||||||
export type LiveProvider = typeof LIVE_PROVIDERS[number];
|
export type LiveProvider = typeof LIVE_PROVIDERS[number];
|
||||||
|
|
||||||
@@ -335,15 +327,29 @@ export type CameraConfig = z.infer<typeof cameraConfigSchema>;
|
|||||||
/**
|
/**
|
||||||
* Custom Element Types.
|
* Custom Element Types.
|
||||||
*/
|
*/
|
||||||
|
const menuBaseSchema = z.object({
|
||||||
|
enabled: z.boolean().default(true).optional(),
|
||||||
|
priority: z
|
||||||
|
.number()
|
||||||
|
.min(0)
|
||||||
|
.max(FRIGATE_MENU_PRIORITY_MAX)
|
||||||
|
.default(FRIGATE_MENU_PRIORITY_DEFAULT)
|
||||||
|
.optional(),
|
||||||
|
alignment: z.enum(['matching', 'opposing']).default('matching').optional(),
|
||||||
|
icon: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const menuIconSchema = iconSchema.extend({
|
export const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({
|
||||||
type: z.literal('custom:frigate-card-menu-icon'),
|
type: z.literal('custom:frigate-card-menu-icon'),
|
||||||
});
|
});
|
||||||
export type MenuIcon = z.infer<typeof menuIconSchema>;
|
export type MenuIcon = z.infer<typeof menuIconSchema>;
|
||||||
|
|
||||||
export const menuStateIconSchema = stateIconSchema.extend({
|
export const menuStateIconSchema = menuBaseSchema
|
||||||
|
.merge(stateIconSchema)
|
||||||
|
.extend({
|
||||||
type: z.literal('custom:frigate-card-menu-state-icon'),
|
type: z.literal('custom:frigate-card-menu-state-icon'),
|
||||||
});
|
})
|
||||||
|
.merge(menuBaseSchema);
|
||||||
export type MenuStateIcon = z.infer<typeof menuStateIconSchema>;
|
export type MenuStateIcon = z.infer<typeof menuStateIconSchema>;
|
||||||
|
|
||||||
const menuSubmenuItemSchema = elementsBaseSchema.extend({
|
const menuSubmenuItemSchema = elementsBaseSchema.extend({
|
||||||
@@ -354,11 +360,12 @@ const menuSubmenuItemSchema = elementsBaseSchema.extend({
|
|||||||
});
|
});
|
||||||
export type MenuSubmenuItem = z.infer<typeof menuSubmenuItemSchema>;
|
export type MenuSubmenuItem = z.infer<typeof menuSubmenuItemSchema>;
|
||||||
|
|
||||||
export const menuSubmenuSchema = iconSchema.extend({
|
export const menuSubmenuSchema = menuBaseSchema.merge(iconSchema).extend({
|
||||||
type: z.literal('custom:frigate-card-menu-submenu'),
|
type: z.literal('custom:frigate-card-menu-submenu'),
|
||||||
items: menuSubmenuItemSchema.array(),
|
items: menuSubmenuItemSchema.array(),
|
||||||
});
|
});
|
||||||
export type MenuSubmenu = z.infer<typeof menuSubmenuSchema>;
|
export type MenuSubmenu = z.infer<typeof menuSubmenuSchema>;
|
||||||
|
export type MenuItem = MenuIcon | MenuStateIcon | MenuSubmenu;
|
||||||
|
|
||||||
const frigateCardConditionSchema = z.object({
|
const frigateCardConditionSchema = z.object({
|
||||||
view: z.string().array().optional(),
|
view: z.string().array().optional(),
|
||||||
@@ -612,25 +619,44 @@ export type LiveConfig = z.infer<typeof liveConfigSchema>;
|
|||||||
/**
|
/**
|
||||||
* Menu configuration section.
|
* Menu configuration section.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const visibleButtonDefault = {
|
||||||
|
priority: FRIGATE_MENU_PRIORITY_DEFAULT,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
const hiddenButtonDefault = {
|
||||||
|
priority: FRIGATE_MENU_PRIORITY_DEFAULT,
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
const menuConfigDefault = {
|
const menuConfigDefault = {
|
||||||
style: 'hidden' as const,
|
style: 'hidden' as const,
|
||||||
position: 'top' as const,
|
position: 'top' as const,
|
||||||
alignment: 'left' as const,
|
alignment: 'left' as const,
|
||||||
buttons: {
|
buttons: {
|
||||||
frigate: true,
|
frigate: visibleButtonDefault,
|
||||||
cameras: true,
|
cameras: visibleButtonDefault,
|
||||||
live: true,
|
live: visibleButtonDefault,
|
||||||
clips: true,
|
clips: visibleButtonDefault,
|
||||||
snapshots: true,
|
snapshots: visibleButtonDefault,
|
||||||
image: false,
|
image: hiddenButtonDefault,
|
||||||
timeline: true,
|
timeline: visibleButtonDefault,
|
||||||
download: true,
|
download: visibleButtonDefault,
|
||||||
frigate_ui: true,
|
frigate_ui: visibleButtonDefault,
|
||||||
fullscreen: true,
|
fullscreen: visibleButtonDefault,
|
||||||
},
|
},
|
||||||
button_size: 40,
|
button_size: 40,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const visibleButtonSchema = menuBaseSchema.extend({
|
||||||
|
enabled: menuBaseSchema.shape.enabled.default(visibleButtonDefault.enabled),
|
||||||
|
priority: menuBaseSchema.shape.priority.default(visibleButtonDefault.priority),
|
||||||
|
});
|
||||||
|
const hiddenButtonSchema = menuBaseSchema.extend({
|
||||||
|
enabled: menuBaseSchema.shape.enabled.default(hiddenButtonDefault.enabled),
|
||||||
|
priority: menuBaseSchema.shape.priority.default(hiddenButtonDefault.priority),
|
||||||
|
});
|
||||||
|
|
||||||
const menuConfigSchema = z
|
const menuConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
style: z.enum(FRIGATE_MENU_STYLES).default(menuConfigDefault.style),
|
style: z.enum(FRIGATE_MENU_STYLES).default(menuConfigDefault.style),
|
||||||
@@ -638,16 +664,16 @@ const menuConfigSchema = z
|
|||||||
alignment: z.enum(FRIGATE_MENU_ALIGNMENTS).default(menuConfigDefault.alignment),
|
alignment: z.enum(FRIGATE_MENU_ALIGNMENTS).default(menuConfigDefault.alignment),
|
||||||
buttons: z
|
buttons: z
|
||||||
.object({
|
.object({
|
||||||
frigate: z.boolean().default(menuConfigDefault.buttons.frigate),
|
frigate: visibleButtonSchema.default(menuConfigDefault.buttons.frigate),
|
||||||
cameras: z.boolean().default(menuConfigDefault.buttons.cameras),
|
cameras: visibleButtonSchema.default(menuConfigDefault.buttons.cameras),
|
||||||
live: z.boolean().default(menuConfigDefault.buttons.live),
|
live: visibleButtonSchema.default(menuConfigDefault.buttons.live),
|
||||||
clips: z.boolean().default(menuConfigDefault.buttons.clips),
|
clips: visibleButtonSchema.default(menuConfigDefault.buttons.clips),
|
||||||
snapshots: z.boolean().default(menuConfigDefault.buttons.snapshots),
|
snapshots: visibleButtonSchema.default(menuConfigDefault.buttons.snapshots),
|
||||||
image: z.boolean().default(menuConfigDefault.buttons.image),
|
image: hiddenButtonSchema.default(menuConfigDefault.buttons.image),
|
||||||
timeline: z.boolean().default(menuConfigDefault.buttons.timeline),
|
timeline: visibleButtonSchema.default(menuConfigDefault.buttons.timeline),
|
||||||
download: z.boolean().default(menuConfigDefault.buttons.download),
|
download: visibleButtonSchema.default(menuConfigDefault.buttons.download),
|
||||||
frigate_ui: z.boolean().default(menuConfigDefault.buttons.frigate_ui),
|
frigate_ui: visibleButtonSchema.default(menuConfigDefault.buttons.frigate_ui),
|
||||||
fullscreen: z.boolean().default(menuConfigDefault.buttons.fullscreen),
|
fullscreen: visibleButtonSchema.default(menuConfigDefault.buttons.fullscreen),
|
||||||
})
|
})
|
||||||
.default(menuConfigDefault.buttons),
|
.default(menuConfigDefault.buttons),
|
||||||
button_size: z.number().min(BUTTON_SIZE_MIN).default(menuConfigDefault.button_size),
|
button_size: z.number().min(BUTTON_SIZE_MIN).default(menuConfigDefault.button_size),
|
||||||
|
|||||||
Reference in New Issue
Block a user