diff --git a/package.json b/package.json index 0313b1fa..3a889531 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dayjs": "^1.10.6", "home-assistant-js-websocket": "^5.11.1", "lit": "^2.0.0-rc.2", + "screenfull": "^5.1.0", "zod": "^3.8.2" }, "devDependencies": { diff --git a/src/card.ts b/src/card.ts index 5a16a5ad..f60a1bd6 100644 --- a/src/card.ts +++ b/src/card.ts @@ -17,6 +17,7 @@ import { getLovelace, stateIcon, } from 'custom-card-helpers'; +import screenfull from 'screenfull'; import { MenuButton, frigateCardConfigSchema } from './types'; import type { @@ -191,6 +192,13 @@ export class FrigateCard extends LitElement { description: localize('menu.frigate_ui'), }); } + if ((this.config.menu_buttons?.fullscreen ?? false) && screenfull.isEnabled) { + buttons.set('fullscreen', { + icon: screenfull.isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen', + description: localize('menu.fullscreen'), + }); + } + const entities = this.config.entities || []; for (let i = 0; this._hass && i < entities.length; i++) { if (!entities[i].show) { @@ -294,6 +302,11 @@ export class FrigateCard extends LitElement { window.open(frigate_url); } break; + case 'fullscreen': + if (screenfull.isEnabled) { + screenfull.toggle(this); + } + break; default: // If it's unknown, it's assumed to be an entity_id. fireEvent(this, 'hass-more-info', { entityId: name }); @@ -383,6 +396,25 @@ export class FrigateCard extends LitElement { } } + protected _fullScreenHandler(): void { + // Re-render after a change to fullscreen mode to take advantage of + // the expanded screen real-estate (vs staying in aspect-ratio locked + // modes). + this.requestUpdate(); + } + connectedCallback(): void { + super.connectedCallback(); + if (screenfull.isEnabled) { + screenfull.on('change', this._fullScreenHandler.bind(this)); + } + } + disconnectedCallback(): void { + if (screenfull.isEnabled) { + screenfull.off('change', this._fullScreenHandler.bind(this)); + } + super.disconnectedCallback(); + } + protected _getAspectRatioPadding(): number | null { const aspect_ratio_mode = this.config.dimensions?.aspect_ratio_mode ?? 'dynamic'; @@ -391,6 +423,7 @@ export class FrigateCard extends LitElement { // dynamic mode (as the aspect_ratio is essentially whatever the media // dimensions are). if ( + (screenfull.isEnabled && screenfull.isFullscreen) || aspect_ratio_mode == 'unconstrained' || (!this._view.isGalleryView() && aspect_ratio_mode == 'dynamic' && this._mediaInfo) ) { diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 0d8dd53a..ba5bfb2a 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -41,7 +41,8 @@ "snapshots": "Snapshots Gallery", "clip": "Latest Clip", "snapshot": "Latest Snapshot", - "frigate_ui": "Frigate User Interface" + "frigate_ui": "Frigate User Interface", + "fullscreen": "Fullscreen" }, "control": { "nextprev": "Media Next & Previous Controls", @@ -93,7 +94,6 @@ "could_not_resolve": "Could not resolve media URL", "no_live_camera": "No live camera", "invalid_configuration": "Invalid configuration", - "missing_webrtc": "WebRTC component not found", - "internal": "Internal error" + "missing_webrtc": "WebRTC component not found" } } diff --git a/src/scss/card.scss b/src/scss/card.scss index 4acdb6e7..95715849 100644 --- a/src/scss/card.scss +++ b/src/scss/card.scss @@ -1,5 +1,7 @@ .container { position: relative; + overflow: auto; + height: 100%; } .frigate-card-contents { @@ -8,6 +10,11 @@ overflow: auto; -ms-overflow-style: none; /* Hide scrollbar: IE and Edge */ scrollbar-width: none; /* Hide scrollbar: Firefox */ + + // Vertically align items in case the container is larger than the media (e.g. + // fullscreen mode, or forced aspect ratios). + display: flex; + align-items: center; } /* Hide scrollbar for Chrome, Safari and Opera */ @@ -15,7 +22,7 @@ display: none; } -/* The 'hover' menu mode is styling applied outside of the menu itself */ +/* When forcing aspect ratio absolute positioning is required */ .frigate-card-contents.absolute { position: absolute; top: 0px; @@ -24,7 +31,7 @@ left: 0px; } -/* Support the 'hover' menu mode. */ +/* The 'hover' menu mode is styling applied outside of the menu itself */ .hover-menu { z-index: 1; transition: all 0.5s ease; @@ -50,4 +57,31 @@ ha-card { ha-card a { color: var(--primary-text-color, white); +} + +frigate-card-gallery, frigate-card-viewer, frigate-card-live { + width: 100%; +} +frigate-card-gallery { + height: 100%; +} + +// Browsers will reject invalid whole CSS selectors if one selector is bad, so +// need to use mixin here instead of just comma-separated selectors. +// - Related: https://stackoverflow.com/questions/16982449/why-isnt-it-possible-to-combine-vendor-specific-pseudo-elements-classes-into-on +@mixin fullscreen-ha-card { + // Make the background black, to give it the expected fullscreen feel. + background-color: black; +} +:host(:fullscreen) ha-card { + @include fullscreen-ha-card; +} +:host(:-webkit-full-screen) ha-card { + @include fullscreen-ha-card; +} +:host(:-moz-full-screen) ha-card { + @include fullscreen-ha-card; +} +:host(:-webkit-full-screen) ha-card { + @include fullscreen-ha-card; } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 3b0d22bc..ae41dd8e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -82,6 +82,7 @@ export const frigateCardConfigSchema = z.object({ clips: z.boolean().default(true), snapshots: z.boolean().default(true), frigate_ui: z.boolean().default(true), + fullscreen: z.boolean().default(false), }) .optional(), entities: z