From eb88c6e0e0ed453feda5470941a74894e2c56146 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 18 Sep 2022 12:28:31 -0700 Subject: [PATCH] Rename surround-thumbnails to just surround. --- src/components/live.ts | 6 +- src/components/surround-basic.ts | 77 +++++++++ src/components/surround-thumbnails.ts | 227 ------------------------- src/components/surround.ts | 230 +++++++++++++++++++++----- src/components/timeline.ts | 10 +- src/components/viewer.ts | 6 +- src/scss/surround-basic.scss | 21 +++ src/scss/surround-thumbnails.scss | 5 - src/scss/surround.scss | 20 +-- src/scss/timeline-core.scss | 2 +- 10 files changed, 302 insertions(+), 302 deletions(-) create mode 100644 src/components/surround-basic.ts delete mode 100644 src/components/surround-thumbnails.ts create mode 100644 src/scss/surround-basic.scss delete mode 100644 src/scss/surround-thumbnails.scss diff --git a/src/components/live.ts b/src/components/live.ts index 0cdec73b..21e22273 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -61,7 +61,7 @@ import { import { dispatchErrorMessageEvent } from './message.js'; import './next-prev-control.js'; import './title-control.js'; -import './surround-thumbnails'; +import './surround.js'; import '../patches/ha-camera-stream'; import { EmblaCarouselPlugins } from './carousel.js'; import { renderTask } from '../utils/task.js'; @@ -213,7 +213,7 @@ export class FrigateCardLive extends LitElement { // is received when the card is in the background). const result = html`${keyed( this._renderKey, - html` - `, + `, )}`; this._messageReceivedPostRender = false; diff --git a/src/components/surround-basic.ts b/src/components/surround-basic.ts new file mode 100644 index 00000000..e8f760a0 --- /dev/null +++ b/src/components/surround-basic.ts @@ -0,0 +1,77 @@ +import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; +import { createRef, ref, Ref } from 'lit/directives/ref.js'; +import { customElement } from 'lit/decorators.js'; + +import { FrigateCardDrawer } from './drawer.js'; + +import './drawer.js'; + +import surroundBasicStyle from '../scss/surround-basic.scss'; + +interface FrigateCardDrawerOpen { + drawer: 'left' | 'right'; +} + +@customElement('frigate-card-surround-basic') +export class FrigateCardSurroundBasic extends LitElement { + protected _refDrawerLeft: Ref = createRef(); + protected _refDrawerRight: Ref = createRef(); + protected _boundDrawerHandler = this._drawerHandler.bind(this); + + /** + * Component connected callback. + */ + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener('frigate-card:drawer:open', this._boundDrawerHandler); + this.addEventListener('frigate-card:drawer:close', this._boundDrawerHandler); + } + + /** + * Component disconnected callback. + */ + disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeEventListener('frigate-card:drawer:open', this._boundDrawerHandler); + this.removeEventListener('frigate-card:drawer:close', this._boundDrawerHandler); + } + + protected _drawerHandler(ev: Event) { + const drawer = (ev as CustomEvent).detail.drawer; + const open = ev.type.endsWith(':open'); + if (drawer === 'left' && this._refDrawerLeft.value) { + this._refDrawerLeft.value.open = open; + } else if (drawer === 'right' && this._refDrawerRight.value) { + this._refDrawerRight.value.open = open; + } + } + + /** + * Master render method. + * @returns A rendered template. + */ + protected render(): TemplateResult | void { + return html` + + + + + + + + `; + } + + /** + * Return compiled CSS styles. + */ + static get styles(): CSSResultGroup { + return unsafeCSS(surroundBasicStyle); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'frigate-card-surround-basic': FrigateCardSurroundBasic; + } +} diff --git a/src/components/surround-thumbnails.ts b/src/components/surround-thumbnails.ts deleted file mode 100644 index 281c8dfd..00000000 --- a/src/components/surround-thumbnails.ts +++ /dev/null @@ -1,227 +0,0 @@ -import './surround.js'; -import './timeline-core.js'; - -import { - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, - unsafeCSS, -} from 'lit'; -import { customElement, property } from 'lit/decorators.js'; - -import surroundThumbnailsStyle from '../scss/surround.scss'; -import { - BrowseMediaQueryParameters, - CameraConfig, - ExtendedHomeAssistant, - FrigateBrowseMediaSource, - FrigateCardError, - MiniTimelineControlConfig, - ThumbnailsControlConfig, -} from '../types.js'; -import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js'; -import { - getFirstTrueMediaChildIndex, - multipleBrowseMediaQueryMerged, -} from '../utils/ha/browse-media'; -import { TimelineDataManager } from '../utils/timeline-data-manager'; -import { View } from '../view.js'; -import { dispatchFrigateCardErrorEvent } from './message.js'; -import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; - -interface ThumbnailViewContext { - // Whetherr or not to fetch thumbnails. - fetch?: boolean; -} - -declare module 'view' { - interface ViewContext { - thumbnails?: ThumbnailViewContext; - } -} - -@customElement('frigate-card-surround-thumbnails') -export class FrigateCardSurroundThumbnails extends LitElement { - @property({ attribute: false }) - public hass?: ExtendedHomeAssistant; - - @property({ attribute: false }) - public view?: Readonly; - - @property({ attribute: false, hasChanged: contentsChanged }) - public thumbnailConfig?: ThumbnailsControlConfig; - - @property({ attribute: false, hasChanged: contentsChanged }) - public timelineConfig?: MiniTimelineControlConfig; - - @property({ attribute: false }) - public inBackground?: boolean; - - @property({ attribute: false, hasChanged: contentsChanged }) - public browseMediaParams?: BrowseMediaQueryParameters | BrowseMediaQueryParameters[]; - - @property({ attribute: false }) - public cameras?: Map; - - @property({ attribute: false }) - public timelineDataManager?: TimelineDataManager; - - /** - * Fetch thumbnail media when a target is not specified in the view (e.g. for - * the live view). - * @param param Task parameters. - * @returns - */ - protected async _fetchMedia(): Promise { - if ( - this.inBackground || - !this.hass || - !this.view || - !this.thumbnailConfig || - this.thumbnailConfig.mode === 'none' || - this.view.target || - !this.browseMediaParams || - !(this.view.context?.thumbnails?.fetch ?? true) - ) { - return; - } - let parent: FrigateBrowseMediaSource | null; - try { - parent = await multipleBrowseMediaQueryMerged(this.hass, this.browseMediaParams); - } catch (e) { - return dispatchFrigateCardErrorEvent(this, e as FrigateCardError); - } - if (getFirstTrueMediaChildIndex(parent) !== null) { - this.view - ?.evolve({ - target: parent, - childIndex: null, - - // Don't carry over history of this 'empty' view. - previous: null, - }) - .dispatchChangeEvent(this); - } - } - - /** - * Determine if a drawer is being used. - * @returns `true` if a drawer is used, `false` otherwise. - */ - protected _hasDrawer(): boolean { - return ( - !!this.thumbnailConfig && ['left', 'right'].includes(this.thumbnailConfig.mode) - ); - } - - /** - * Called before each update. - */ - protected willUpdate(changedProperties: PropertyValues): void { - // Once the component will certainly update, dispatch a media request. Only - // do so if properties relevant to the request have changed (as per their - // hasChanged). - if ( - ['view', 'fetch', 'browseMediaParams', 'inBackground'].some((prop) => - changedProperties.has(prop), - ) - ) { - this._fetchMedia(); - } - } - - /** - * Master render method. - * @returns A rendered template. - */ - protected render(): TemplateResult | void { - if (!this.hass || !this.view || !this.thumbnailConfig) { - return; - } - - const changeDrawer = (ev: CustomEvent, action: 'open' | 'close') => { - // The event catch/re-dispatch below protect encapsulation: Catches the - // request to view thumbnails and re-dispatches a request to open the drawer - // (if the thumbnails are in a drawer). The new event needs to be dispatched - // from the origin of the inbound event, so it can be handled by - // . - if (this.thumbnailConfig && this._hasDrawer()) { - dispatchFrigateCardEvent(ev.composedPath()[0], 'drawer:' + action, { - drawer: this.thumbnailConfig.mode, - }); - } - }; - - return html` changeDrawer(ev, 'open')} - @frigate-card:thumbnails:close=${(ev: CustomEvent) => changeDrawer(ev, 'close')} - > - ${this.thumbnailConfig && - this.thumbnailConfig.mode !== 'none' && - !this.inBackground - ? html` changeDrawer(ev, 'close')} - @frigate-card:thumbnail-carousel:tap=${( - ev: CustomEvent, - ) => { - const child: FrigateBrowseMediaSource | null = - ev.detail.target?.children?.[ev.detail.childIndex] ?? null; - // Send the view change from the source of the tap event, so the - // view change will be caught by the handler above (to close the drawer). - if (child) { - this.view - ?.evolve({ - view: this.view.is('recording') ? 'recording' : 'media', - target: ev.detail.target, - childIndex: ev.detail.childIndex, - context: null, - ...(child?.frigate?.cameraID && { - camera: child?.frigate?.cameraID, - }), - }) - .dispatchChangeEvent(ev.composedPath()[0]); - } - }} - > - ` - : ''} - ${this.timelineConfig && !this.inBackground - ? html` - ` - : ''} - - `; - } - - /** - * Return compiled CSS styles. - */ - static get styles(): CSSResultGroup { - return unsafeCSS(surroundThumbnailsStyle); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'frigate-card-surround-thumbnails': FrigateCardSurroundThumbnails; - } -} diff --git a/src/components/surround.ts b/src/components/surround.ts index 414058ab..21ee2038 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -1,48 +1,134 @@ -import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; -import { createRef, ref, Ref } from 'lit/directives/ref.js'; -import { customElement } from 'lit/decorators.js'; - -import { FrigateCardDrawer } from './drawer.js'; - -import './drawer.js'; +import { + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, + unsafeCSS, +} from 'lit'; +import { customElement, property } from 'lit/decorators.js'; import surroundStyle from '../scss/surround.scss'; +import { + BrowseMediaQueryParameters, + CameraConfig, + ExtendedHomeAssistant, + FrigateBrowseMediaSource, + FrigateCardError, + MiniTimelineControlConfig, + ThumbnailsControlConfig, +} from '../types.js'; +import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js'; +import { + getFirstTrueMediaChildIndex, + multipleBrowseMediaQueryMerged, +} from '../utils/ha/browse-media'; +import { TimelineDataManager } from '../utils/timeline-data-manager'; +import { View } from '../view.js'; +import { dispatchFrigateCardErrorEvent } from './message.js'; +import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; -interface FrigateCardDrawerOpen { - drawer: 'left' | 'right'; +import './surround-basic.js'; +import './timeline-core.js'; + +interface ThumbnailViewContext { + // Whetherr or not to fetch thumbnails. + fetch?: boolean; +} + +declare module 'view' { + interface ViewContext { + thumbnails?: ThumbnailViewContext; + } } @customElement('frigate-card-surround') export class FrigateCardSurround extends LitElement { - protected _refDrawerLeft: Ref = createRef(); - protected _refDrawerRight: Ref = createRef(); - protected _boundDrawerHandler = this._drawerHandler.bind(this); + @property({ attribute: false }) + public hass?: ExtendedHomeAssistant; + + @property({ attribute: false }) + public view?: Readonly; + + @property({ attribute: false, hasChanged: contentsChanged }) + public thumbnailConfig?: ThumbnailsControlConfig; + + @property({ attribute: false, hasChanged: contentsChanged }) + public timelineConfig?: MiniTimelineControlConfig; + + @property({ attribute: false }) + public inBackground?: boolean; + + @property({ attribute: false, hasChanged: contentsChanged }) + public browseMediaParams?: BrowseMediaQueryParameters | BrowseMediaQueryParameters[]; + + @property({ attribute: false }) + public cameras?: Map; + + @property({ attribute: false }) + public timelineDataManager?: TimelineDataManager; /** - * Component connected callback. + * Fetch thumbnail media when a target is not specified in the view (e.g. for + * the live view). + * @param param Task parameters. + * @returns */ - connectedCallback(): void { - super.connectedCallback(); - this.addEventListener('frigate-card:drawer:open', this._boundDrawerHandler); - this.addEventListener('frigate-card:drawer:close', this._boundDrawerHandler); + protected async _fetchMedia(): Promise { + if ( + this.inBackground || + !this.hass || + !this.view || + !this.thumbnailConfig || + this.thumbnailConfig.mode === 'none' || + this.view.target || + !this.browseMediaParams || + !(this.view.context?.thumbnails?.fetch ?? true) + ) { + return; + } + let parent: FrigateBrowseMediaSource | null; + try { + parent = await multipleBrowseMediaQueryMerged(this.hass, this.browseMediaParams); + } catch (e) { + return dispatchFrigateCardErrorEvent(this, e as FrigateCardError); + } + if (getFirstTrueMediaChildIndex(parent) !== null) { + this.view + ?.evolve({ + target: parent, + childIndex: null, + + // Don't carry over history of this 'empty' view. + previous: null, + }) + .dispatchChangeEvent(this); + } } /** - * Component disconnected callback. + * Determine if a drawer is being used. + * @returns `true` if a drawer is used, `false` otherwise. */ - disconnectedCallback(): void { - super.disconnectedCallback(); - this.removeEventListener('frigate-card:drawer:open', this._boundDrawerHandler); - this.removeEventListener('frigate-card:drawer:close', this._boundDrawerHandler); + protected _hasDrawer(): boolean { + return ( + !!this.thumbnailConfig && ['left', 'right'].includes(this.thumbnailConfig.mode) + ); } - protected _drawerHandler(ev: Event) { - const drawer = (ev as CustomEvent).detail.drawer; - const open = ev.type.endsWith(':open'); - if (drawer === 'left' && this._refDrawerLeft.value) { - this._refDrawerLeft.value.open = open; - } else if (drawer === 'right' && this._refDrawerRight.value) { - this._refDrawerRight.value.open = open; + /** + * Called before each update. + */ + protected willUpdate(changedProperties: PropertyValues): void { + // Once the component will certainly update, dispatch a media request. Only + // do so if properties relevant to the request have changed (as per their + // hasChanged). + if ( + ['view', 'fetch', 'browseMediaParams', 'inBackground'].some((prop) => + changedProperties.has(prop), + ) + ) { + this._fetchMedia(); } } @@ -51,15 +137,79 @@ export class FrigateCardSurround extends LitElement { * @returns A rendered template. */ protected render(): TemplateResult | void { - return html` + if (!this.hass || !this.view || !this.thumbnailConfig) { + return; + } + + const changeDrawer = (ev: CustomEvent, action: 'open' | 'close') => { + // The event catch/re-dispatch below protect encapsulation: Catches the + // request to view thumbnails and re-dispatches a request to open the drawer + // (if the thumbnails are in a drawer). The new event needs to be dispatched + // from the origin of the inbound event, so it can be handled by + // . + if (this.thumbnailConfig && this._hasDrawer()) { + dispatchFrigateCardEvent(ev.composedPath()[0], 'drawer:' + action, { + drawer: this.thumbnailConfig.mode, + }); + } + }; + + return html` changeDrawer(ev, 'open')} + @frigate-card:thumbnails:close=${(ev: CustomEvent) => changeDrawer(ev, 'close')} + > + ${this.thumbnailConfig && + this.thumbnailConfig.mode !== 'none' && + !this.inBackground + ? html` changeDrawer(ev, 'close')} + @frigate-card:thumbnail-carousel:tap=${( + ev: CustomEvent, + ) => { + const child: FrigateBrowseMediaSource | null = + ev.detail.target?.children?.[ev.detail.childIndex] ?? null; + // Send the view change from the source of the tap event, so the + // view change will be caught by the handler above (to close the drawer). + if (child) { + this.view + ?.evolve({ + view: this.view.is('recording') ? 'recording' : 'media', + target: ev.detail.target, + childIndex: ev.detail.childIndex, + context: null, + ...(child?.frigate?.cameraID && { + camera: child?.frigate?.cameraID, + }), + }) + .dispatchChangeEvent(ev.composedPath()[0]); + } + }} + > + ` + : ''} + ${this.timelineConfig && !this.inBackground + ? html` + ` + : ''} - - - - - - - `; + `; } /** @@ -71,7 +221,7 @@ export class FrigateCardSurround extends LitElement { } declare global { - interface HTMLElementTagNameMap { - "frigate-card-surround": FrigateCardSurround - } + interface HTMLElementTagNameMap { + 'frigate-card-surround': FrigateCardSurround; + } } diff --git a/src/components/timeline.ts b/src/components/timeline.ts index 7bb95b55..26e39591 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -1,10 +1,10 @@ // TODO: When a media viewer is first loaded the selected child won't work (because the underlying carousel has not yet rendered) -// TODO: rename surround to surround basic and this file to surround? // TODO: thumbnails in drawers don't work. // TODO: delete segments if not in summary? is this actually necessary? could it create gaps in data? better off stopping access via summary? // TODO: support filtering created dataviews by recordings or mediatype (so storage ) // TODO: dataview refresh instead of rewriteitem? // TODO: Make minitimeline configurable in the editor +// TODO: Is it really useful to select the children in the main timeline view on range change? import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; @@ -12,12 +12,12 @@ import timelineStyle from '../scss/timeline.scss'; import { CameraConfig, ExtendedHomeAssistant, TimelineConfig } from '../types'; import { TimelineDataManager } from '../utils/timeline-data-manager'; import { View } from '../view'; -import './surround-thumbnails.js'; +import './surround.js'; import './timeline-core.js'; // This file is kept separate from timeline-core.ts to avoid a circular dependency: // FrigateCardTimeline -> -// FrigateCardSurroundThumbnails -> +// FrigateCardSurround -> // FrigateCardTimelineCore @customElement('frigate-card-timeline') @@ -46,7 +46,7 @@ export class FrigateCardTimeline extends LitElement { return html``; } - return html` - `; + `; } /** diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 701dd89e..8283f680 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -51,7 +51,7 @@ import { import './next-prev-control.js'; import './title-control.js'; import '../patches/ha-hls-player'; -import './surround-thumbnails'; +import './surround.js'; import { EmblaCarouselPlugins } from './carousel.js'; import { renderTask } from '../utils/task.js'; import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js'; @@ -134,7 +134,7 @@ export class FrigateCardViewer extends LitElement { return renderProgressIndicator(); } - return html` - `; + `; } /** diff --git a/src/scss/surround-basic.scss b/src/scss/surround-basic.scss new file mode 100644 index 00000000..d6ff998a --- /dev/null +++ b/src/scss/surround-basic.scss @@ -0,0 +1,21 @@ +:host { + width: 100%; + height: 100%; + + // Share the screen space with thumbnails that may be above/below. + display: flex; + flex-direction: column; + + // Set the drawer relative to this host. + position: relative; + + // Hide any content outside the main pane (e.g. side drawers) to ensure the + // user cannot scroll across to the drawers without opening them. + overflow: hidden; +} + +::slotted:not([name]) { + // Expand the main body to fill available content not otherwise used by the + // surround. + flex: 1; +} diff --git a/src/scss/surround-thumbnails.scss b/src/scss/surround-thumbnails.scss deleted file mode 100644 index f0f64c07..00000000 --- a/src/scss/surround-thumbnails.scss +++ /dev/null @@ -1,5 +0,0 @@ -:host { - width: 100%; - height: 100%; - display: block; -} \ No newline at end of file diff --git a/src/scss/surround.scss b/src/scss/surround.scss index d6ff998a..f0f64c07 100644 --- a/src/scss/surround.scss +++ b/src/scss/surround.scss @@ -1,21 +1,5 @@ :host { width: 100%; height: 100%; - - // Share the screen space with thumbnails that may be above/below. - display: flex; - flex-direction: column; - - // Set the drawer relative to this host. - position: relative; - - // Hide any content outside the main pane (e.g. side drawers) to ensure the - // user cannot scroll across to the drawers without opening them. - overflow: hidden; -} - -::slotted:not([name]) { - // Expand the main body to fill available content not otherwise used by the - // surround. - flex: 1; -} + display: block; +} \ No newline at end of file diff --git a/src/scss/timeline-core.scss b/src/scss/timeline-core.scss index d755ca05..a6851b2a 100644 --- a/src/scss/timeline-core.scss +++ b/src/scss/timeline-core.scss @@ -60,7 +60,7 @@ div.timeline { // ensure the background (recordings) always span the full height. Otherwise, in // cases where there are no events, the background is incorrectly rendered too // short by visjs. -:host:not([groups]) .vis-item.vis-background { +:host(:not([groups])) .vis-item.vis-background { min-height: 100%; }