Merge pull request #415 from dermotduffy/menu-refactor

Change submenus to use fixed mode so card curved borders behave correctly
This commit is contained in:
Dermot Duffy
2022-02-26 15:57:04 -08:00
committed by GitHub
7 changed files with 146 additions and 120 deletions
-4
View File
@@ -976,16 +976,12 @@ export class FrigateCard extends LitElement {
* @returns A rendered template.
*/
protected _renderMenu(): TemplateResult | void {
const classes = {
'hover-menu': this._getConfig().menu.mode.startsWith('hover-'),
};
return html`
<frigate-card-menu
.hass=${this._hass}
.menuConfig=${this._getConfig().menu}
.buttons=${this._getMenuButtons()}
.conditionState=${this._conditionState}
class="${classMap(classes)}"
></frigate-card-menu>
`;
}
-1
View File
@@ -47,7 +47,6 @@ import {
dispatchErrorMessageEvent,
dispatchExistingMediaShowInfoAsEvent,
dispatchMediaShowEvent,
dispatchMessageEvent,
getCameraIcon,
getCameraTitle,
homeAssistantSignPath,
+19 -53
View File
@@ -26,28 +26,27 @@ import {
} from '../common.js';
import menuStyle from '../scss/menu.scss';
import { Corner } from '@material/mwc-menu';
export const FRIGATE_BUTTON_MENU_ICON = 'frigate';
export const FRIGATE_ICON_FILLED =
'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' +
'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' +
'-0.43297,0.10824 -2.32718,-3.43665 -2.7601492,-4.95202 -0.4329602,-1.51538 ' +
'-0.6764993,-3.22017 -0.5682593,-4.19434 0.1082301,-0.97417 5.7097085,-2.48955 ' +
'5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' +
'1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' +
'-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' +
'-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' +
'-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' +
'-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' +
'5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' +
'1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' +
'-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' +
'-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' +
'-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' +
'-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' +
'-4.97909,2.05658 -4.97909,2.43542 0,0.37885 0.16236,0.67651 0.0541,1.54244 -0.10824,' +
'0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' +
'0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' +
'0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' +
'0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' +
'-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' +
'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' +
'-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' +
'-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' +
'0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' +
'0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' +
'0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' +
'0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' +
'-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' +
'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' +
'-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' +
'-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' +
'-0.21649,2.59778 -0.21649,3.43665 0,0.75379 -0.10824,2.43542 0,3.30135 z';
/**
@@ -63,11 +62,13 @@ export class FrigateCardMenu extends LitElement {
if (menuConfig) {
this.style.setProperty('--frigate-card-menu-button-size', menuConfig.button_size);
}
// Store the menu mode as an attribute (used for CSS attribute selectors).
this.setAttribute('data-mode', menuConfig.mode);
}
@state()
protected _menuConfig?: MenuConfig;
@state()
@property({ attribute: true, type: Boolean, reflect: true })
protected expanded = false;
@property({ attribute: false })
@@ -199,15 +200,7 @@ export class FrigateCardMenu extends LitElement {
*/
protected _renderButton(button: MenuButton): TemplateResult | void {
if (button.type == 'custom:frigate-card-menu-submenu') {
let corner: Corner | undefined;
if (this._menuConfig?.mode.endsWith('-left')) {
// Minor nicety: Start the menu to the right of the menu itself is on
// the left, otherwise use the default.
corner = 'BOTTOM_RIGHT';
}
return html` <frigate-card-submenu
.corner=${corner}
.hass=${this.hass}
.submenu=${button}
@action=${this._actionHandler.bind(this)}
@@ -276,39 +269,12 @@ export class FrigateCardMenu extends LitElement {
return;
}
const classes = {
'frigate-card-menu': true,
'overlay-hidden':
mode.startsWith('hidden-') ||
mode.startsWith('overlay-') ||
mode.startsWith('hover-'),
'expanded-horizontal':
(mode.startsWith('overlay-') ||
mode.startsWith('hover-') ||
(mode.startsWith('hidden-') && this.expanded)) &&
(mode.endsWith('-top') || mode.endsWith('-bottom')),
'expanded-vertical':
(mode.startsWith('overlay-') ||
mode.startsWith('hover-') ||
(mode.startsWith('hidden-') && this.expanded)) &&
(mode.endsWith('-left') || mode.endsWith('-right')),
full: mode == 'above' || mode == 'below',
left: mode.endsWith('-left'),
right: mode.endsWith('-right'),
top: mode.endsWith('-top'),
bottom: mode.endsWith('-bottom'),
};
// If the hidden menu isn't expanded, only show the Frigate button.
const buttons =
!mode.startsWith('hidden-') || this.expanded
? this.buttons
: this.buttons.filter((button) => button.icon === FRIGATE_BUTTON_MENU_ICON);
return html`
<div class=${classMap(classes)}>
${buttons.map((button) => this._renderButton(button))}
</div>
`;
return html` ${buttons.map((button) => this._renderButton(button))} `;
}
/**
+63 -19
View File
@@ -1,6 +1,7 @@
import type { Corner, Menu } from '@material/mwc-menu';
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { HomeAssistant } from 'custom-card-helpers';
import { customElement, property } from 'lit/decorators';
import { customElement, property, query } from 'lit/decorators';
import { frigateCardHasAction, refreshDynamicStateParameters } from '../common.js';
import { ifDefined } from 'lit/directives/if-defined';
import { styleMap } from 'lit/directives/style-map';
@@ -9,7 +10,6 @@ import { ExtendedHomeAssistant, MenuSubmenu, MenuSubmenuItem } from '../types.js
import { actionHandler } from '../action-handler-directive.js';
import submenuStyle from '../scss/submenu.scss';
import type { Corner } from "@material/mwc-menu";
@customElement('frigate-card-submenu')
export class FrigateCardSubmenu extends LitElement {
@@ -22,11 +22,13 @@ export class FrigateCardSubmenu extends LitElement {
@property({ attribute: false })
public corner?: Corner;
@query('mwc-menu') private _menu?: Menu;
protected _renderItem(item: MenuSubmenuItem): TemplateResult | void {
if (!this.hass) {
return;
}
const stateParameters = refreshDynamicStateParameters(this.hass, {...item});
const stateParameters = refreshDynamicStateParameters(this.hass, { ...item });
return html`
<mwc-list-item
@@ -59,14 +61,68 @@ export class FrigateCardSubmenu extends LitElement {
`;
}
/**
* Get the fixed root element in which fixed elements are positioned against.
* @returns The fixed root element or null if not found.
*/
protected _getFixedRoot(): HTMLElement | null {
let n = this as Node | null;
while (n) {
if (n.nodeType === Node.ELEMENT_NODE && (n as Element).tagName === 'HA-APP-LAYOUT') {
return n as HTMLElement;
}
n = n.parentNode
? n.parentNode
: n.nodeType === Node.DOCUMENT_FRAGMENT_NODE
? (n as ShadowRoot).host
: null;
}
return null;
}
protected render(): TemplateResult {
if (!this.submenu) {
return html``;
}
return html`
<ha-button-menu
corner=${this.corner || "BOTTOM_LEFT"}
<ha-icon-button
style="${styleMap(this.submenu.style || {})}"
class="button"
.label=${this.submenu.title || ''}
.actionHandler=${actionHandler({
hasHold: frigateCardHasAction(this.submenu.hold_action),
hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
})}
@click=${() => {
if (this._menu) {
// Hack: This insanity is brought about by lack of MWCMenu playing
// nicely with the Home Assistant view/sidepanel. The menu must be
// rendered in fixed mode in order to allow the menu to render
// outside of the card boundaries (card has overflow as hidden).
// When in fixed mode, the menu anchoring refuses to get the
// placement correct -- it's always off by exactly the dimensions of
// the sidebar/header. To work around this we iterate up the DOM to
// find the main root (excl. the sidebar) and subtract those
// dimensions off wherever the menu believes it should render.
this._menu.anchor = this;
const root = this._getFixedRoot();
if (root) {
const rootPosition = root.getBoundingClientRect();
this._menu.x = -rootPosition.x;
this._menu.y = -rootPosition.y;
} else {
this._menu.x = 0;
this._menu.y = 0;
}
this._menu.show();
}
}}
>
<ha-icon icon="${this.submenu.icon}"></ha-icon>
</ha-icon-button>
<mwc-menu
.corner=${this.corner || "BOTTOM_LEFT"}
fixed
@closed=${
// Prevent the submenu closing from closing anything upstream (e.g.
// selecting a submenu in the editor dialog should not close the
@@ -74,20 +130,8 @@ export class FrigateCardSubmenu extends LitElement {
(ev) => ev.stopPropagation()
}
>
<ha-icon-button
style="${styleMap(this.submenu.style || {})}"
class="button"
slot="trigger"
.label=${this.submenu.title || ''}
.actionHandler=${actionHandler({
hasHold: frigateCardHasAction(this.submenu.hold_action),
hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
})}
>
<ha-icon icon="${this.submenu.icon}"></ha-icon>
</ha-icon-button>
${this.submenu.items.map(this._renderItem.bind(this))}
</ha-button-menu>
</mwc-menu>
`;
}
+13 -15
View File
@@ -1,5 +1,8 @@
:host {
display: block;
// Anchor absolutes to the outer part of the card.
position: relative;
}
.container {
@@ -10,11 +13,6 @@
margin: auto;
display: flex;
justify-content: center;
// Need to apply the border radius on the container level, as the ha-card has
// overflow visible in order to allow a submenu to extend beyond the card
// boundary.
border-radius: var(--ha-card-border-radius, 4px);
}
.frigate-card-contents {
@@ -44,29 +42,29 @@
}
/* The 'hover' menu mode is styling applied outside of the menu itself */
.hover-menu {
frigate-card-menu[data-mode^=hover-] {
z-index: 1;
transition: all 0.5s ease;
transition: opacity 0.5s ease;
}
.outer + .hover-menu {
.outer + frigate-card-menu[data-mode^=hover-] {
opacity: 0.0;
}
.outer:hover + .hover-menu, .hover-menu:hover {
opacity: 0.9;
.outer:hover + frigate-card-menu[data-mode^=hover-], frigate-card-menu[data-mode^=hover-]:hover {
opacity: 1.0;
}
ha-card {
display: flex;
flex-direction: column;
margin: auto;
// Some elements (such as menus) may need to extend beyond the card boundary.
overflow: visible;
overflow: hidden;
width: 100%;
height: 100%;
position: relative;
position: static;
color: var(--secondary-text-color, white);
background-color: var(--secondary-background-color, black);
border-radius: var(--ha-card-border-radius, 4px);
}
frigate-card-live.hidden {
@@ -93,6 +91,6 @@ frigate-card-live.hidden {
:host(:-moz-full-screen) ha-card {
@include fullscreen-ha-card;
}
:host(:-webkit-full-screen) ha-card {
:host(:-ms-fullscreen) ha-card {
@include fullscreen-ha-card;
}
+47 -28
View File
@@ -4,25 +4,22 @@
--frigate-card-menu-button-size: 40px;
--mdc-icon-button-size: var(--frigate-card-menu-button-size);
--mdc-icon-size: calc(var(--mdc-icon-button-size) / 2);
}
.frigate-card-menu {
/* Menu div itself does not handle click events. Without this, in overlay
mode, the menu div prevents clicking on gallery items 'behind' the overlay.
*/
// Menu itself does not handle click events. Without this, in overlay mode the
// menu component prevents clicking on gallery items 'behind' the overlay.
pointer-events: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.frigate-card-menu.overlay-hidden {
position: absolute;
overflow: hidden;
width: calc(var(--frigate-card-menu-button-size) + 6px);
height: calc(var(--frigate-card-menu-button-size) + 6px);
:host([data-mode="above"]),:host([data-mode="below"]) {
width: 100%;
background: var(--secondary-background-color);
}
.frigate-card-menu.overlay-hidden.left {
:host([data-mode$="-left"]) {
left: 0px;
top: 0px;
@@ -38,23 +35,21 @@
// - https://bugs.chromium.org/p/chromium/issues/detail?id=507397
writing-mode: vertical-lr;
}
.frigate-card-menu.overlay-hidden.top {
left: 0px;
top: 0px;
}
.frigate-card-menu.overlay-hidden.right {
:host([data-mode$="-right"]) {
right: 0px;
top: 0px;
// See "Awful hack" above.
writing-mode: vertical-rl;
}
.frigate-card-menu.overlay-hidden.left > *,.frigate-card-menu.overlay-hidden.right > * {
// See "Awful hack" above.
writing-mode: horizontal-tb;
}
.frigate-card-menu.overlay-hidden.bottom {
:host([data-mode$="-top"]) {
left: 0px;
top: 0px;
}
:host([data-mode$="-bottom"]) {
left: 0px;
bottom: 0px;
@@ -62,20 +57,44 @@
// Frigate button in the same place.
flex-wrap: wrap-reverse;
}
.frigate-card-menu.overlay-hidden.expanded-horizontal {
:host([data-mode^="overlay-"]),:host([data-mode^="hover-"]),:host([data-mode^="hidden-"]) {
position: absolute;
overflow: hidden;
width: calc(var(--frigate-card-menu-button-size) + 6px);
height: calc(var(--frigate-card-menu-button-size) + 6px);
}
:host([data-mode="overlay-top"]),
:host([data-mode="overlay-bottom"]),
:host([data-mode="hover-top"]),
:host([data-mode="hover-bottom"]),
:host([data-mode="hidden-top"][expanded]),
:host([data-mode="hidden-bottom"][expanded]) {
width: 100%;
height: auto;
overflow: visible;
background: linear-gradient(90deg, rgba(0,0,0,0.3), rgba(0,0,0,0));
}
.frigate-card-menu.overlay-hidden.expanded-vertical {
:host([data-mode="overlay-left"]),
:host([data-mode="overlay-right"]),
:host([data-mode="hover-left"]),
:host([data-mode="hover-right"]),
:host([data-mode="hidden-left"][expanded]),
:host([data-mode="hidden-right"][expanded]) {
height: 100%;
width: auto;
overflow: visible;
background: linear-gradient(180deg, rgba(0,0,0,0.3), rgba(0,0,0,0));
}
/* Full above/below menu */
.frigate-card-menu.full {
width: 100%;
background: var(--secondary-background-color);
}
:host([data-mode="overlay-left"]) > *,
:host([data-mode="hover-left"]) > *,
:host([data-mode="hidden-left"]) > *,
:host([data-mode="overlay-right"]) > *,
:host([data-mode="hover-right"]) > *,
:host([data-mode="hidden-right"]) > * {
// See "Awful hack" above.
writing-mode: horizontal-tb;
}
+4
View File
@@ -6,4 +6,8 @@
mwc-list-item {
z-index: 20;
}
mwc-list-item[disabled] {
color: var(--disabled-text-color);
}