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%;
}