From ae1ab1ffe807e351c07fba1d82976890fcef7f2e Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sat, 21 Aug 2021 21:35:59 -0700 Subject: [PATCH 01/10] New menu modes and significant CSS cleanup. --- src/frigate-hass-card-menu.scss | 40 ++++----- src/frigate-hass-card.scss | 61 +++++--------- src/frigate-hass-card.ts | 141 +++++++++++++++++++------------- src/types.ts | 10 +++ 4 files changed, 133 insertions(+), 119 deletions(-) diff --git a/src/frigate-hass-card-menu.scss b/src/frigate-hass-card-menu.scss index c5bc5d2a..19c41a6d 100644 --- a/src/frigate-hass-card-menu.scss +++ b/src/frigate-hass-card-menu.scss @@ -1,38 +1,34 @@ -.frigate-card-menu-container { - position: absolute; - top: 0px; - z-index: 1; - width: 100%; -} - -.frigate-card-menu { - position: absolute; - top: 0px; +/* Base: Not used directly */ +.frigate-card-menu-base { z-index: 1; height: 56px; +} + +/* Small 'F' button for hidden menu */ +.frigate-card-menu-hidden { + @extend .frigate-card-menu-base; + position: absolute; width: 50px; } -.frigate-card-menu-expanded { - @extend .frigate-card-menu; +/* Expanded overlay menu */ +.frigate-card-menu-overlay { + @extend .frigate-card-menu-hidden; width: 100%; - background-color: rgba(0, 0, 0, 0.6); + background: linear-gradient(90deg, rgba(0,0,0,0.3), rgba(0,0,0,0)) } -.frigate-card-menu-title { - @extend .frigate-card-menu-expanded; - top: 56px; - height: 1em; - padding-bottom: 0.5em; - padding-left: 0.5em; - color: rgba(255, 255, 255, 0.7); - background-color: rgba(0, 0, 0, 0.5); +/* Full above/below menu */ +.frigate-card-menu-full { + @extend .frigate-card-menu-base; + width: 100%; + background: var(--secondary-background-color); } ha-icon-button.button { position: relative; z-index: 10; - color: white; + color: var(--secondary-color, white); opacity: 0.8; background-color: rgba(0, 0, 0, 0.6); border-radius: 50%; diff --git a/src/frigate-hass-card.scss b/src/frigate-hass-card.scss index cd3fba56..035ee9e7 100644 --- a/src/frigate-hass-card.scss +++ b/src/frigate-hass-card.scss @@ -1,23 +1,24 @@ @use "@material/image-list/mdc-image-list"; @use "@material/image-list"; +.frigate-card-contents { + width: 100%; +} + .frigate-card-viewer { width: 100%; - height: 100%; - position: absolute; } .frigate-card-gallery { overflow: auto; - position: absolute; + aspect-ratio: 16 / 9; + -ms-overflow-style: none; /* Hide scrollbar: IE and Edge */ + scrollbar-width: none; /* Hide scrollbar: Firefox */ +} - left: 0px; - right: 0px; - top: 0px; - height: 100%; - - -ms-overflow-style: none; // Hide scrollbar: IE and Edge - scrollbar-width: none; // Hide scrollbar: Firefox +/* Hide scrollbar for Chrome, Safari and Opera */ +.frigate-card-gallery::-webkit-scrollbar { + display: none; } .frigate-card-attention { @@ -27,39 +28,13 @@ height: 100%; } -/* Hide scrollbar for Chrome, Safari and Opera */ -.frigate-card-gallery::-webkit-scrollbar { - display: none; -} - .frigate-card-image-list { - @include image-list.standard-columns(5); + @include image-list.standard-columns(5, 0px); @include image-list.shape-radius(5px); } -.frigate-card-image-list-icon-overlay { - position: absolute; - margin: auto; -} - -.frigate-card-image-list-highlight { - // Put border inside. - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - - opacity: 0.5; - - border-style: dashed; - border-width: 1px; - border-color: var(--primary-color); -} - -.invisible { - visibility: hidden; -} -.visibile { - visibility: visible; +video, img { + display: block; } ha-card { @@ -70,4 +45,12 @@ ha-card { width: 100%; height: 100%; position: relative; + background-color: var(--secondary-background-color, black); } + +/* Don't drop shadow or have radius for nested cards: webrtc */ +ha-card ha-card { + box-shadow: none; + border-radius: 0px; + background-color: var(--secondary-background-color, black); +} \ No newline at end of file diff --git a/src/frigate-hass-card.ts b/src/frigate-hass-card.ts index c3ef01af..98f700bd 100644 --- a/src/frigate-hass-card.ts +++ b/src/frigate-hass-card.ts @@ -1,3 +1,6 @@ +// TODO Put heading back +// TODO Verify getCardSize() + /* eslint-disable @typescript-eslint/no-explicit-any */ import { LitElement, @@ -26,7 +29,7 @@ import './editor'; import frigate_card_style from './frigate-hass-card.scss'; import frigate_card_menu_style from './frigate-hass-card-menu.scss'; -import { frigateCardConfigSchema, frigateGetEventsResponseSchema } from './types'; +import { frigateCardConfigSchema, frigateGetEventsResponseSchema, FrigateMenuMode } from './types'; import type { FrigateCardView, FrigateCardConfig, @@ -95,6 +98,9 @@ function shouldUpdateBasedOnHass( export class FrigateCardMenu extends LitElement { static FRIGATE_CARD_MENU_ID: string = 'frigate-card-menu-id' as const; + @property({ attribute: false }) + protected menuMode: FrigateMenuMode = "hidden"; + @property({ attribute: false }) protected expand = false; @@ -107,9 +113,6 @@ export class FrigateCardMenu extends LitElement { @property({ attribute: false }) protected actionCallback: FrigateCardMenuCallback | null = null; - @property({ attribute: false }) - protected heading: string | null = null; - protected shouldUpdate(changedProps: PropertyValues): boolean { const oldHass = changedProps.get('hass') as HomeAssistant | undefined; if (oldHass) { @@ -122,11 +125,15 @@ export class FrigateCardMenu extends LitElement { protected _renderFrigateButton(): TemplateResult { return html` { - this.expand = !this.expand; + if (this.menuMode == 'hidden') { + this.expand = !this.expand; + } else { + this._callAction('default'); + } }} >`; } @@ -140,10 +147,6 @@ export class FrigateCardMenu extends LitElement { // Render the menu. protected render(): TemplateResult | void | ((part: NodePart) => Promise) { - if (!this.expand) { - return html`
${this._renderFrigateButton()}
`; - } - let motionIcon: string | null = null; if (this.motionEntity && this.hass) { motionIcon = @@ -152,10 +155,19 @@ export class FrigateCardMenu extends LitElement { : 'mdi:walk'; } + let menuClass = "frigate-card-menu-full"; + if (["hidden", "overlay"].includes(this.menuMode)) { + if (this.menuMode == 'overlay' || this.expand) { + menuClass = "frigate-card-menu-overlay"; + } else { + menuClass = "frigate-card-menu-hidden"; + } + } + return html` -
-
- ${this._renderFrigateButton()} +
+ ${this._renderFrigateButton()} + ${this.menuMode != "hidden" || this.expand ? html` `} -
- ${this.heading - ? html`
${this.heading}
` - : ``} + ` : ``}
`; } @@ -534,6 +543,12 @@ export class FrigateCard extends LitElement { protected _menuActionHandler(name: string): void { switch (name) { + case 'default': + this._controlVideos({ stop: true, control_clip: true }); + this._controlVideos({ stop: true, control_live: true }); + this._heading = null; + this._setViewModeToDefault(); + break; case 'live': this._controlVideos({ stop: true, control_clip: true }); this._controlVideos({ stop: false, control_live: true }); @@ -679,16 +694,13 @@ export class FrigateCard extends LitElement { return this._renderAttentionIcon('mdi:camera-off', 'No live camera'); } if (this._webrtcElement) { - return html`
- ${this._webrtcElement} -
`; + return html`${this._webrtcElement}`; } return html` `; } @@ -707,6 +719,18 @@ export class FrigateCard extends LitElement { }, this.config.view_timeout * 1000); } + protected _renderMenu(): TemplateResult | void { + return html` + + `; + } + // Render the call (master render method). protected render(): TemplateResult | void { if (this.config.show_warning) { @@ -715,46 +739,47 @@ export class FrigateCard extends LitElement { if (this.config.show_error) { return this._showError(localize('common.show_error')); } - // Note: Menu is rendered last to allow heading to be set by render methods. return html` - - ${ - this._viewMode == 'clips' - ? html` @@ -546,27 +555,23 @@ export class FrigateCard extends LitElement { case 'default': this._controlVideos({ stop: true, control_clip: true }); this._controlVideos({ stop: true, control_live: true }); - this._heading = null; - this._setViewModeToDefault(); + this._changeView(); break; case 'live': this._controlVideos({ stop: true, control_clip: true }); this._controlVideos({ stop: false, control_live: true }); - this._viewMode = name; - this._heading = null; + this._changeView(name); break; case 'clips': this._controlVideos({ stop: true, control_live: true }); - this._viewMode = name; - this._heading = null; + this._changeView(name); break; case 'snapshots': this._controlVideos({ stop: true, control_clip: true, control_live: true }); - this._viewMode = name; - this._heading = null; + this._changeView(name); break; case 'frigate-ui': - window.open(this.config.frigate_url); + window.open(this._getFrigateURLFromContext()); break; case 'motion': if (this.config.motion_entity) { @@ -578,6 +583,13 @@ export class FrigateCard extends LitElement { } } + protected _getFrigateURLFromContext(): string { + if (this._eventBeingShown) { + return `${this.config.frigate_url}/events/${this._eventBeingShown.id}`; + } + return `${this.config.frigate_url}/cameras/${this.config.frigate_camera_name}`; + } + protected _getClipURLFromEvent(event: FrigateEvent): string | null { if (!event.has_clip) { return null; @@ -596,8 +608,8 @@ export class FrigateCard extends LitElement { protected async _renderClipPlayer(): Promise { let event: FrigateEvent, events: FrigateGetEventsResponse; let autoplay = true; - if (this._viewEvent) { - event = this._viewEvent; + if (this._requestedEvent) { + event = this._requestedEvent; } else { try { events = await this._getEvents({ @@ -627,29 +639,30 @@ export class FrigateCard extends LitElement { return this._renderAttentionIcon('mdi:camera-off', 'No recent clip'); } - this._heading = this._getEventTitle(event); + this._eventBeingShown = event; return html` `; + class="frigate-card-viewer" + muted + controls + @play=${() => { + this._clipPlaying = true; + }} + @pause=${() => { + this._clipPlaying = false; + }} + ?autoplay="${autoplay}" + > + + + `; } // Render a snapshot. protected async _renderSnapshotViewer(): Promise { let event: FrigateEvent, events: FrigateGetEventsResponse; - if (this._viewEvent) { - event = this._viewEvent; + if (this._requestedEvent) { + event = this._requestedEvent; } else { try { events = await this._getEvents({ @@ -672,15 +685,14 @@ export class FrigateCard extends LitElement { return this._renderAttentionIcon('mdi:filmstrip-off', 'No recent snapshots'); } - this._heading = this._getEventTitle(event); + this._eventBeingShown = event; return html` { if (event.has_clip) { - this._viewEvent = event; - this._viewMode = 'clip'; + this._changeView('clip', event); } }} />`; @@ -715,7 +727,7 @@ export class FrigateCard extends LitElement { } this._interactionTimerID = window.setTimeout(() => { this._interactionTimerID = null; - this._setViewModeToDefault(); + this._changeView(); }, this.config.view_timeout * 1000); } From 8555ec28d1cfdf3f81466288308bf59b6d19ec23 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 22 Aug 2021 08:04:09 -0700 Subject: [PATCH 03/10] Menu mode editing. --- src/editor.ts | 28 +++++++++++++++++++++++++++- src/frigate-hass-card.ts | 4 ---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/editor.ts b/src/editor.ts index 22e53907..5c617388 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -103,6 +103,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor snapshot: 'Latest snapshot', }; + const menuModes = { + '': '', + hidden: 'Hidden', + overlay: 'Overlay', + above: 'Above', + below: 'Below', + }; + const liveProvider = { '': '', frigate: 'Frigate', @@ -195,6 +203,24 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor })} + + + ${Object.keys(menuModes).map((key) => { + return html` + ${menuModes[key]} + `; + })} + + - + Date: Sun, 22 Aug 2021 08:05:41 -0700 Subject: [PATCH 04/10] Style fixes. --- src/frigate-hass-card.ts | 205 +++++++++++++++++++-------------------- 1 file changed, 101 insertions(+), 104 deletions(-) diff --git a/src/frigate-hass-card.ts b/src/frigate-hass-card.ts index 6d27f222..140048e9 100644 --- a/src/frigate-hass-card.ts +++ b/src/frigate-hass-card.ts @@ -26,7 +26,11 @@ import './editor'; import frigate_card_style from './frigate-hass-card.scss'; import frigate_card_menu_style from './frigate-hass-card-menu.scss'; -import { frigateCardConfigSchema, frigateGetEventsResponseSchema, FrigateMenuMode } from './types'; +import { + frigateCardConfigSchema, + frigateGetEventsResponseSchema, + FrigateMenuMode, +} from './types'; import type { FrigateCardView, FrigateCardConfig, @@ -96,7 +100,7 @@ export class FrigateCardMenu extends LitElement { static FRIGATE_CARD_MENU_ID: string = 'frigate-card-menu-id' as const; @property({ attribute: false }) - protected menuMode: FrigateMenuMode = "hidden"; + protected menuMode: FrigateMenuMode = 'hidden'; @property({ attribute: false }) protected expand = false; @@ -122,7 +126,9 @@ export class FrigateCardMenu extends LitElement { protected _renderFrigateButton(): TemplateResult { return html` { @@ -152,72 +158,74 @@ export class FrigateCardMenu extends LitElement { : 'mdi:walk'; } - let menuClass = "frigate-card-menu-full"; - if (["hidden", "overlay"].includes(this.menuMode)) { + let menuClass = 'frigate-card-menu-full'; + if (['hidden', 'overlay'].includes(this.menuMode)) { if (this.menuMode == 'overlay' || this.expand) { - menuClass = "frigate-card-menu-overlay"; + menuClass = 'frigate-card-menu-overlay'; } else { - menuClass = "frigate-card-menu-hidden"; + menuClass = 'frigate-card-menu-hidden'; } } return html`
${this._renderFrigateButton()} - ${this.menuMode != "hidden" || this.expand ? html` - { - this.expand = false; - this._callAction('live'); - }} - > - { - this.expand = false; - this._callAction('clips'); - }} - > - { - this.expand = false; - this._callAction('snapshots'); - }} - > - { - this.expand = false; - this._callAction('frigate-ui'); - }} - > - ${!motionIcon - ? html`` - : html` { this.expand = false; - this._callAction('motion'); + this._callAction('live'); }} - >`} - ` : ``} + > + { + this.expand = false; + this._callAction('clips'); + }} + > + { + this.expand = false; + this._callAction('snapshots'); + }} + > + { + this.expand = false; + this._callAction('frigate-ui'); + }} + > + ${!motionIcon + ? html`` + : html` { + this.expand = false; + this._callAction('motion'); + }} + >`} + ` + : ``}
`; } @@ -327,8 +335,9 @@ export class FrigateCard extends LitElement { } protected _changeView( - view?: FrigateCardView | undefined, - event?: FrigateEvent | undefined): void { + view?: FrigateCardView | undefined, + event?: FrigateEvent | undefined, + ): void { if (view !== undefined) { this._viewMode = view; } else { @@ -476,7 +485,7 @@ export class FrigateCard extends LitElement { class="mdc-image-list__image" src="data:image/png;base64,${event.thumbnail}" @click=${() => { - this._changeView(want_clips ? 'clip' : 'snapshot' , event); + this._changeView(want_clips ? 'clip' : 'snapshot', event); }} />
@@ -552,7 +561,7 @@ export class FrigateCard extends LitElement { this._controlVideos({ stop: true, control_clip: true }); this._controlVideos({ stop: true, control_live: true }); this._changeView(); - break; + break; case 'live': this._controlVideos({ stop: true, control_clip: true }); this._controlVideos({ stop: false, control_live: true }); @@ -637,7 +646,8 @@ export class FrigateCard extends LitElement { this._eventBeingShown = event; - return html`