feat: Add permanent menu items for hidden menus (#1636)
This commit is contained in:
@@ -22,7 +22,7 @@ See the [conditional elements example](../../../examples.md?id=conditional-eleme
|
|||||||
|
|
||||||
## `menu-icon`
|
## `menu-icon`
|
||||||
|
|
||||||
Add an arbitrary icon to the Frigate Card menu. Configuration is ~identical to that of the [Picture Elements Icon](https://www.home-assistant.io/lovelace/picture-elements/#icon-element) except with a type name of `custom:frigate-card-menu-icon`.
|
Add an arbitrary icon to the Frigate Card menu.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
elements:
|
elements:
|
||||||
@@ -30,6 +30,20 @@ elements:
|
|||||||
# [...]
|
# [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Configuration is similar to a stock [Picture Elements Icon](https://www.home-assistant.io/lovelace/picture-elements/#icon-element).
|
||||||
|
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
| ------------------------------------------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `type` | | Must be `custom:frigate-card-menu-icon`. |
|
||||||
|
| `enabled` | `true` | Whether or not to show this menu item. |
|
||||||
|
| `entity` | | An optional Home Assistant entity for use with actions. |
|
||||||
|
| `icon` | | An required icon to display, e.g. `mdi:car` |
|
||||||
|
| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). |
|
||||||
|
| `priority` | `50` | The menu item priority. Higher priority item 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`. |
|
||||||
|
| `style` | | Position and style the element using CSS. |
|
||||||
|
| `tap_action`, `double_tap_action`, `hold_action`, `start_tap`, `end_tap` | | The [actions](../../actions/README.md) to take when this item is interacted with. |
|
||||||
|
| `title` | | An optional title to display. |
|
||||||
|
|
||||||
## `menu-submenu`
|
## `menu-submenu`
|
||||||
|
|
||||||
Add a configurable submenu dropdown.
|
Add a configurable submenu dropdown.
|
||||||
@@ -40,12 +54,18 @@ elements:
|
|||||||
# [...]
|
# [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
Parameters for this element are identical to the parameters of the [stock Home Assistant Icon Element](https://www.home-assistant.io/lovelace/picture-elements/#icon-element) with the exception of these parameters which differ:
|
| Parameter | Default | Description |
|
||||||
|
| ------------------------------------------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Parameter | Description |
|
| `type` | | Must be `custom:frigate-card-menu-submenu`. |
|
||||||
| --------- | ------------------------------------------- |
|
| `enabled` | `true` | Whether or not to show this menu item. |
|
||||||
| `type` | Must be `custom:frigate-card-menu-submenu`. |
|
| `entity` | | An optional Home Assistant entity for use with actions. |
|
||||||
| `items` | A list of menu items, as described below. |
|
| `icon` | | An required icon to display, e.g. `mdi:car` |
|
||||||
|
| `items` | | A list of menu items, as described below. |
|
||||||
|
| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). |
|
||||||
|
| `priority` | `50` | The menu item priority. Higher priority item 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`. |
|
||||||
|
| `style` | | Position and style the element using CSS. |
|
||||||
|
| `tap_action`, `double_tap_action`, `hold_action`, `start_tap`, `end_tap` | | The [actions](../../actions/README.md) to take when this item is interacted with. |
|
||||||
|
| `title` | | An optional title to display. |
|
||||||
|
|
||||||
### Submenu items
|
### Submenu items
|
||||||
|
|
||||||
@@ -70,25 +90,46 @@ elements:
|
|||||||
# [...]
|
# [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
Parameters for the `custom:frigate-card-menu-submenu-select` element are identical to the parameters of the [stock Home Assistant State Icon Element](https://www.home-assistant.io/dashboards/picture-elements/#state-icon) with the exception of these parameters which differ:
|
| Parameter | Default | Description |
|
||||||
|
| ------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Parameter | Description |
|
| `type` | | Must be `custom:frigate-card-menu-submenu-select`. |
|
||||||
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| `enabled` | `true` | Whether or not to show this menu item. |
|
||||||
| `type` | Must be `custom:frigate-card-menu-submenu-select`. |
|
| `entity` | | An optional Home Assistant entity for use with actions. |
|
||||||
| `options` | An optional dictionary of overrides keyed by the option name that the given select entity supports. These options can be used to set or override submenu item parameters on a per-option basis. The format is as described in [Submenu Items](./README.md?id=submenu-items) above. |
|
| `icon` | | An required icon to display, e.g. `mdi:car` |
|
||||||
|
| `options` | | An optional dictionary of overrides keyed by the option name that the given select entity supports. These options can be used to set or override submenu item parameters on a per-option basis. The format is as described in [Submenu Items](./README.md?id=submenu-items) above. |
|
||||||
|
| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). |
|
||||||
|
| `priority` | `50` | The menu item priority. Higher priority item 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`. |
|
||||||
|
| `style` | | Position and style the element using CSS. |
|
||||||
|
| `tap_action`, `double_tap_action`, `hold_action`, `start_tap`, `end_tap` | | The [actions](../../actions/README.md) to take when this item is interacted with. |
|
||||||
|
| `title` | | An optional title to display. |
|
||||||
|
|
||||||
See the `select` [submenu example](../../../examples.md?id=select-submenu).
|
See the `select` [submenu example](../../../examples.md?id=select-submenu).
|
||||||
|
|
||||||
## `state-icon`
|
## `state-icon`
|
||||||
|
|
||||||
Add a state icon to the Frigate Card menu that represents the state of a Home Assistant entity. Configuration is ~identical to that of the [Picture Elements State Icon](https://www.home-assistant.io/lovelace/picture-elements/#state-icon) except with a type name of `custom:frigate-card-menu-state-icon`.
|
Add a state icon to the Frigate Card menu that represents the state of a Home Assistant entity.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
elements:
|
elements:
|
||||||
- type: custom:frigate-card-menu-icon
|
- type: custom:frigate-card-menu-state-icon
|
||||||
# [...]
|
# [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Configuration is similar to a stock [Picture Elements State Icon](https://www.home-assistant.io/lovelace/picture-elements/#state-icon).
|
||||||
|
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
| ------------------------------------------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `type` | | Must be `custom:frigate-card-menu-state-icon`. |
|
||||||
|
| `enabled` | `true` | Whether or not to show this menu item. |
|
||||||
|
| `entity` | | An optional Home Assistant entity for use with actions. |
|
||||||
|
| `icon` | | An required icon to display, e.g. `mdi:car` |
|
||||||
|
| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). |
|
||||||
|
| `priority` | `50` | The menu item priority. Higher priority item 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`. |
|
||||||
|
| `tap_action`, `double_tap_action`, `hold_action`, `start_tap`, `end_tap` | | The [actions](../../actions/README.md) to take when this item is interacted with. |
|
||||||
|
| `state_color` | `true` | Set to true to have icons colored when entity is active. |
|
||||||
|
| `style` | | Position and style the element using CSS. |
|
||||||
|
| `title` | | An optional title to display. |
|
||||||
|
|
||||||
## `status-bar-icon`
|
## `status-bar-icon`
|
||||||
|
|
||||||
Add an arbitrary icon to the status bar.
|
Add an arbitrary icon to the status bar.
|
||||||
@@ -154,44 +195,82 @@ elements:
|
|||||||
|
|
||||||
## Fully expanded reference
|
## Fully expanded reference
|
||||||
|
|
||||||
> [Actions](../../actions/README.md) are omitted for simplicity.
|
?> [Actions](../../actions/README.md) are omitted for simplicity.
|
||||||
|
|
||||||
[](../../common/expanded-warning.md ':include')
|
[](../../common/expanded-warning.md ':include')
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
elements:
|
elements:
|
||||||
- type: custom:frigate-card-menu-icon
|
- type: custom:frigate-card-menu-icon
|
||||||
|
enabled: true
|
||||||
|
entity: light.office_main_lights
|
||||||
icon: mdi:car
|
icon: mdi:car
|
||||||
|
permanent: false
|
||||||
|
priority: 50
|
||||||
|
style:
|
||||||
|
color: white
|
||||||
title: Vroom
|
title: Vroom
|
||||||
- type: custom:frigate-card-menu-state-icon
|
- type: custom:frigate-card-menu-state-icon
|
||||||
|
enabled: true
|
||||||
entity: light.office_main_lights
|
entity: light.office_main_lights
|
||||||
title: Office lights
|
|
||||||
icon: mdi:chair-rolling
|
icon: mdi:chair-rolling
|
||||||
|
permanent: false
|
||||||
|
priority: 50
|
||||||
state_color: true
|
state_color: true
|
||||||
|
style:
|
||||||
|
color: white
|
||||||
|
title: Office lights
|
||||||
- type: custom:frigate-card-menu-submenu
|
- type: custom:frigate-card-menu-submenu
|
||||||
|
enabled: true
|
||||||
|
entity: light.office_main_lights
|
||||||
icon: mdi:menu
|
icon: mdi:menu
|
||||||
items:
|
items:
|
||||||
- title: Lights
|
- enabled: true
|
||||||
icon: mdi:lightbulb
|
|
||||||
entity: light.office_main_lights
|
entity: light.office_main_lights
|
||||||
|
icon: mdi:lightbulb
|
||||||
|
selected: false
|
||||||
|
state_color: true
|
||||||
|
style:
|
||||||
|
color: white
|
||||||
tap_action:
|
tap_action:
|
||||||
action: toggle
|
action: toggle
|
||||||
- title: Google
|
title: Lights
|
||||||
|
- enabled: true
|
||||||
icon: mdi:google
|
icon: mdi:google
|
||||||
enabled: false
|
selected: false
|
||||||
|
style:
|
||||||
|
color: white
|
||||||
tap_action:
|
tap_action:
|
||||||
action: url
|
action: url
|
||||||
url_path: https://www.google.com
|
url_path: https://www.google.com
|
||||||
|
title: Google
|
||||||
|
permanent: false
|
||||||
|
priority: 50
|
||||||
|
style:
|
||||||
|
color: white
|
||||||
|
title: Office lights
|
||||||
- type: custom:frigate-card-menu-submenu-select
|
- type: custom:frigate-card-menu-submenu-select
|
||||||
icon: mdi:lamps
|
enabled: true
|
||||||
entity: input_select.kitchen_scene
|
entity: input_select.kitchen_scene
|
||||||
|
icon: mdi:lamps
|
||||||
options:
|
options:
|
||||||
|
items:
|
||||||
scene.kitchen_cooking_scene:
|
scene.kitchen_cooking_scene:
|
||||||
|
enabled: true
|
||||||
icon: mdi:chef-hat
|
icon: mdi:chef-hat
|
||||||
|
selected: false
|
||||||
|
state_color: true
|
||||||
|
style:
|
||||||
|
color: white
|
||||||
title: Cooking time!
|
title: Cooking time!
|
||||||
scene.kitchen_tv_scene:
|
scene.kitchen_tv_scene:
|
||||||
icon: mdi:television
|
icon: mdi:television
|
||||||
title: TV!
|
title: TV!
|
||||||
|
permanent: false
|
||||||
|
priority: 50
|
||||||
|
style:
|
||||||
|
color: white
|
||||||
|
title: 'Kitchen Scene'
|
||||||
# Show a pig icon if a variety of conditions are met.
|
# Show a pig icon if a variety of conditions are met.
|
||||||
- type: custom:frigate-card-conditional
|
- type: custom:frigate-card-conditional
|
||||||
elements:
|
elements:
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ menu:
|
|||||||
### Options for each button
|
### Options for each button
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `alignment` | `matching` | 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`. |
|
| `alignment` | `matching` | 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`. |
|
||||||
| `enabled` | `true` for `frigate`, `cameras`, `substreams`, `live`, `clips`, `snapshots`, `timeline`, `download`, `camera_ui`, `fullscreen`, `media_player`, `display_mode` and `ptz_home`. `false` for `image`, `expand`, `microphone`, `mute`, `play`, `recordings`, `screenshot`, `ptz_controls` | Whether or not to show the button. |
|
| `enabled` | `true` for `frigate`, `cameras`, `substreams`, `live`, `clips`, `snapshots`, `timeline`, `download`, `camera_ui`, `fullscreen`, `media_player`, `display_mode` and `ptz_home`. `false` for `image`, `expand`, `microphone`, `mute`, `play`, `recordings`, `screenshot`, `ptz_controls` | Whether or not to show the button. |
|
||||||
| `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. |
|
| `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. |
|
||||||
| `priority` | `50` | 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`. |
|
| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). |
|
||||||
|
|
||||||
## `style`
|
## `style`
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ This card supports several menu styles.
|
|||||||
|
|
||||||
| Key | Description | Screenshot |
|
| Key | Description | Screenshot |
|
||||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||||
| `hidden` | Hide the menu by default, expandable upon clicking the Frigate button. |  |
|
| `hidden` | Hide the menu by default. It may be toggled open as needed. |  |
|
||||||
| `hover-card` | Overlay the menu over the card contents when the mouse is over the **card**, otherwise it is not shown. The Frigate button shows the default view. |  |
|
| `hover-card` | Overlay the menu over the card contents when the mouse is over the **card**, otherwise it is not shown. The Frigate button shows the default view. |  |
|
||||||
| `hover` | Overlay the menu over the card contents when the mouse is over the **menu**, otherwise it is not shown. The Frigate button shows the default view. |  |
|
| `hover` | Overlay the menu over the card contents when the mouse is over the **menu**, otherwise it is not shown. The Frigate button shows the default view. |  |
|
||||||
| `none` | No menu is shown. |  |
|
| `none` | No menu is shown. |  |
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ export class ActionFactory {
|
|||||||
case 'fullscreen':
|
case 'fullscreen':
|
||||||
return new FullscreenAction(context, frigateCardAction, options?.config);
|
return new FullscreenAction(context, frigateCardAction, options?.config);
|
||||||
case 'menu_toggle':
|
case 'menu_toggle':
|
||||||
// This is a rare code path: this would only be used if someone has a
|
|
||||||
// menu toggle action configured outside of the menu itself.
|
|
||||||
return new MenuToggleAction(context, frigateCardAction, options?.config);
|
return new MenuToggleAction(context, frigateCardAction, options?.config);
|
||||||
case 'camera_select':
|
case 'camera_select':
|
||||||
return new CameraSelectAction(context, frigateCardAction, options?.config);
|
return new CameraSelectAction(context, frigateCardAction, options?.config);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export class MenuButtonController {
|
|||||||
options?: MenuButtonControllerOptions,
|
options?: MenuButtonControllerOptions,
|
||||||
): MenuItem[] {
|
): MenuItem[] {
|
||||||
return [
|
return [
|
||||||
this._getFrigateButton(config),
|
this._getDefaultButton(config),
|
||||||
this._getCamerasButton(config, cameraManager, options?.view),
|
this._getCamerasButton(config, cameraManager, options?.view),
|
||||||
this._getSubstreamsButton(config, cameraManager, options?.view),
|
this._getSubstreamsButton(config, cameraManager, options?.view),
|
||||||
this._getLiveButton(config, options?.view, options?.viewManager),
|
this._getLiveButton(config, options?.view, options?.viewManager),
|
||||||
@@ -105,7 +105,7 @@ export class MenuButtonController {
|
|||||||
].filter(isTruthy);
|
].filter(isTruthy);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _getFrigateButton(config: FrigateCardConfig): MenuItem {
|
protected _getDefaultButton(config: FrigateCardConfig): MenuItem {
|
||||||
return {
|
return {
|
||||||
// Use a magic icon value that the menu will use to render the custom
|
// Use a magic icon value that the menu will use to render the custom
|
||||||
// Frigate icon.
|
// Frigate icon.
|
||||||
@@ -113,6 +113,9 @@ export class MenuButtonController {
|
|||||||
...config.menu.buttons.frigate,
|
...config.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'),
|
||||||
|
// The default button always shows regardless of whether the menu is
|
||||||
|
// hidden or not.
|
||||||
|
permanent: true,
|
||||||
tap_action:
|
tap_action:
|
||||||
config.menu?.style === 'hidden'
|
config.menu?.style === 'hidden'
|
||||||
? (createGeneralAction('menu_toggle') as FrigateCardCustomAction)
|
? (createGeneralAction('menu_toggle') as FrigateCardCustomAction)
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { HASSDomEvent, HomeAssistant } from '@dermotduffy/custom-card-helpers';
|
import { HASSDomEvent, HomeAssistant } from '@dermotduffy/custom-card-helpers';
|
||||||
import { LitElement } from 'lit';
|
import { LitElement } from 'lit';
|
||||||
|
import { orderBy } from 'lodash-es';
|
||||||
import { FRIGATE_ICON_SVG_PATH } from '../camera-manager/frigate/icon.js';
|
import { FRIGATE_ICON_SVG_PATH } from '../camera-manager/frigate/icon.js';
|
||||||
import type {
|
import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js';
|
||||||
ActionType,
|
import {
|
||||||
ActionsConfig,
|
FRIGATE_MENU_PRIORITY_MAX,
|
||||||
MenuConfig,
|
type ActionType,
|
||||||
MenuItem,
|
type ActionsConfig,
|
||||||
|
type MenuConfig,
|
||||||
|
type MenuItem,
|
||||||
} from '../config/types.js';
|
} from '../config/types.js';
|
||||||
import { FRIGATE_BUTTON_MENU_ICON } from '../const.js';
|
import { FRIGATE_BUTTON_MENU_ICON } from '../const.js';
|
||||||
import { StateParameters } from '../types.js';
|
import { StateParameters } from '../types.js';
|
||||||
@@ -15,7 +18,6 @@ import {
|
|||||||
} from '../utils/action';
|
} from '../utils/action';
|
||||||
import { arrayify, isTruthy, setOrRemoveAttribute } from '../utils/basic.js';
|
import { arrayify, isTruthy, setOrRemoveAttribute } from '../utils/basic.js';
|
||||||
import { refreshDynamicStateParameters } from '../utils/ha/index.js';
|
import { refreshDynamicStateParameters } from '../utils/ha/index.js';
|
||||||
import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js';
|
|
||||||
|
|
||||||
export class MenuController {
|
export class MenuController {
|
||||||
protected _host: LitElement;
|
protected _host: LitElement;
|
||||||
@@ -59,8 +61,6 @@ export class MenuController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getButtons(alignment: 'matching' | 'opposing'): MenuItem[] {
|
public getButtons(alignment: 'matching' | 'opposing'): MenuItem[] {
|
||||||
const style = this._config?.style;
|
|
||||||
|
|
||||||
const aligned = (button: MenuItem): boolean => {
|
const aligned = (button: MenuItem): boolean => {
|
||||||
return (
|
return (
|
||||||
button.alignment === alignment || (alignment === 'matching' && !button.alignment)
|
button.alignment === alignment || (alignment === 'matching' && !button.alignment)
|
||||||
@@ -71,16 +71,12 @@ export class MenuController {
|
|||||||
return button.enabled !== false;
|
return button.enabled !== false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const suitableToShowIfHiddenMenu = (button: MenuItem): boolean => {
|
const show = (button: MenuItem): boolean => {
|
||||||
// If the hidden menu isn't expanded, only show the Frigate button.
|
return !this._isHidingMenu() || this._expanded || !!button.permanent;
|
||||||
return (
|
|
||||||
style !== 'hidden' || this._expanded || button.icon === FRIGATE_BUTTON_MENU_ICON
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return this._buttons.filter(
|
return this._buttons.filter(
|
||||||
(button) =>
|
(button) => enabled(button) && aligned(button) && show(button),
|
||||||
enabled(button) && aligned(button) && suitableToShowIfHiddenMenu(button),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +122,7 @@ export class MenuController {
|
|||||||
let menuToggle = false;
|
let menuToggle = false;
|
||||||
|
|
||||||
const toggleLessActions = actions.filter(
|
const toggleLessActions = actions.filter(
|
||||||
(item) => isTruthy(item) && !this._isMenuToggleAction(item),
|
(item) => isTruthy(item) && !this._isUnknownActionMenuToggleAction(item),
|
||||||
);
|
);
|
||||||
if (toggleLessActions.length != actions.length) {
|
if (toggleLessActions.length != actions.length) {
|
||||||
menuToggle = true;
|
menuToggle = true;
|
||||||
@@ -176,42 +172,27 @@ export class MenuController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected _sortButtons(): void {
|
protected _sortButtons(): void {
|
||||||
const style = this._config?.style;
|
this._buttons = orderBy(
|
||||||
const sortButtons = (a: MenuItem, b: MenuItem): number => {
|
this._buttons,
|
||||||
// If the menu is hidden, the Frigate button must come first.
|
(button) => {
|
||||||
if (style === 'hidden') {
|
const priority = button.priority ?? 0;
|
||||||
if (a.icon === FRIGATE_BUTTON_MENU_ICON) {
|
// If the menu is hidden, the buttons that toggle the menu must come
|
||||||
return -1;
|
// first.
|
||||||
} else if (b.icon === FRIGATE_BUTTON_MENU_ICON) {
|
return (
|
||||||
return 1;
|
priority +
|
||||||
}
|
(this._isHidingMenu() && button.permanent ? FRIGATE_MENU_PRIORITY_MAX : 0)
|
||||||
}
|
);
|
||||||
|
},
|
||||||
// Otherwise sort by priority.
|
['desc'],
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
this._buttons.sort(sortButtons);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _isHidingMenu(): boolean {
|
protected _isHidingMenu(): boolean {
|
||||||
return this._config?.style === 'hidden' ?? false;
|
return this._config?.style === 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _isMenuToggleAction(action: ActionType): boolean {
|
protected _isUnknownActionMenuToggleAction(action: ActionType): boolean {
|
||||||
const frigateCardAction = convertActionToCardCustomAction(action);
|
const parsedAction = convertActionToCardCustomAction(action);
|
||||||
return !!frigateCardAction && frigateCardAction.frigate_card_action == 'menu_toggle';
|
return !!parsedAction && parsedAction.frigate_card_action == 'menu_toggle';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -591,6 +591,7 @@ const menuBaseSchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
alignment: z.enum(['matching', 'opposing']).default('matching').optional(),
|
alignment: z.enum(['matching', 'opposing']).default('matching').optional(),
|
||||||
icon: z.string().optional(),
|
icon: z.string().optional(),
|
||||||
|
permanent: z.boolean().default(false).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({
|
const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({
|
||||||
|
|||||||
@@ -1292,6 +1292,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
label: localize('config.menu.buttons.alignment'),
|
label: localize('config.menu.buttons.alignment'),
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
|
${this._renderSwitch(
|
||||||
|
`${CONF_MENU_BUTTONS}.${button}.permanent`,
|
||||||
|
this._defaults.menu.buttons[button]?.permanent ?? false,
|
||||||
|
{
|
||||||
|
label: localize('config.menu.buttons.permanent'),
|
||||||
|
},
|
||||||
|
)}
|
||||||
${this._renderNumberInput(`${CONF_MENU_BUTTONS}.${button}.priority`, {
|
${this._renderNumberInput(`${CONF_MENU_BUTTONS}.${button}.priority`, {
|
||||||
max: FRIGATE_MENU_PRIORITY_MAX,
|
max: FRIGATE_MENU_PRIORITY_MAX,
|
||||||
default: this._defaults.menu.buttons[button]?.priority,
|
default: this._defaults.menu.buttons[button]?.priority,
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
"media_player": "Envia al reproductor multimèdia",
|
"media_player": "Envia al reproductor multimèdia",
|
||||||
"microphone": "Micròfon",
|
"microphone": "Micròfon",
|
||||||
"mute": "Silenciar / Activar el so",
|
"mute": "Silenciar / Activar el so",
|
||||||
|
"permanent": "",
|
||||||
"play": "Reproduir / Pausa",
|
"play": "Reproduir / Pausa",
|
||||||
"priority": "Prioritat",
|
"priority": "Prioritat",
|
||||||
"ptz_controls": "",
|
"ptz_controls": "",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
"media_player": "Send to media player",
|
"media_player": "Send to media player",
|
||||||
"microphone": "Microphone",
|
"microphone": "Microphone",
|
||||||
"mute": "Mute / Unmute",
|
"mute": "Mute / Unmute",
|
||||||
|
"permanent": "Show when the menu is hidden",
|
||||||
"play": "Play / Pause",
|
"play": "Play / Pause",
|
||||||
"priority": "Priority",
|
"priority": "Priority",
|
||||||
"ptz_controls": "Show PTZ controls",
|
"ptz_controls": "Show PTZ controls",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
"media_player": "Envoyer au lecteur multimédia",
|
"media_player": "Envoyer au lecteur multimédia",
|
||||||
"microphone": "Microphone",
|
"microphone": "Microphone",
|
||||||
"mute": "Désactiver/Réactiver le son",
|
"mute": "Désactiver/Réactiver le son",
|
||||||
|
"permanent": "",
|
||||||
"play": "Jouer / Pause",
|
"play": "Jouer / Pause",
|
||||||
"priority": "Priorité",
|
"priority": "Priorité",
|
||||||
"ptz_controls": "",
|
"ptz_controls": "",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
"media_player": "Invia a Media Player",
|
"media_player": "Invia a Media Player",
|
||||||
"microphone": "",
|
"microphone": "",
|
||||||
"mute": "",
|
"mute": "",
|
||||||
|
"permanent": "",
|
||||||
"play": "",
|
"play": "",
|
||||||
"priority": "Priorità",
|
"priority": "Priorità",
|
||||||
"ptz_controls": "",
|
"ptz_controls": "",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
"media_player": "Enviar para o reprodutor de mídia",
|
"media_player": "Enviar para o reprodutor de mídia",
|
||||||
"microphone": "",
|
"microphone": "",
|
||||||
"mute": "",
|
"mute": "",
|
||||||
|
"permanent": "",
|
||||||
"play": "",
|
"play": "",
|
||||||
"priority": "Prioridade",
|
"priority": "Prioridade",
|
||||||
"ptz_controls": "",
|
"ptz_controls": "",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
"media_player": "Enviar para o reprodutor de mídia",
|
"media_player": "Enviar para o reprodutor de mídia",
|
||||||
"microphone": "",
|
"microphone": "",
|
||||||
"mute": "",
|
"mute": "",
|
||||||
|
"permanent": "",
|
||||||
"play": "",
|
"play": "",
|
||||||
"priority": "Prioridade",
|
"priority": "Prioridade",
|
||||||
"ptz_controls": "",
|
"ptz_controls": "",
|
||||||
|
|||||||
@@ -81,13 +81,14 @@ describe('MenuButtonController', () => {
|
|||||||
controller = new MenuButtonController();
|
controller = new MenuButtonController();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should have frigate menu button', () => {
|
describe('should have default menu button', () => {
|
||||||
it('with hidden menu style', () => {
|
it('with hidden menu style', () => {
|
||||||
const buttons = calculateButtons(controller);
|
const buttons = calculateButtons(controller);
|
||||||
|
|
||||||
expect(buttons).toContainEqual({
|
expect(buttons).toContainEqual({
|
||||||
icon: 'frigate',
|
icon: 'frigate',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
permanent: true,
|
||||||
priority: 50,
|
priority: 50,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: 'Frigate menu / Default view',
|
title: 'Frigate menu / Default view',
|
||||||
@@ -104,6 +105,7 @@ describe('MenuButtonController', () => {
|
|||||||
expect(buttons).toContainEqual({
|
expect(buttons).toContainEqual({
|
||||||
icon: 'frigate',
|
icon: 'frigate',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
permanent: true,
|
||||||
priority: 50,
|
priority: 50,
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: 'Frigate menu / Default view',
|
title: 'Frigate menu / Default view',
|
||||||
|
|||||||
@@ -72,8 +72,14 @@ describe('MenuController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('should set and sort buttons', () => {
|
describe('should set and sort buttons', () => {
|
||||||
it('by priority', () => {
|
it('without a hidden menu', () => {
|
||||||
const controller = new MenuController(createLitElement());
|
const controller = new MenuController(createLitElement());
|
||||||
|
controller.setMenuConfig(
|
||||||
|
createMenuConfig({
|
||||||
|
style: 'overlay',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
controller.setButtons([
|
controller.setButtons([
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
@@ -97,6 +103,9 @@ describe('MenuController', () => {
|
|||||||
icon: 'mdi:horse',
|
icon: 'mdi:horse',
|
||||||
priority: 40,
|
priority: 40,
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
|
|
||||||
|
// Will have no effect without a hidden menu.
|
||||||
|
permanent: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
@@ -118,6 +127,7 @@ describe('MenuController', () => {
|
|||||||
icon: 'mdi:horse',
|
icon: 'mdi:horse',
|
||||||
priority: 40,
|
priority: 40,
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
|
permanent: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
@@ -139,7 +149,7 @@ describe('MenuController', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with frigate button first', () => {
|
it('with an expanded hidden menu', () => {
|
||||||
const controller = new MenuController(createLitElement());
|
const controller = new MenuController(createLitElement());
|
||||||
controller.setMenuConfig(
|
controller.setMenuConfig(
|
||||||
createMenuConfig({
|
createMenuConfig({
|
||||||
@@ -151,13 +161,14 @@ describe('MenuController', () => {
|
|||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'mdi:cow',
|
icon: 'mdi:cow',
|
||||||
priority: 100,
|
priority: 99,
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'frigate',
|
icon: 'frigate',
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
|
permanent: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
@@ -172,19 +183,73 @@ describe('MenuController', () => {
|
|||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'frigate',
|
icon: 'frigate',
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
|
permanent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'custom:frigate-card-menu-icon',
|
||||||
|
icon: 'mdi:sheep',
|
||||||
|
priority: 100,
|
||||||
|
alignment: 'matching',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'custom:frigate-card-menu-icon',
|
||||||
|
icon: 'mdi:cow',
|
||||||
|
priority: 99,
|
||||||
|
alignment: 'matching',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with a non-expanded hidden menu', () => {
|
||||||
|
const controller = new MenuController(createLitElement());
|
||||||
|
controller.setMenuConfig(
|
||||||
|
createMenuConfig({
|
||||||
|
style: 'hidden',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
controller.setExpanded(false);
|
||||||
|
controller.setButtons([
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'mdi:cow',
|
icon: 'mdi:cow',
|
||||||
priority: 100,
|
priority: 100,
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'custom:frigate-card-menu-icon',
|
||||||
|
icon: 'frigate',
|
||||||
|
alignment: 'matching',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'mdi:sheep',
|
icon: 'mdi:sheep',
|
||||||
priority: 100,
|
priority: 100,
|
||||||
alignment: 'matching',
|
alignment: 'matching',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'custom:frigate-card-menu-icon',
|
||||||
|
icon: 'mdi:cow',
|
||||||
|
alignment: 'matching',
|
||||||
|
priority: 100,
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(controller.getButtons('matching')).toEqual([
|
||||||
|
{
|
||||||
|
type: 'custom:frigate-card-menu-icon',
|
||||||
|
icon: 'mdi:cow',
|
||||||
|
alignment: 'matching',
|
||||||
|
priority: 100,
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'custom:frigate-card-menu-icon',
|
||||||
|
icon: 'frigate',
|
||||||
|
alignment: 'matching',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -266,6 +331,7 @@ describe('MenuController', () => {
|
|||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'frigate',
|
icon: 'frigate',
|
||||||
|
permanent: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
@@ -277,6 +343,7 @@ describe('MenuController', () => {
|
|||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'frigate',
|
icon: 'frigate',
|
||||||
|
permanent: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -286,6 +353,7 @@ describe('MenuController', () => {
|
|||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
icon: 'frigate',
|
icon: 'frigate',
|
||||||
|
permanent: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
|
|||||||
Reference in New Issue
Block a user