From ed0bb99a45d7540c987267c4a7695ab8c48d24a9 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Fri, 13 Dec 2024 08:11:26 -0800 Subject: [PATCH] fix: Panel height should be 100% when casted (#1750) --- src/card-controller/card-element-manager.ts | 4 ++- src/components-lib/menu-button-controller.ts | 21 ++++----------- src/scss/card.scss | 10 +++++-- src/utils/casting.ts | 7 +++++ .../card-element-manager.test.ts | 3 +++ tests/utils/casting.test.ts | 26 +++++++++++++++++++ vite.config.ts | 1 + 7 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 src/utils/casting.ts create mode 100644 tests/utils/casting.test.ts diff --git a/src/card-controller/card-element-manager.ts b/src/card-controller/card-element-manager.ts index bea978c3..04cd163b 100644 --- a/src/card-controller/card-element-manager.ts +++ b/src/card-controller/card-element-manager.ts @@ -1,6 +1,7 @@ import { LitElement, ReactiveControllerHost } from 'lit'; import { ActionEventTarget } from '../action-handler-directive'; import { setOrRemoveAttribute } from '../utils/basic'; +import { isBeingCasted } from '../utils/casting'; import { isCardInPanel } from '../utils/ha'; import { ActionExecutionRequestEventTarget } from './actions/utils/execution-request'; import { InitializationAspect } from './initialization-manager'; @@ -83,7 +84,7 @@ export class CardElementManager { // Whether or not the card is in panel mode on the dashboard. setOrRemoveAttribute(this._element, isCardInPanel(this._element), 'panel'); - setOrRemoveAttribute(this._element, true, 'tabindex', '0'); + setOrRemoveAttribute(this._element, isBeingCasted(), 'casted'); this._api.getFullscreenManager().connect(); @@ -137,6 +138,7 @@ export class CardElementManager { public elementDisconnected(): void { setOrRemoveAttribute(this._element, false, 'panel'); setOrRemoveAttribute(this._element, false, 'tabindex'); + setOrRemoveAttribute(this._element, false, 'casted'); // When the dashboard 'tab' is changed, the media is effectively unloaded. this._api.getMediaLoadedInfoManager().clear(); diff --git a/src/components-lib/menu-button-controller.ts b/src/components-lib/menu-button-controller.ts index 8495984c..e76aa22a 100644 --- a/src/components-lib/menu-button-controller.ts +++ b/src/components-lib/menu-button-controller.ts @@ -15,13 +15,14 @@ import { localize } from '../localize/localize.js'; import { MediaLoadedInfo } from '../types'; import { createCameraAction, - createPTZMultiAction, createDisplayModeAction, + createGeneralAction, createMediaPlayerAction, createPTZControlsAction, - createGeneralAction, + createPTZMultiAction, } from '../utils/action'; import { isTruthy } from '../utils/basic'; +import { isBeingCasted } from '../utils/casting'; import { getEntityIcon, getEntityTitle } from '../utils/ha'; import { getPTZTarget } from '../utils/ptz'; import { getStreamCameraID, hasSubstream } from '../utils/substream'; @@ -340,11 +341,7 @@ export class MenuButtonController { const mediaCapabilities = selectedMedia ? cameraManager?.getMediaCapabilities(selectedMedia) : null; - if ( - view?.isViewerView() && - mediaCapabilities?.canDownload && - !this._isBeingCasted() - ) { + if (view?.isViewerView() && mediaCapabilities?.canDownload && !isBeingCasted()) { return { icon: 'mdi:download', ...config.menu.buttons.download, @@ -429,7 +426,7 @@ export class MenuButtonController { config: FrigateCardConfig, inFullscreenMode?: boolean, ): MenuItem | null { - return !this._isBeingCasted() + return !isBeingCasted() ? { icon: inFullscreenMode ? 'mdi:fullscreen-exit' : 'mdi:fullscreen', ...config.menu.buttons.fullscreen, @@ -712,12 +709,4 @@ export class MenuButtonController { } return {}; } - - /** - * Determine if the card is currently being casted. - * @returns - */ - protected _isBeingCasted(): boolean { - return !!navigator.userAgent.match(/CrKey\//); - } } diff --git a/src/scss/card.scss b/src/scss/card.scss index 5c4ab1af..1761c8b3 100644 --- a/src/scss/card.scss +++ b/src/scss/card.scss @@ -39,10 +39,16 @@ frigate-card-loading { :host([dark]) { filter: brightness(75%); } -:host([panel]) { - // Card always extends to the full height in panel mode +:host([panel]:not([casted])) { + // Card always extends to the full height in panel mode minus the header. height: calc(100vh - var(--header-height)); } +:host([panel][casted]) { + // Card always extends to the full height in panel mode when casting (there is + // no header). + // See: https://github.com/dermotduffy/frigate-hass-card/issues/1746 + height: 100%; +} div.main { position: relative; diff --git a/src/utils/casting.ts b/src/utils/casting.ts new file mode 100644 index 00000000..9abc4366 --- /dev/null +++ b/src/utils/casting.ts @@ -0,0 +1,7 @@ +/** + * Determine if the card is currently being casted. + * @returns + */ +export const isBeingCasted = (): boolean => { + return !!navigator.userAgent.match(/CrKey\//); +}; diff --git a/tests/card-controller/card-element-manager.test.ts b/tests/card-controller/card-element-manager.test.ts index ee3b19c6..008b1eed 100644 --- a/tests/card-controller/card-element-manager.test.ts +++ b/tests/card-controller/card-element-manager.test.ts @@ -101,6 +101,7 @@ describe('CardElementManager', () => { manager.elementConnected(); expect(element.getAttribute('panel')).toBeNull(); + expect(element.getAttribute('casted')).toBeNull(); expect(api.getFullscreenManager().connect).toBeCalled(); expect(addEventListener).toBeCalledWith( @@ -134,6 +135,7 @@ describe('CardElementManager', () => { const element = createLitElement(); element.setAttribute('panel', ''); + element.setAttribute('casted', ''); const removeEventListener = vi.fn(); element.removeEventListener = removeEventListener; @@ -150,6 +152,7 @@ describe('CardElementManager', () => { manager.elementDisconnected(); expect(element.getAttribute('panel')).toBeNull(); + expect(element.getAttribute('casted')).toBeNull(); expect(api.getMediaLoadedInfoManager().clear).toBeCalled(); expect(api.getFullscreenManager().disconnect).toBeCalled(); diff --git a/tests/utils/casting.test.ts b/tests/utils/casting.test.ts new file mode 100644 index 00000000..2e8a3b84 --- /dev/null +++ b/tests/utils/casting.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it, vi } from 'vitest'; +import { isBeingCasted } from '../../src/utils/casting.js'; + +describe('isBeingCasted', () => { + it('should confirm being casted', () => { + vi.stubGlobal('navigator', { + userAgent: + 'Mozilla/5.0 (Fuchsia) AppleWebKit/537.36 (KHTML, like Gecko) ' + + 'Chrome/114.0.0.0 Safari/537.36 CrKey/1.56.500000', + }); + + // Import the function + expect(isBeingCasted()).toBeTruthy(); + }); + + it('should confirm not being casted', () => { + vi.stubGlobal('navigator', { + userAgent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' + + 'Chrome/131.0.0.0 Safari/537.36', + }); + + // Import the function + expect(isBeingCasted()).toBeFalsy(); + }); +}); diff --git a/vite.config.ts b/vite.config.ts index 3b2379ab..bf807445 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,6 +20,7 @@ const FULL_COVERAGE_FILES_RELATIVE = [ 'utils/audio.ts', 'utils/basic.ts', 'utils/camera.ts', + 'utils/casting.ts', 'utils/custom-icons.ts', 'utils/debug.ts', 'utils/diagnostics.ts',