feat: Add permanent menu items for hidden menus (#1636)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -363,6 +363,7 @@
|
||||
"media_player": "Invia a Media Player",
|
||||
"microphone": "",
|
||||
"mute": "",
|
||||
"permanent": "",
|
||||
"play": "",
|
||||
"priority": "Priorità",
|
||||
"ptz_controls": "",
|
||||
|
||||
@@ -363,6 +363,7 @@
|
||||
"media_player": "Enviar para o reprodutor de mídia",
|
||||
"microphone": "",
|
||||
"mute": "",
|
||||
"permanent": "",
|
||||
"play": "",
|
||||
"priority": "Prioridade",
|
||||
"ptz_controls": "",
|
||||
|
||||
@@ -363,6 +363,7 @@
|
||||
"media_player": "Enviar para o reprodutor de mídia",
|
||||
"microphone": "",
|
||||
"mute": "",
|
||||
"permanent": "",
|
||||
"play": "",
|
||||
"priority": "Prioridade",
|
||||
"ptz_controls": "",
|
||||
|
||||
Reference in New Issue
Block a user