diff --git a/docs/configuration/elements/custom/README.md b/docs/configuration/elements/custom/README.md index a8200888..3b847998 100644 --- a/docs/configuration/elements/custom/README.md +++ b/docs/configuration/elements/custom/README.md @@ -22,7 +22,7 @@ See the [conditional elements example](../../../examples.md?id=conditional-eleme ## `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 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` 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 | Description | -| --------- | ------------------------------------------- | -| `type` | Must be `custom:frigate-card-menu-submenu`. | -| `items` | A list of menu items, as described below. | +| Parameter | Default | Description | +| ------------------------------------------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | | Must be `custom:frigate-card-menu-submenu`. | +| `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` | +| `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 @@ -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 | Description | -| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | Must be `custom:frigate-card-menu-submenu-select`. | -| `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. | +| Parameter | Default | Description | +| ------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | | Must be `custom:frigate-card-menu-submenu-select`. | +| `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` | +| `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). ## `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 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` Add an arbitrary icon to the status bar. @@ -154,44 +195,82 @@ elements: ## Fully expanded reference -> [Actions](../../actions/README.md) are omitted for simplicity. +?> [Actions](../../actions/README.md) are omitted for simplicity. [](../../common/expanded-warning.md ':include') ```yaml elements: - type: custom:frigate-card-menu-icon + enabled: true + entity: light.office_main_lights icon: mdi:car + permanent: false + priority: 50 + style: + color: white title: Vroom - type: custom:frigate-card-menu-state-icon + enabled: true entity: light.office_main_lights - title: Office lights icon: mdi:chair-rolling + permanent: false + priority: 50 state_color: true + style: + color: white + title: Office lights - type: custom:frigate-card-menu-submenu + enabled: true + entity: light.office_main_lights icon: mdi:menu items: - - title: Lights - icon: mdi:lightbulb + - enabled: true entity: light.office_main_lights + icon: mdi:lightbulb + selected: false + state_color: true + style: + color: white tap_action: action: toggle - - title: Google + title: Lights + - enabled: true icon: mdi:google - enabled: false + selected: false + style: + color: white tap_action: action: url 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 - icon: mdi:lamps + enabled: true entity: input_select.kitchen_scene + icon: mdi:lamps options: + items: scene.kitchen_cooking_scene: + enabled: true icon: mdi:chef-hat + selected: false + state_color: true + style: + color: white title: Cooking time! scene.kitchen_tv_scene: icon: mdi:television title: TV! + permanent: false + priority: 50 + style: + color: white + title: 'Kitchen Scene' # Show a pig icon if a variety of conditions are met. - type: custom:frigate-card-conditional elements: diff --git a/docs/configuration/menu.md b/docs/configuration/menu.md index e885a32c..d0ab239b 100644 --- a/docs/configuration/menu.md +++ b/docs/configuration/menu.md @@ -51,12 +51,12 @@ menu: ### Options for each button -| 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`. | -| `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`. | -| `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`. | +| 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`. | +| `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`. | +| `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` @@ -64,7 +64,7 @@ This card supports several menu styles. | Key | Description | Screenshot | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -| `hidden` | Hide the menu by default, expandable upon clicking the Frigate button. | ![](../images/menu-mode-hidden.png 'Menu hidden :size=400') | +| `hidden` | Hide the menu by default. It may be toggled open as needed. | ![](../images/menu-mode-hidden.png 'Menu hidden :size=400') | | `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. | ![](../images/menu-mode-overlay.png 'Menu hover-card :size=400') | | `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. | ![](../images/menu-mode-overlay.png 'Menu hover :size=400') | | `none` | No menu is shown. | ![](../images/menu-mode-none.png 'No menu :size=400') | diff --git a/src/card-controller/actions/factory.ts b/src/card-controller/actions/factory.ts index 2cf522bc..a23794c8 100644 --- a/src/card-controller/actions/factory.ts +++ b/src/card-controller/actions/factory.ts @@ -85,8 +85,6 @@ export class ActionFactory { case 'fullscreen': return new FullscreenAction(context, frigateCardAction, options?.config); 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); case 'camera_select': return new CameraSelectAction(context, frigateCardAction, options?.config); diff --git a/src/components-lib/menu-button-controller.ts b/src/components-lib/menu-button-controller.ts index b985c4c3..8495984c 100644 --- a/src/components-lib/menu-button-controller.ts +++ b/src/components-lib/menu-button-controller.ts @@ -66,7 +66,7 @@ export class MenuButtonController { options?: MenuButtonControllerOptions, ): MenuItem[] { return [ - this._getFrigateButton(config), + this._getDefaultButton(config), this._getCamerasButton(config, cameraManager, options?.view), this._getSubstreamsButton(config, cameraManager, options?.view), this._getLiveButton(config, options?.view, options?.viewManager), @@ -105,7 +105,7 @@ export class MenuButtonController { ].filter(isTruthy); } - protected _getFrigateButton(config: FrigateCardConfig): MenuItem { + protected _getDefaultButton(config: FrigateCardConfig): MenuItem { return { // Use a magic icon value that the menu will use to render the custom // Frigate icon. @@ -113,6 +113,9 @@ export class MenuButtonController { ...config.menu.buttons.frigate, type: 'custom:frigate-card-menu-icon', title: localize('config.menu.buttons.frigate'), + // The default button always shows regardless of whether the menu is + // hidden or not. + permanent: true, tap_action: config.menu?.style === 'hidden' ? (createGeneralAction('menu_toggle') as FrigateCardCustomAction) diff --git a/src/components-lib/menu-controller.ts b/src/components-lib/menu-controller.ts index 967a1bcc..c621cf44 100644 --- a/src/components-lib/menu-controller.ts +++ b/src/components-lib/menu-controller.ts @@ -1,11 +1,14 @@ import { HASSDomEvent, HomeAssistant } from '@dermotduffy/custom-card-helpers'; import { LitElement } from 'lit'; +import { orderBy } from 'lodash-es'; import { FRIGATE_ICON_SVG_PATH } from '../camera-manager/frigate/icon.js'; -import type { - ActionType, - ActionsConfig, - MenuConfig, - MenuItem, +import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js'; +import { + FRIGATE_MENU_PRIORITY_MAX, + type ActionType, + type ActionsConfig, + type MenuConfig, + type MenuItem, } from '../config/types.js'; import { FRIGATE_BUTTON_MENU_ICON } from '../const.js'; import { StateParameters } from '../types.js'; @@ -15,7 +18,6 @@ import { } from '../utils/action'; import { arrayify, isTruthy, setOrRemoveAttribute } from '../utils/basic.js'; import { refreshDynamicStateParameters } from '../utils/ha/index.js'; -import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js'; export class MenuController { protected _host: LitElement; @@ -59,8 +61,6 @@ export class MenuController { } public getButtons(alignment: 'matching' | 'opposing'): MenuItem[] { - const style = this._config?.style; - const aligned = (button: MenuItem): boolean => { return ( button.alignment === alignment || (alignment === 'matching' && !button.alignment) @@ -71,16 +71,12 @@ export class MenuController { return button.enabled !== false; }; - const suitableToShowIfHiddenMenu = (button: MenuItem): boolean => { - // If the hidden menu isn't expanded, only show the Frigate button. - return ( - style !== 'hidden' || this._expanded || button.icon === FRIGATE_BUTTON_MENU_ICON - ); + const show = (button: MenuItem): boolean => { + return !this._isHidingMenu() || this._expanded || !!button.permanent; }; return this._buttons.filter( - (button) => - enabled(button) && aligned(button) && suitableToShowIfHiddenMenu(button), + (button) => enabled(button) && aligned(button) && show(button), ); } @@ -126,7 +122,7 @@ export class MenuController { let menuToggle = false; const toggleLessActions = actions.filter( - (item) => isTruthy(item) && !this._isMenuToggleAction(item), + (item) => isTruthy(item) && !this._isUnknownActionMenuToggleAction(item), ); if (toggleLessActions.length != actions.length) { menuToggle = true; @@ -176,42 +172,27 @@ export class MenuController { } protected _sortButtons(): void { - const style = this._config?.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; - }; - - this._buttons.sort(sortButtons); + this._buttons = orderBy( + this._buttons, + (button) => { + const priority = button.priority ?? 0; + // If the menu is hidden, the buttons that toggle the menu must come + // first. + return ( + priority + + (this._isHidingMenu() && button.permanent ? FRIGATE_MENU_PRIORITY_MAX : 0) + ); + }, + ['desc'], + ); } protected _isHidingMenu(): boolean { - return this._config?.style === 'hidden' ?? false; + return this._config?.style === 'hidden'; } - protected _isMenuToggleAction(action: ActionType): boolean { - const frigateCardAction = convertActionToCardCustomAction(action); - return !!frigateCardAction && frigateCardAction.frigate_card_action == 'menu_toggle'; + protected _isUnknownActionMenuToggleAction(action: ActionType): boolean { + const parsedAction = convertActionToCardCustomAction(action); + return !!parsedAction && parsedAction.frigate_card_action == 'menu_toggle'; } } diff --git a/src/config/types.ts b/src/config/types.ts index 6e95a03c..16e1cef2 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -591,6 +591,7 @@ const menuBaseSchema = z.object({ .optional(), alignment: z.enum(['matching', 'opposing']).default('matching').optional(), icon: z.string().optional(), + permanent: z.boolean().default(false).optional(), }); const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({ diff --git a/src/editor.ts b/src/editor.ts index 4f43c307..867a11b5 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -1292,6 +1292,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor 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`, { max: FRIGATE_MENU_PRIORITY_MAX, default: this._defaults.menu.buttons[button]?.priority, diff --git a/src/localize/languages/ca.json b/src/localize/languages/ca.json index 63ba95c2..743d63c4 100644 --- a/src/localize/languages/ca.json +++ b/src/localize/languages/ca.json @@ -363,6 +363,7 @@ "media_player": "Envia al reproductor multimèdia", "microphone": "Micròfon", "mute": "Silenciar / Activar el so", + "permanent": "", "play": "Reproduir / Pausa", "priority": "Prioritat", "ptz_controls": "", diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index d3ba2a2c..1e190523 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -363,6 +363,7 @@ "media_player": "Send to media player", "microphone": "Microphone", "mute": "Mute / Unmute", + "permanent": "Show when the menu is hidden", "play": "Play / Pause", "priority": "Priority", "ptz_controls": "Show PTZ controls", diff --git a/src/localize/languages/fr.json b/src/localize/languages/fr.json index 6c02fc0b..f990fd57 100644 --- a/src/localize/languages/fr.json +++ b/src/localize/languages/fr.json @@ -363,6 +363,7 @@ "media_player": "Envoyer au lecteur multimédia", "microphone": "Microphone", "mute": "Désactiver/Réactiver le son", + "permanent": "", "play": "Jouer / Pause", "priority": "Priorité", "ptz_controls": "", diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index 76338a10..b9446c32 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -363,6 +363,7 @@ "media_player": "Invia a Media Player", "microphone": "", "mute": "", + "permanent": "", "play": "", "priority": "Priorità", "ptz_controls": "", diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index e0d017c5..e820e875 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -363,6 +363,7 @@ "media_player": "Enviar para o reprodutor de mídia", "microphone": "", "mute": "", + "permanent": "", "play": "", "priority": "Prioridade", "ptz_controls": "", diff --git a/src/localize/languages/pt-PT.json b/src/localize/languages/pt-PT.json index 53cb03fa..6306ff65 100644 --- a/src/localize/languages/pt-PT.json +++ b/src/localize/languages/pt-PT.json @@ -363,6 +363,7 @@ "media_player": "Enviar para o reprodutor de mídia", "microphone": "", "mute": "", + "permanent": "", "play": "", "priority": "Prioridade", "ptz_controls": "", diff --git a/tests/components-lib/menu-button-controller.test.ts b/tests/components-lib/menu-button-controller.test.ts index 0833a42e..4f4bf50d 100644 --- a/tests/components-lib/menu-button-controller.test.ts +++ b/tests/components-lib/menu-button-controller.test.ts @@ -81,13 +81,14 @@ describe('MenuButtonController', () => { controller = new MenuButtonController(); }); - describe('should have frigate menu button', () => { + describe('should have default menu button', () => { it('with hidden menu style', () => { const buttons = calculateButtons(controller); expect(buttons).toContainEqual({ icon: 'frigate', enabled: true, + permanent: true, priority: 50, type: 'custom:frigate-card-menu-icon', title: 'Frigate menu / Default view', @@ -104,6 +105,7 @@ describe('MenuButtonController', () => { expect(buttons).toContainEqual({ icon: 'frigate', enabled: true, + permanent: true, priority: 50, type: 'custom:frigate-card-menu-icon', title: 'Frigate menu / Default view', diff --git a/tests/components-lib/menu-controller.test.ts b/tests/components-lib/menu-controller.test.ts index 99aee2b0..e2bd5c79 100644 --- a/tests/components-lib/menu-controller.test.ts +++ b/tests/components-lib/menu-controller.test.ts @@ -72,8 +72,14 @@ describe('MenuController', () => { }); describe('should set and sort buttons', () => { - it('by priority', () => { + it('without a hidden menu', () => { const controller = new MenuController(createLitElement()); + controller.setMenuConfig( + createMenuConfig({ + style: 'overlay', + }), + ); + controller.setButtons([ { type: 'custom:frigate-card-menu-icon', @@ -97,6 +103,9 @@ describe('MenuController', () => { icon: 'mdi:horse', priority: 40, alignment: 'matching', + + // Will have no effect without a hidden menu. + permanent: true, }, { type: 'custom:frigate-card-menu-icon', @@ -118,6 +127,7 @@ describe('MenuController', () => { icon: 'mdi:horse', priority: 40, alignment: 'matching', + permanent: true, }, { alignment: 'matching', @@ -139,7 +149,7 @@ describe('MenuController', () => { ]); }); - it('with frigate button first', () => { + it('with an expanded hidden menu', () => { const controller = new MenuController(createLitElement()); controller.setMenuConfig( createMenuConfig({ @@ -151,13 +161,14 @@ describe('MenuController', () => { { type: 'custom:frigate-card-menu-icon', icon: 'mdi:cow', - priority: 100, + priority: 99, alignment: 'matching', }, { type: 'custom:frigate-card-menu-icon', icon: 'frigate', alignment: 'matching', + permanent: true, }, { type: 'custom:frigate-card-menu-icon', @@ -172,19 +183,73 @@ describe('MenuController', () => { type: 'custom:frigate-card-menu-icon', icon: 'frigate', 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', icon: 'mdi:cow', priority: 100, alignment: 'matching', }, + { + type: 'custom:frigate-card-menu-icon', + icon: 'frigate', + 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', + 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', icon: 'frigate', + permanent: true, }, { type: 'custom:frigate-card-menu-icon', @@ -277,6 +343,7 @@ describe('MenuController', () => { { type: 'custom:frigate-card-menu-icon', icon: 'frigate', + permanent: true, }, ]); @@ -286,6 +353,7 @@ describe('MenuController', () => { { type: 'custom:frigate-card-menu-icon', icon: 'frigate', + permanent: true, }, { type: 'custom:frigate-card-menu-icon',