feat: Add support for Frigate reviews / detections [initial PR] (#2315)
- Add support for Frigate reviews / detections. - Add support for GenAI metadata. - Significant internal refactor to more flexible "UnifiedQuery" to allow mixing cameras with simple metadata and review metadata (e.g. a timeline view of a Frigate camera with reviews, and a Reolink camera with simple metadata). - Add support for folder media as camera media. There are a few more PRs to commit prior to this going live, but commiting this for now due to the scale of the change. BREAKING CHANGE: `media_type` and `events_type` are retired under `live`, `viewer` and `timeline` configuration sections, instead media type is associated (once) with the camera under `media`.
This commit is contained in:
@@ -52,6 +52,9 @@ export class AdvancedCameraCardCarousel extends LitElement {
|
||||
protected _refRoot: Ref<HTMLElement> = createRef();
|
||||
protected _carousel: CarouselController | null = null;
|
||||
|
||||
// Track slide count to distinguish user-navigation from content changes.
|
||||
protected _previousSlideCount: number | null = null;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
@@ -100,6 +103,8 @@ export class AdvancedCameraCardCarousel extends LitElement {
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
const currentSlideCount = this._refParent.value?.assignedElements().length ?? 0;
|
||||
|
||||
if (
|
||||
!this._carousel &&
|
||||
this._refRoot.value &&
|
||||
@@ -127,9 +132,21 @@ export class AdvancedCameraCardCarousel extends LitElement {
|
||||
wheelScrolling: this.wheelScrolling,
|
||||
},
|
||||
);
|
||||
} else if (changedProps.has('selected')) {
|
||||
this._carousel?.selectSlide(this.selected);
|
||||
this._previousSlideCount = currentSlideCount;
|
||||
} else if (changedProps.has('selected') && this._carousel) {
|
||||
// Only scroll to selection if the slide count hasn't changed.
|
||||
// This distinguishes between:
|
||||
// - Navigation (next/prev): count same → scroll to selection
|
||||
// - Content change (item removed): count changed → preserve position
|
||||
const contentChanged =
|
||||
this._previousSlideCount !== null &&
|
||||
this._previousSlideCount !== currentSlideCount;
|
||||
|
||||
if (!contentChanged && this.selected !== this._carousel.getSelectedIndex()) {
|
||||
this._carousel.selectSlide(this.selected);
|
||||
}
|
||||
}
|
||||
this._previousSlideCount = currentSlideCount;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { FoldersManager } from '../../card-controller/folders/manager.js';
|
||||
import { ViewItemManager } from '../../card-controller/view/item-manager.js';
|
||||
import { ViewManagerEpoch } from '../../card-controller/view/types.js';
|
||||
import {
|
||||
getUpFolderMediaItem,
|
||||
upFolderClickHandler,
|
||||
} from '../../components-lib/folder/up-folder.js';
|
||||
import { FolderGalleryController } from '../../components-lib/gallery/folder-gallery-controller.js';
|
||||
import { MediaGalleryConfig } from '../../config/schema/media-gallery.js';
|
||||
import { HomeAssistant } from '../../ha/types.js';
|
||||
import { localize } from '../../localize/localize';
|
||||
import folderGalleryStyle from '../../scss/folder-gallery.scss';
|
||||
import { ViewItem } from '../../view/item.js';
|
||||
import '../media-filter';
|
||||
import '../message.js';
|
||||
import { renderMessage } from '../message.js';
|
||||
import '../surround-basic';
|
||||
import '../thumbnail/thumbnail.js';
|
||||
import './gallery-core.js';
|
||||
|
||||
@customElement('advanced-camera-card-folder-gallery')
|
||||
export class AdvancedCameraCardFolderGallery extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewManagerEpoch?: ViewManagerEpoch;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewItemManager?: ViewItemManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public galleryConfig?: MediaGalleryConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public foldersManager?: FoldersManager;
|
||||
|
||||
protected _controller = new FolderGalleryController(this);
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has('galleryConfig')) {
|
||||
this._controller.setThumbnailSize(this.galleryConfig?.controls.thumbnails.size);
|
||||
}
|
||||
}
|
||||
|
||||
protected _renderThumbnail(
|
||||
item: ViewItem,
|
||||
selected: boolean,
|
||||
clickCallback: (item: ViewItem, ev: Event) => void,
|
||||
): TemplateResult | void {
|
||||
return html`<advanced-camera-card-thumbnail
|
||||
class=${classMap({
|
||||
selected,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.item=${item}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
?selected=${selected}
|
||||
?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
|
||||
?show_favorite_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_favorite_control}
|
||||
?show_timeline_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_timeline_control}
|
||||
?show_download_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_download_control}
|
||||
@click=${(ev: Event) => clickCallback(item, ev)}
|
||||
>
|
||||
</advanced-camera-card-thumbnail>`;
|
||||
}
|
||||
|
||||
protected _renderThumbnails(): TemplateResult | void {
|
||||
const selected = this.viewManagerEpoch?.manager
|
||||
.getView()
|
||||
?.queryResults?.getSelectedResult();
|
||||
|
||||
return html`
|
||||
${this.viewManagerEpoch?.manager
|
||||
.getView()
|
||||
?.queryResults?.getResults()
|
||||
?.map((item) =>
|
||||
this._renderThumbnail(item, item === selected, (item: ViewItem, ev: Event) => {
|
||||
const manager = this.viewManagerEpoch?.manager;
|
||||
if (manager) {
|
||||
this._controller.itemClickHandler(manager, item, ev, this.foldersManager);
|
||||
}
|
||||
}),
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const folderIsLoading =
|
||||
!!this.viewManagerEpoch?.manager.getView()?.context?.loading?.query;
|
||||
const upThumbnail = getUpFolderMediaItem(this.viewManagerEpoch?.manager.getView());
|
||||
|
||||
return html`
|
||||
<advanced-camera-card-surround-basic>
|
||||
${!this.viewManagerEpoch?.manager.getView()?.queryResults?.hasResults() &&
|
||||
(folderIsLoading || !upThumbnail)
|
||||
? renderMessage({
|
||||
type: 'info',
|
||||
message: folderIsLoading
|
||||
? localize('error.awaiting_folder')
|
||||
: localize('common.no_folder'),
|
||||
icon: 'mdi:folder-play',
|
||||
dotdotdot: folderIsLoading,
|
||||
})
|
||||
: html`<advanced-camera-card-gallery-core
|
||||
.hass=${this.hass}
|
||||
.columnWidth=${this._controller.getColumnWidth(
|
||||
this.galleryConfig?.controls.thumbnails,
|
||||
)}
|
||||
.columnCountRoundMethod=${this._controller.getColumnCountRoundMethod(
|
||||
this.galleryConfig?.controls.thumbnails,
|
||||
)}
|
||||
>
|
||||
${upThumbnail
|
||||
? this._renderThumbnail(
|
||||
upThumbnail,
|
||||
false,
|
||||
(item: ViewItem, ev: Event) =>
|
||||
upFolderClickHandler(item, ev, this.viewManagerEpoch),
|
||||
)
|
||||
: ''}
|
||||
${this._renderThumbnails()}
|
||||
</advanced-camera-card-gallery-core>`}
|
||||
</advanced-camera-card-surround-basic>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(folderGalleryStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'advanced-camera-card-folder-gallery': AdvancedCameraCardFolderGallery;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { CameraManager } from '../../camera-manager/manager.js';
|
||||
import { FoldersManager } from '../../card-controller/folders/manager.js';
|
||||
import { ViewItemManager } from '../../card-controller/view/item-manager.js';
|
||||
import { MergeContextViewModifier } from '../../card-controller/view/modifiers/merge-context.js';
|
||||
import { RemoveContextViewModifier } from '../../card-controller/view/modifiers/remove-context.js';
|
||||
import { ViewManagerEpoch } from '../../card-controller/view/types.js';
|
||||
import { GalleryController } from '../../components-lib/gallery/controller.js';
|
||||
import {
|
||||
FolderNavigationParamaters,
|
||||
getUpFolderItem,
|
||||
navigateToFolder,
|
||||
navigateToMedia,
|
||||
navigateUp,
|
||||
} from '../../components-lib/navigation.js';
|
||||
import { ConditionStateManagerReadonlyInterface } from '../../conditions/types.js';
|
||||
import { MediaGalleryConfig } from '../../config/schema/media-gallery.js';
|
||||
import { CardWideConfig } from '../../config/schema/types.js';
|
||||
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../../const.js';
|
||||
import { HomeAssistant } from '../../ha/types.js';
|
||||
import { localize } from '../../localize/localize.js';
|
||||
import galleryStyle from '../../scss/media-gallery.scss';
|
||||
import { stopEventFromActivatingCardWideActions } from '../../utils/action.js';
|
||||
import { ViewItemClassifier } from '../../view/item-classifier.js';
|
||||
import { ViewFolder, ViewItem } from '../../view/item.js';
|
||||
import { UnifiedQueryBuilder } from '../../view/unified-query-builder.js';
|
||||
import { UnifiedQueryRunner } from '../../view/unified-query-runner.js';
|
||||
import '../media-filter.js';
|
||||
import '../message.js';
|
||||
import { renderMessage } from '../message.js';
|
||||
import '../surround-basic.js';
|
||||
import '../thumbnail/thumbnail.js';
|
||||
import './gallery-core.js';
|
||||
import { GalleryExtendEvent } from './types.js';
|
||||
|
||||
const GALLERY_FILTER_MENU_ICONS = {
|
||||
closed: 'mdi:filter-cog-outline',
|
||||
open: 'mdi:filter-cog',
|
||||
};
|
||||
|
||||
@customElement('advanced-camera-card-gallery')
|
||||
export class AdvancedCameraCardGallery extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewManagerEpoch?: ViewManagerEpoch;
|
||||
|
||||
@property({ attribute: false })
|
||||
public galleryConfig?: MediaGalleryConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public foldersManager?: FoldersManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewItemManager?: ViewItemManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cardWideConfig?: CardWideConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public conditionStateManager?: ConditionStateManagerReadonlyInterface;
|
||||
|
||||
protected _controller = new GalleryController(this);
|
||||
protected _upFolderItem: ViewFolder | null = null;
|
||||
protected _builder: UnifiedQueryBuilder | null = null;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (
|
||||
(changedProps.has('cameraManager') || changedProps.has('foldersManager')) &&
|
||||
this.cameraManager &&
|
||||
this.foldersManager
|
||||
) {
|
||||
this._builder = new UnifiedQueryBuilder(this.cameraManager, this.foldersManager);
|
||||
}
|
||||
|
||||
if (changedProps.has('viewManagerEpoch')) {
|
||||
const view = this.viewManagerEpoch?.manager.getView();
|
||||
this._controller.setItemsFromView(view, this.viewManagerEpoch?.oldView);
|
||||
this._upFolderItem = getUpFolderItem(view?.query);
|
||||
}
|
||||
|
||||
if (changedProps.has('galleryConfig')) {
|
||||
this._controller.setThumbnailSize(this.galleryConfig?.controls.thumbnails.size);
|
||||
}
|
||||
}
|
||||
|
||||
protected _getLimit(): number {
|
||||
return (
|
||||
this.cardWideConfig?.performance?.features?.media_chunk_size ??
|
||||
MEDIA_CHUNK_SIZE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
protected _getFolderNavigationParameters(): FolderNavigationParamaters | null {
|
||||
return this._builder && this.viewManagerEpoch
|
||||
? {
|
||||
builder: this._builder,
|
||||
viewManagerEpoch: this.viewManagerEpoch,
|
||||
limit: this._getLimit(),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
protected _renderUpFolder(): TemplateResult | void {
|
||||
if (!this._upFolderItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
return html`<advanced-camera-card-thumbnail
|
||||
.hass=${this.hass}
|
||||
.item=${this._upFolderItem}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
|
||||
@click=${(ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
navigateUp(this._getFolderNavigationParameters());
|
||||
}}
|
||||
>
|
||||
</advanced-camera-card-thumbnail>`;
|
||||
}
|
||||
|
||||
protected _renderThumbnails(): TemplateResult | void {
|
||||
const selected = this.viewManagerEpoch?.manager
|
||||
.getView()
|
||||
?.queryResults?.getSelectedResult();
|
||||
|
||||
return html`
|
||||
${this._controller.getItems()?.map(
|
||||
(item) =>
|
||||
html`<advanced-camera-card-thumbnail
|
||||
class=${classMap({
|
||||
selected: item === selected,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
.item=${item}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
?selected=${item === selected}
|
||||
?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
|
||||
?show_favorite_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_favorite_control}
|
||||
?show_timeline_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_timeline_control}
|
||||
?show_download_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_download_control}
|
||||
?show_review_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_review_control}
|
||||
?show_info_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_info_control}
|
||||
@click=${(ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (ViewItemClassifier.isMedia(item) && this.viewManagerEpoch) {
|
||||
navigateToMedia(item, {
|
||||
viewManagerEpoch: this.viewManagerEpoch,
|
||||
modifiers: [
|
||||
new RemoveContextViewModifier(['timeline', 'mediaViewer']),
|
||||
new MergeContextViewModifier({
|
||||
gallery: {
|
||||
originView: this.viewManagerEpoch.manager.getView()?.view,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
} else if (ViewItemClassifier.isFolder(item)) {
|
||||
navigateToFolder(item, this._getFolderNavigationParameters());
|
||||
}
|
||||
}}
|
||||
>
|
||||
</advanced-camera-card-thumbnail>`,
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const isLoading =
|
||||
!!this.viewManagerEpoch?.manager.getView()?.context?.loading?.query;
|
||||
|
||||
const hasItems =
|
||||
(this._controller.getItems()?.length ?? 0) > 0 || !!this._upFolderItem;
|
||||
|
||||
const showFilter =
|
||||
this.galleryConfig && this.galleryConfig.controls.filter.mode !== 'none';
|
||||
|
||||
return html`
|
||||
<advanced-camera-card-surround-basic
|
||||
.drawerIcons=${{
|
||||
...(showFilter && {
|
||||
[this.galleryConfig!.controls.filter.mode]: GALLERY_FILTER_MENU_ICONS,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
${showFilter && this.galleryConfig
|
||||
? html` <advanced-camera-card-media-filter
|
||||
.hass=${this.hass}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.foldersManager=${this.foldersManager}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
slot=${this.galleryConfig.controls.filter.mode}
|
||||
>
|
||||
</advanced-camera-card-media-filter>`
|
||||
: ''}
|
||||
${!hasItems
|
||||
? renderMessage({
|
||||
type: 'info',
|
||||
message: isLoading
|
||||
? localize('error.awaiting_media')
|
||||
: localize('common.no_media'),
|
||||
icon: 'mdi:multimedia',
|
||||
dotdotdot: isLoading,
|
||||
})
|
||||
: html`<advanced-camera-card-gallery-core
|
||||
.hass=${this.hass}
|
||||
.columnWidth=${this._controller.getColumnWidth(
|
||||
this.galleryConfig?.controls.thumbnails,
|
||||
)}
|
||||
.columnCountRoundMethod=${this._controller.getColumnCountRoundMethod(
|
||||
this.galleryConfig?.controls.thumbnails,
|
||||
)}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.extendUp=${true}
|
||||
.extendDown=${true}
|
||||
@advanced-camera-card:gallery:extend:up=${(
|
||||
ev: CustomEvent<GalleryExtendEvent>,
|
||||
) =>
|
||||
this._extendGallery(
|
||||
ev,
|
||||
'later',
|
||||
// Avoid use of cache since the user is explicitly looking for
|
||||
// the freshest possible data.
|
||||
false,
|
||||
)}
|
||||
@advanced-camera-card:gallery:extend:down=${(
|
||||
ev: CustomEvent<GalleryExtendEvent>,
|
||||
) => this._extendGallery(ev, 'earlier')}
|
||||
>
|
||||
${this._renderUpFolder()} ${this._renderThumbnails()}
|
||||
</advanced-camera-card-gallery-core>`}
|
||||
</advanced-camera-card-surround-basic>
|
||||
`;
|
||||
}
|
||||
|
||||
protected async _extendGallery(
|
||||
ev: CustomEvent<GalleryExtendEvent>,
|
||||
direction: 'earlier' | 'later',
|
||||
useCache = true,
|
||||
): Promise<void> {
|
||||
if (
|
||||
!this.cameraManager ||
|
||||
!this.foldersManager ||
|
||||
!this.viewManagerEpoch ||
|
||||
!this.conditionStateManager
|
||||
) {
|
||||
ev.detail.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const runner = new UnifiedQueryRunner(
|
||||
this.cameraManager,
|
||||
this.foldersManager,
|
||||
this.conditionStateManager,
|
||||
);
|
||||
|
||||
await this._controller.extend(runner, this.viewManagerEpoch, direction, useCache);
|
||||
ev.detail.resolve();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(galleryStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'advanced-camera-card-gallery': AdvancedCameraCardGallery;
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
'advanced-camera-card:media:reviewed': CustomEvent<ViewItem>;
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { CameraManager } from '../../camera-manager/manager.js';
|
||||
import { ViewItemManager } from '../../card-controller/view/item-manager.js';
|
||||
import { ViewManagerEpoch } from '../../card-controller/view/types.js';
|
||||
import { MediaGalleryController } from '../../components-lib/gallery/media-gallery-controller.js';
|
||||
import { MediaGalleryConfig } from '../../config/schema/media-gallery.js';
|
||||
import { CardWideConfig } from '../../config/schema/types.js';
|
||||
import { HomeAssistant } from '../../ha/types.js';
|
||||
import { localize } from '../../localize/localize';
|
||||
import mediaGalleryStyle from '../../scss/media-gallery.scss';
|
||||
import '../media-filter';
|
||||
import '../message.js';
|
||||
import { renderMessage } from '../message.js';
|
||||
import '../surround-basic';
|
||||
import '../thumbnail/thumbnail.js';
|
||||
import './gallery-core.js';
|
||||
import { GalleryExtendEvent } from './types.js';
|
||||
|
||||
const MEDIA_GALLERY_FILTER_MENU_ICONS = {
|
||||
closed: 'mdi:filter-cog-outline',
|
||||
open: 'mdi:filter-cog',
|
||||
};
|
||||
|
||||
@customElement('advanced-camera-card-media-gallery')
|
||||
export class AdvancedCameraCardMediaGallery extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewManagerEpoch?: ViewManagerEpoch;
|
||||
|
||||
@property({ attribute: false })
|
||||
public galleryConfig?: MediaGalleryConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewItemManager?: ViewItemManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cardWideConfig?: CardWideConfig;
|
||||
|
||||
protected _controller = new MediaGalleryController(this);
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has('viewManagerEpoch')) {
|
||||
this._controller.setMediaFromView(
|
||||
this.viewManagerEpoch?.manager.getView(),
|
||||
this.viewManagerEpoch?.oldView,
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProps.has('galleryConfig')) {
|
||||
this._controller.setThumbnailSize(this.galleryConfig?.controls.thumbnails.size);
|
||||
}
|
||||
}
|
||||
|
||||
protected _renderThumbnails(): TemplateResult | void {
|
||||
const selected = this.viewManagerEpoch?.manager
|
||||
.getView()
|
||||
?.queryResults?.getSelectedResult();
|
||||
|
||||
return html`
|
||||
${this._controller.getMedia()?.map(
|
||||
(media, index) =>
|
||||
html`<advanced-camera-card-thumbnail
|
||||
class=${classMap({
|
||||
selected: media === selected,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
.item=${media}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
?selected=${media === selected}
|
||||
?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
|
||||
?show_favorite_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_favorite_control}
|
||||
?show_timeline_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_timeline_control}
|
||||
?show_download_control=${!!this.galleryConfig?.controls.thumbnails
|
||||
.show_download_control}
|
||||
@click=${(ev: Event) => {
|
||||
const manager = this.viewManagerEpoch?.manager;
|
||||
if (manager) {
|
||||
this._controller.itemClickHandler(manager, index, ev);
|
||||
}
|
||||
}}
|
||||
>
|
||||
</advanced-camera-card-thumbnail>`,
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const mediaIsLoading =
|
||||
!!this.viewManagerEpoch?.manager.getView()?.context?.loading?.query;
|
||||
|
||||
return html`
|
||||
<advanced-camera-card-surround-basic
|
||||
.drawerIcons=${{
|
||||
...(this.galleryConfig &&
|
||||
this.galleryConfig.controls.filter.mode !== 'none' && {
|
||||
[this.galleryConfig.controls.filter.mode]: MEDIA_GALLERY_FILTER_MENU_ICONS,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
${this.galleryConfig && this.galleryConfig.controls.filter.mode !== 'none'
|
||||
? html` <advanced-camera-card-media-filter
|
||||
.hass=${this.hass}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
slot=${this.galleryConfig.controls.filter.mode}
|
||||
>
|
||||
</advanced-camera-card-media-filter>`
|
||||
: ''}
|
||||
${!this._controller.getMedia()?.length
|
||||
? renderMessage({
|
||||
type: 'info',
|
||||
message: mediaIsLoading
|
||||
? localize('error.awaiting_media')
|
||||
: localize('common.no_media'),
|
||||
icon: 'mdi:multimedia',
|
||||
dotdotdot: mediaIsLoading,
|
||||
})
|
||||
: html`<advanced-camera-card-gallery-core
|
||||
.hass=${this.hass}
|
||||
.columnWidth=${this._controller.getColumnWidth(
|
||||
this.galleryConfig?.controls.thumbnails,
|
||||
)}
|
||||
.columnCountRoundMethod=${this._controller.getColumnCountRoundMethod(
|
||||
this.galleryConfig?.controls.thumbnails,
|
||||
)}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.extendUp=${true}
|
||||
.extendDown=${true}
|
||||
@advanced-camera-card:gallery:extend:up=${(
|
||||
ev: CustomEvent<GalleryExtendEvent>,
|
||||
) =>
|
||||
this._extendGallery(
|
||||
ev,
|
||||
'later',
|
||||
// Avoid use of cache since the user is explicitly looking for
|
||||
// the freshest possible data.
|
||||
false,
|
||||
)}
|
||||
@advanced-camera-card:gallery:extend:down=${(
|
||||
ev: CustomEvent<GalleryExtendEvent>,
|
||||
) => this._extendGallery(ev, 'earlier')}
|
||||
>
|
||||
${this._renderThumbnails()}
|
||||
</advanced-camera-card-gallery-core>`}
|
||||
</advanced-camera-card-surround-basic>
|
||||
`;
|
||||
}
|
||||
|
||||
protected async _extendGallery(
|
||||
ev: CustomEvent<GalleryExtendEvent>,
|
||||
direction: 'earlier' | 'later',
|
||||
useCache = true,
|
||||
): Promise<void> {
|
||||
if (!this.cameraManager || !this.viewManagerEpoch) {
|
||||
return;
|
||||
}
|
||||
await this._controller.extendMediaGallery(
|
||||
this.cameraManager,
|
||||
this.viewManagerEpoch,
|
||||
direction,
|
||||
useCache,
|
||||
);
|
||||
ev.detail.resolve();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(mediaGalleryStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'advanced-camera-card-media-gallery': AdvancedCameraCardMediaGallery;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,11 @@ import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
||||
import { CameraManager } from '../camera-manager/manager';
|
||||
import { FoldersManager } from '../card-controller/folders/manager';
|
||||
import { ViewManagerEpoch } from '../card-controller/view/types';
|
||||
import {
|
||||
MediaFilterController,
|
||||
MediaFilterCoreFavoriteSelection,
|
||||
MediaFilterCoreWhen,
|
||||
MediaFilterMediaType,
|
||||
} from '../components-lib/media-filter-controller';
|
||||
import { CardWideConfig } from '../config/schema/types';
|
||||
import { HomeAssistant } from '../ha/types';
|
||||
@@ -38,6 +37,9 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
@property({ attribute: false })
|
||||
public viewManagerEpoch?: ViewManagerEpoch;
|
||||
|
||||
@property({ attribute: false })
|
||||
public foldersManager?: FoldersManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cardWideConfig?: CardWideConfig;
|
||||
|
||||
@@ -56,6 +58,7 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
protected _refWhat: Ref<AdvancedCameraCardSelect> = createRef();
|
||||
protected _refWhere: Ref<AdvancedCameraCardSelect> = createRef();
|
||||
protected _refFavorite: Ref<AdvancedCameraCardSelect> = createRef();
|
||||
protected _refReviewed: Ref<AdvancedCameraCardSelect> = createRef();
|
||||
protected _refTags: Ref<AdvancedCameraCardSelect> = createRef();
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
@@ -63,42 +66,56 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
this._mediaFilterController.setViewManager(this.viewManagerEpoch?.manager ?? null);
|
||||
}
|
||||
|
||||
if (changedProps.has('cameraManager') && this.cameraManager) {
|
||||
this._mediaFilterController.computeCameraOptions(this.cameraManager);
|
||||
if (
|
||||
(changedProps.has('cameraManager') || changedProps.has('foldersManager')) &&
|
||||
this.cameraManager &&
|
||||
this.foldersManager
|
||||
) {
|
||||
this._mediaFilterController.computeCameraOptions(
|
||||
this.cameraManager,
|
||||
this.foldersManager,
|
||||
);
|
||||
this._mediaFilterController.computeMetadataOptions(this.cameraManager);
|
||||
}
|
||||
|
||||
// The first time the viewManager is set, compute the initial default selections.
|
||||
if (
|
||||
!changedProps.get('viewManager') &&
|
||||
(!changedProps.get('viewManager') || changedProps.has('foldersManager')) &&
|
||||
this.viewManagerEpoch &&
|
||||
this.cameraManager
|
||||
this.cameraManager &&
|
||||
this.foldersManager
|
||||
) {
|
||||
this._mediaFilterController.computeInitialDefaultsFromView(this.cameraManager);
|
||||
this._mediaFilterController.computeInitialDefaultsFromView(
|
||||
this.cameraManager,
|
||||
this.foldersManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const valueChange = async () => {
|
||||
if (!this.cameraManager || !this.viewManagerEpoch || !this.cardWideConfig) {
|
||||
if (
|
||||
!this.cameraManager ||
|
||||
!this.foldersManager ||
|
||||
!this.viewManagerEpoch ||
|
||||
!this.cardWideConfig
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this._mediaFilterController.valueChangeHandler(
|
||||
this.cameraManager,
|
||||
this.foldersManager,
|
||||
this.cardWideConfig,
|
||||
{
|
||||
camera: this._refCamera.value?.value ?? undefined,
|
||||
mediaType: (this._refMediaType.value?.value ?? undefined) as
|
||||
| MediaFilterMediaType
|
||||
| undefined,
|
||||
mediaTypes: this._refMediaType.value?.value ?? undefined,
|
||||
when: {
|
||||
selected: this._refWhen.value?.value ?? undefined,
|
||||
from: this._refWhenFrom.value?.value,
|
||||
to: this._refWhenTo.value?.value,
|
||||
},
|
||||
favorite: (this._refFavorite.value?.value ?? undefined) as
|
||||
| MediaFilterCoreFavoriteSelection
|
||||
| undefined,
|
||||
favorite: this._refFavorite.value?.value ?? undefined,
|
||||
reviewed: this._refReviewed.value?.value ?? undefined,
|
||||
where: this._refWhere.value?.value ?? undefined,
|
||||
what: this._refWhat.value?.value ?? undefined,
|
||||
tags: this._refTags.value?.value ?? undefined,
|
||||
@@ -126,7 +143,6 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controls = this._mediaFilterController.getControlsToShow(this.cameraManager);
|
||||
const defaults = this._mediaFilterController.getDefaults();
|
||||
const whatOptions = this._mediaFilterController.getWhatOptions();
|
||||
const tagsOptions = this._mediaFilterController.getTagsOptions();
|
||||
@@ -137,7 +153,9 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
label=${localize('media_filter.media_type')}
|
||||
placeholder=${localize('media_filter.select_media_type')}
|
||||
.options=${this._mediaFilterController.getMediaTypeOptions()}
|
||||
.initialValue=${defaults?.mediaType}
|
||||
.initialValue=${defaults?.mediaTypes}
|
||||
multiple
|
||||
clearable
|
||||
@advanced-camera-card:select:change=${() => valueChange()}
|
||||
>
|
||||
</advanced-camera-card-select>
|
||||
@@ -184,7 +202,7 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
@advanced-camera-card:select:change=${() => valueChange()}
|
||||
>
|
||||
</advanced-camera-card-select>
|
||||
${controls.events && whatOptions.length
|
||||
${whatOptions.length
|
||||
? html` <advanced-camera-card-select
|
||||
${ref(this._refWhat)}
|
||||
label=${localize('media_filter.what')}
|
||||
@@ -197,7 +215,7 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
>
|
||||
</advanced-camera-card-select>`
|
||||
: ''}
|
||||
${controls.events && tagsOptions.length
|
||||
${tagsOptions.length
|
||||
? html` <advanced-camera-card-select
|
||||
${ref(this._refTags)}
|
||||
label=${localize('media_filter.tag')}
|
||||
@@ -210,7 +228,7 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
>
|
||||
</advanced-camera-card-select>`
|
||||
: ''}
|
||||
${controls.events && whereOptions.length
|
||||
${whereOptions.length
|
||||
? html` <advanced-camera-card-select
|
||||
${ref(this._refWhere)}
|
||||
label=${localize('media_filter.where')}
|
||||
@@ -223,20 +241,26 @@ class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) {
|
||||
>
|
||||
</advanced-camera-card-select>`
|
||||
: ''}
|
||||
${controls.favorites
|
||||
? html`
|
||||
<advanced-camera-card-select
|
||||
${ref(this._refFavorite)}
|
||||
label=${localize('media_filter.favorite')}
|
||||
placeholder=${localize('media_filter.select_favorite')}
|
||||
.options=${this._mediaFilterController.getFavoriteOptions()}
|
||||
.initialValue=${defaults?.favorite}
|
||||
clearable
|
||||
@advanced-camera-card:select:change=${() => valueChange()}
|
||||
>
|
||||
</advanced-camera-card-select>
|
||||
`
|
||||
: ''}`;
|
||||
<advanced-camera-card-select
|
||||
${ref(this._refFavorite)}
|
||||
label=${localize('media_filter.favorite')}
|
||||
placeholder=${localize('media_filter.select_favorite')}
|
||||
.options=${this._mediaFilterController.getFavoriteOptions()}
|
||||
.initialValue=${defaults?.favorite}
|
||||
clearable
|
||||
@advanced-camera-card:select:change=${() => valueChange()}
|
||||
>
|
||||
</advanced-camera-card-select>
|
||||
<advanced-camera-card-select
|
||||
${ref(this._refReviewed)}
|
||||
label=${localize('media_filter.reviewed')}
|
||||
placeholder=${localize('media_filter.select_reviewed')}
|
||||
.options=${this._mediaFilterController.getReviewedOptions()}
|
||||
.initialValue=${defaults?.reviewed}
|
||||
clearable
|
||||
@advanced-camera-card:select:change=${() => valueChange()}
|
||||
>
|
||||
</advanced-camera-card-select>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||
import overlayMessageStyle from '../scss/overlay-message.scss';
|
||||
import { MetadataField, OverlayMessage, OverlayMessageControl } from '../types.js';
|
||||
import { dispatchDismissOverlayMessageEvent } from '../utils/overlay-message.js';
|
||||
import './icon.js';
|
||||
|
||||
@customElement('advanced-camera-card-overlay-message')
|
||||
export class AdvancedCameraCardOverlayMessage extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public message: OverlayMessage | null = null;
|
||||
|
||||
protected _refMessage: Ref<HTMLElement> = createRef();
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.addEventListener('click', this._handleOutsideInteraction);
|
||||
window.addEventListener('focusin', this._handleOutsideInteraction);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
window.removeEventListener('click', this._handleOutsideInteraction);
|
||||
window.removeEventListener('focusin', this._handleOutsideInteraction);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
const heading = this.message.heading;
|
||||
const details = this.message.details ?? [];
|
||||
const text = this.message.text;
|
||||
const controls = this.message.controls ?? [];
|
||||
|
||||
return html`
|
||||
<div class="backdrop" @click=${this._dismiss}></div>
|
||||
<div
|
||||
class="message"
|
||||
${ref(this._refMessage)}
|
||||
@animationend=${this._handleAnimationEnd}
|
||||
>
|
||||
<div class="details">
|
||||
${heading ? this._renderDetail(heading, true) : ''}
|
||||
${details.map((detail) => this._renderDetail(detail))}
|
||||
${text ? html`<div class="description">${text}</div>` : ''}
|
||||
</div>
|
||||
${controls.length
|
||||
? html`<div class="controls">
|
||||
${controls.map((control) => this._renderControl(control))}
|
||||
</div>`
|
||||
: ''}
|
||||
<div class="close" @click=${this._dismiss}>
|
||||
<advanced-camera-card-icon
|
||||
.icon=${{ icon: 'mdi:close' }}
|
||||
></advanced-camera-card-icon>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected _renderControl(control: OverlayMessageControl): TemplateResult {
|
||||
const emphasisClass = control.emphasis ? `emphasis-${control.emphasis}` : '';
|
||||
return html`
|
||||
<div
|
||||
class="control ${emphasisClass}"
|
||||
title=${control.title}
|
||||
@click=${async () => this._handleControlClick(control)}
|
||||
>
|
||||
${control.icon
|
||||
? html`<advanced-camera-card-icon
|
||||
.icon=${control.icon}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected async _handleControlClick(control: OverlayMessageControl): Promise<void> {
|
||||
const result = await control.callback();
|
||||
if (result === null) {
|
||||
// null = close the message
|
||||
this._dismiss();
|
||||
} else {
|
||||
// Updated message = keep open and refresh
|
||||
this.message = result;
|
||||
}
|
||||
}
|
||||
|
||||
protected _renderDetail(detail: MetadataField, isHeading = false): TemplateResult {
|
||||
return html`
|
||||
<div class="detail ${isHeading ? 'heading' : ''}">
|
||||
${detail.icon
|
||||
? html`<advanced-camera-card-icon
|
||||
title=${detail.hint ?? ''}
|
||||
.icon=${detail.icon}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
<span title=${detail.title}>${detail.title}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected _dismiss = (): void => {
|
||||
this._refMessage.value?.classList.add('exiting');
|
||||
};
|
||||
|
||||
protected _handleAnimationEnd = (ev: AnimationEvent): void => {
|
||||
if (ev.animationName === 'slideDown') {
|
||||
dispatchDismissOverlayMessageEvent(this);
|
||||
}
|
||||
};
|
||||
|
||||
protected _handleOutsideInteraction = (ev: Event): void => {
|
||||
if (!ev.composedPath().includes(this)) {
|
||||
this._dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(overlayMessageStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'advanced-camera-card-overlay-message': AdvancedCameraCardOverlayMessage;
|
||||
}
|
||||
}
|
||||
@@ -70,10 +70,11 @@ export class AdvancedCameraCardSelect extends ScopedRegistryHost(LitElement) {
|
||||
const initialValueSet = this.value === null;
|
||||
this.value = value;
|
||||
|
||||
// The underlying gr-select element will call on the first first value set
|
||||
// The underlying gr-select element will call on the first value set
|
||||
// (even when the user has not interacted with the control). Do not
|
||||
// dispatch events for this.
|
||||
if (!initialValueSet) {
|
||||
// dispatch events for this UNLESS the value is actually non-empty
|
||||
// (indicating a user selection).
|
||||
if (!initialValueSet || (Array.isArray(value) ? value.length : !!value)) {
|
||||
fireAdvancedCameraCardEvent(this, 'select:change', value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ export class AdvancedCameraCardStatusBar extends LitElement {
|
||||
return html`<div
|
||||
.actionHandler=${handler}
|
||||
class="${classes}"
|
||||
data-severity=${item.severity ?? ''}
|
||||
@action=${(ev) => this._controller.actionHandler(ev, item.actions)}
|
||||
>
|
||||
${item.string}
|
||||
@@ -109,6 +110,7 @@ export class AdvancedCameraCardStatusBar extends LitElement {
|
||||
.actionHandler=${handler}
|
||||
.icon=${{ icon: item.icon }}
|
||||
class="${classes}"
|
||||
data-severity=${item.severity ?? ''}
|
||||
@action=${(ev) => this._controller.actionHandler(ev, item.actions)}
|
||||
></advanced-camera-card-icon>`;
|
||||
} else if (item.type === 'custom:advanced-camera-card-status-bar-image') {
|
||||
@@ -116,6 +118,7 @@ export class AdvancedCameraCardStatusBar extends LitElement {
|
||||
.actionHandler=${handler}
|
||||
class="${classes}"
|
||||
src="${item.image}"
|
||||
data-severity=${item.severity ?? ''}
|
||||
@action=${(ev) => this._controller.actionHandler(ev, item.actions)}
|
||||
/>`;
|
||||
}
|
||||
|
||||
@@ -7,22 +7,18 @@ import {
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { CameraManager } from '../camera-manager/manager.js';
|
||||
import { FoldersManager } from '../card-controller/folders/manager.js';
|
||||
import { ViewItemManager } from '../card-controller/view/item-manager.js';
|
||||
import { ViewManagerEpoch } from '../card-controller/view/types.js';
|
||||
import { TimelineKeys } from '../components-lib/timeline/types.js';
|
||||
import { ConditionStateManagerReadonlyInterface } from '../conditions/types.js';
|
||||
import { ThumbnailsControlConfig } from '../config/schema/common/controls/thumbnails.js';
|
||||
import { MiniTimelineControlConfig } from '../config/schema/common/controls/timeline.js';
|
||||
import { FolderConfig } from '../config/schema/folders.js';
|
||||
import { CardWideConfig } from '../config/schema/types.js';
|
||||
import { HomeAssistant } from '../ha/types.js';
|
||||
import basicBlockStyle from '../scss/basic-block.scss';
|
||||
import { contentsChanged } from '../utils/basic.js';
|
||||
import { fireAdvancedCameraCardEvent } from '../utils/fire-advanced-camera-card-event.js';
|
||||
import { QueryClassifier } from '../view/query-classifier.js';
|
||||
import './surround-basic.js';
|
||||
import './thumbnail-carousel';
|
||||
|
||||
@@ -55,11 +51,8 @@ export class AdvancedCameraCardSurround extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cardWideConfig?: CardWideConfig;
|
||||
|
||||
protected _timelineKeys?: TimelineKeys | null = null;
|
||||
|
||||
/**
|
||||
* Determine if a drawer is being used.
|
||||
* @returns `true` if a drawer is used, `false` otherwise.
|
||||
*/
|
||||
protected _hasDrawer(): boolean {
|
||||
return (
|
||||
@@ -67,80 +60,11 @@ export class AdvancedCameraCardSurround extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected willUpdate(_changedProperties: PropertyValues): void {
|
||||
if (this.timelineConfig?.mode && this.timelineConfig.mode !== 'none') {
|
||||
import('./timeline-core.js');
|
||||
}
|
||||
|
||||
// Only reset the timeline cameraIDs when the media or display mode
|
||||
// materially changes (and not on every view change, since the view will
|
||||
// change frequently when the user is scrubbing video).
|
||||
const view = this.viewManagerEpoch?.manager.getView();
|
||||
if (
|
||||
changedProperties.has('viewManagerEpoch') &&
|
||||
(this.viewManagerEpoch?.manager.hasMajorMediaChange(
|
||||
this.viewManagerEpoch?.oldView,
|
||||
) ||
|
||||
this.viewManagerEpoch?.oldView?.displayMode !== view?.displayMode)
|
||||
) {
|
||||
const newKeys = this._getTimelineKeys();
|
||||
// Update only if changed, to avoid unnecessary timeline destructions.
|
||||
if (!isEqual(newKeys, this._timelineKeys)) {
|
||||
this._timelineKeys = newKeys ?? undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _getTimelineKeys(): TimelineKeys | null {
|
||||
const cameraIDsToKeys = (cameraIDs: Set<string> | null): TimelineKeys | null => {
|
||||
return cameraIDs?.size
|
||||
? {
|
||||
type: 'camera',
|
||||
cameraIDs,
|
||||
}
|
||||
: null;
|
||||
};
|
||||
|
||||
const folderToKeys = (folderConfig: FolderConfig): TimelineKeys => {
|
||||
return {
|
||||
type: 'folder',
|
||||
folder: folderConfig,
|
||||
};
|
||||
};
|
||||
|
||||
const view = this.viewManagerEpoch?.manager.getView();
|
||||
if (!view || !this.cameraManager) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (view.is('live')) {
|
||||
const capabilitySearch = {
|
||||
anyCapabilities: ['clips' as const, 'snapshots' as const, 'recordings' as const],
|
||||
};
|
||||
if (view.supportsMultipleDisplayModes() && view.isGrid()) {
|
||||
return cameraIDsToKeys(
|
||||
this.cameraManager.getStore().getCameraIDsWithCapability(capabilitySearch),
|
||||
);
|
||||
} else {
|
||||
return cameraIDsToKeys(
|
||||
this.cameraManager
|
||||
.getStore()
|
||||
.getAllDependentCameras(view.camera, capabilitySearch),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const queries = view.query;
|
||||
if (view.isViewerView()) {
|
||||
if (QueryClassifier.isMediaQuery(queries)) {
|
||||
return cameraIDsToKeys(queries.getQueryCameraIDs());
|
||||
} else if (QueryClassifier.isFolderQuery(queries)) {
|
||||
const folderConfig = queries.getQuery()?.folder;
|
||||
return folderConfig ? folderToKeys(folderConfig) : null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@@ -175,7 +99,9 @@ export class AdvancedCameraCardSurround extends LitElement {
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
.fadeThumbnails=${view.isViewerView()}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
.foldersManager=${this.foldersManager}
|
||||
.selected=${view.queryResults?.getSelectedIndex() ?? undefined}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
>
|
||||
</advanced-camera-card-thumbnail-carousel>`
|
||||
: ''}
|
||||
@@ -189,7 +115,6 @@ export class AdvancedCameraCardSurround extends LitElement {
|
||||
this.thumbnailConfig?.mode === 'none'
|
||||
? 'play'
|
||||
: 'select'}
|
||||
.keys=${this._timelineKeys}
|
||||
.mini=${true}
|
||||
.timelineConfig=${this.timelineConfig}
|
||||
.thumbnailConfig=${this.thumbnailConfig}
|
||||
|
||||
@@ -9,14 +9,20 @@ import {
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { CameraManager } from '../camera-manager/manager.js';
|
||||
import { FoldersManager } from '../card-controller/folders/manager.js';
|
||||
import { ViewItemManager } from '../card-controller/view/item-manager.js';
|
||||
import { RemoveContextViewModifier } from '../card-controller/view/modifiers/remove-context.js';
|
||||
import { ViewManagerEpoch } from '../card-controller/view/types.js';
|
||||
import {
|
||||
getUpFolderMediaItem,
|
||||
upFolderClickHandler,
|
||||
} from '../components-lib/folder/up-folder.js';
|
||||
FolderNavigationParamaters,
|
||||
getUpFolderItem,
|
||||
navigateToFolder,
|
||||
navigateToMedia,
|
||||
navigateUp,
|
||||
} from '../components-lib/navigation.js';
|
||||
import { ThumbnailsControlConfig } from '../config/schema/common/controls/thumbnails.js';
|
||||
import { CardWideConfig } from '../config/schema/types.js';
|
||||
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js';
|
||||
import { HomeAssistant } from '../ha/types.js';
|
||||
import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss';
|
||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||
@@ -24,7 +30,7 @@ import { CarouselDirection } from '../utils/embla/carousel-controller.js';
|
||||
import { fireAdvancedCameraCardEvent } from '../utils/fire-advanced-camera-card-event.js';
|
||||
import { ViewItemClassifier } from '../view/item-classifier.js';
|
||||
import { ViewItem, ViewMedia } from '../view/item.js';
|
||||
import { QueryClassifier } from '../view/query-classifier.js';
|
||||
import { UnifiedQueryBuilder } from '../view/unified-query-builder.js';
|
||||
import './carousel.js';
|
||||
import './thumbnail/thumbnail.js';
|
||||
|
||||
@@ -43,18 +49,50 @@ export class AdvancedCameraCardThumbnailCarousel extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public foldersManager?: FoldersManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewItemManager?: ViewItemManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public config?: ThumbnailsControlConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cardWideConfig?: CardWideConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public fadeThumbnails = false;
|
||||
|
||||
protected _thumbnails: TemplateResult[] = [];
|
||||
protected _builder: UnifiedQueryBuilder | null = null;
|
||||
|
||||
protected _getLimit(): number {
|
||||
return (
|
||||
this.cardWideConfig?.performance?.features?.media_chunk_size ??
|
||||
MEDIA_CHUNK_SIZE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
protected _getFolderNavOptions(): FolderNavigationParamaters | undefined {
|
||||
return this._builder && this.viewManagerEpoch
|
||||
? {
|
||||
builder: this._builder,
|
||||
viewManagerEpoch: this.viewManagerEpoch,
|
||||
limit: this._getLimit(),
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (
|
||||
(changedProps.has('cameraManager') || changedProps.has('foldersManager')) &&
|
||||
this.cameraManager &&
|
||||
this.foldersManager
|
||||
) {
|
||||
this._builder = new UnifiedQueryBuilder(this.cameraManager, this.foldersManager);
|
||||
}
|
||||
|
||||
if (changedProps.has('config')) {
|
||||
if (this.config?.size) {
|
||||
this.style.setProperty(
|
||||
@@ -94,53 +132,17 @@ export class AdvancedCameraCardThumbnailCarousel extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
protected _itemClickCallback(item: ViewItem, ev: Event): void {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
|
||||
const view = this.viewManagerEpoch?.manager.getView();
|
||||
const query = view?.query;
|
||||
const results = view?.queryResults;
|
||||
|
||||
if (!view || !query || !results) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ViewItemClassifier.isMedia(item)) {
|
||||
const newResults = results
|
||||
.clone()
|
||||
.selectResultIfFound((result) => result === item);
|
||||
const cameraID = item.getCameraID();
|
||||
|
||||
fireAdvancedCameraCardEvent<ThumbnailMediaSelect>(
|
||||
this,
|
||||
'thumbnails-carousel:media-select',
|
||||
{ media: item },
|
||||
);
|
||||
this.viewManagerEpoch?.manager.setViewByParameters({
|
||||
params: {
|
||||
view: 'media',
|
||||
queryResults: newResults,
|
||||
...(cameraID && { camera: cameraID }),
|
||||
},
|
||||
protected _handleMediaClick(item: ViewMedia): void {
|
||||
fireAdvancedCameraCardEvent<ThumbnailMediaSelect>(
|
||||
this,
|
||||
'thumbnails-carousel:media-select',
|
||||
{ media: item },
|
||||
);
|
||||
if (this.viewManagerEpoch) {
|
||||
navigateToMedia(item, {
|
||||
viewManagerEpoch: this.viewManagerEpoch,
|
||||
modifiers: [new RemoveContextViewModifier(['timeline', 'mediaViewer'])],
|
||||
});
|
||||
} else if (
|
||||
QueryClassifier.isFolderQuery(query) &&
|
||||
ViewItemClassifier.isFolder(item)
|
||||
) {
|
||||
const rawQuery = query.getQuery();
|
||||
if (!rawQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewManagerEpoch?.manager.setViewByParametersWithExistingQuery({
|
||||
params: {
|
||||
query: query.clone().setQuery({
|
||||
folder: rawQuery.folder,
|
||||
path: [...(rawQuery.path ?? []), { folder: item }],
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,31 +173,42 @@ export class AdvancedCameraCardThumbnailCarousel extends LitElement {
|
||||
?show_favorite_control=${this.config?.show_favorite_control}
|
||||
?show_timeline_control=${this.config?.show_timeline_control}
|
||||
?show_download_control=${this.config?.show_download_control}
|
||||
?show_review_control=${this.config?.show_review_control}
|
||||
?show_info_control=${this.config?.show_info_control}
|
||||
@click=${(ev: Event) => clickCallback(item, ev)}
|
||||
>
|
||||
</advanced-camera-card-thumbnail>`;
|
||||
}
|
||||
|
||||
protected _renderThumbnails(): TemplateResult[] {
|
||||
const upThumbnail = getUpFolderMediaItem(this.viewManagerEpoch?.manager.getView());
|
||||
const thumbnails: TemplateResult[] = [
|
||||
...(upThumbnail
|
||||
? [
|
||||
this._renderThumbnail(upThumbnail, false, (item: ViewItem, ev: Event) =>
|
||||
upFolderClickHandler(item, ev, this.viewManagerEpoch),
|
||||
),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
const upFolderItem = getUpFolderItem(
|
||||
this.viewManagerEpoch?.manager.getView()?.query,
|
||||
);
|
||||
const thumbnails: TemplateResult[] = upFolderItem
|
||||
? [
|
||||
this._renderThumbnail(upFolderItem, false, (_item: ViewItem, ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
navigateUp(this._getFolderNavOptions());
|
||||
}),
|
||||
]
|
||||
: [];
|
||||
const view = this.viewManagerEpoch?.manager.getView();
|
||||
const selectedIndex = this._getSelectedSlide();
|
||||
|
||||
for (const item of view?.queryResults?.getResults() ?? []) {
|
||||
const clickHandler = (item: ViewItem, ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (ViewItemClassifier.isMedia(item)) {
|
||||
this._handleMediaClick(item);
|
||||
} else if (ViewItemClassifier.isFolder(item)) {
|
||||
navigateToFolder(item, this._getFolderNavOptions());
|
||||
}
|
||||
};
|
||||
thumbnails.push(
|
||||
this._renderThumbnail(
|
||||
item,
|
||||
selectedIndex === thumbnails.length,
|
||||
(item: ViewItem, ev: Event) => this._itemClickCallback(item, ev),
|
||||
clickHandler,
|
||||
view?.context?.mediaViewer?.seek,
|
||||
),
|
||||
);
|
||||
@@ -214,18 +227,18 @@ export class AdvancedCameraCardThumbnailCarousel extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const direction = this._getDirection();
|
||||
if (!this._thumbnails.length || !this.config || !direction) {
|
||||
if (!this._thumbnails.length || !this.config?.mode || this.config.mode === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
return html`<advanced-camera-card-carousel
|
||||
direction=${direction}
|
||||
.selected=${this._getSelectedSlide() ?? 0}
|
||||
class="${classMap({ fade: this.fadeThumbnails })}"
|
||||
direction=${this._getDirection() ?? 'horizontal'}
|
||||
.selected=${this._getSelectedSlide()}
|
||||
.dragFree=${true}
|
||||
>
|
||||
${this._thumbnails}
|
||||
</advanced-camera-card-carousel>`;
|
||||
</advanced-camera-card-carousel> `;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -7,10 +7,12 @@ import {
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { CameraManager } from '../../camera-manager/manager';
|
||||
import { ThumbnailDetailsController } from '../../components-lib/thumbnail/details-controller';
|
||||
import { MediaDetailsController } from '../../components-lib/media/details-controller';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import thumbnailDetailsStyle from '../../scss/thumbnail-details.scss';
|
||||
import { MetadataField } from '../../types';
|
||||
import { ViewItem } from '../../view/item';
|
||||
import '../icon';
|
||||
|
||||
@@ -28,7 +30,7 @@ export class AdvancedCameraCardThumbnailDetails extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public seek?: Date;
|
||||
|
||||
private _controller = new ThumbnailDetailsController();
|
||||
private _controller = new MediaDetailsController();
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
if (['item', 'seek', 'cameraManager'].some((prop) => changedProperties.has(prop))) {
|
||||
@@ -40,28 +42,26 @@ export class AdvancedCameraCardThumbnailDetails extends LitElement {
|
||||
const heading = this._controller.getHeading();
|
||||
const details = this._controller.getDetails();
|
||||
|
||||
const renderDetail = (detail: MetadataField, heading = false): TemplateResult => {
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
heading,
|
||||
})}
|
||||
>
|
||||
${detail.icon
|
||||
? html` <advanced-camera-card-icon
|
||||
severity=${detail.emphasis}
|
||||
title=${detail.hint ?? ''}
|
||||
.icon=${detail.icon}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
<span title=${detail.title}>${detail.title}</span>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
return html`
|
||||
${heading
|
||||
? html` <div class="heading">
|
||||
<span title=${heading}>${heading}</span>
|
||||
</div>`
|
||||
: ``}
|
||||
${details
|
||||
? html` <div class="details">
|
||||
${details.map(
|
||||
(detail) =>
|
||||
html`<div>
|
||||
${detail.icon
|
||||
? html` <advanced-camera-card-icon
|
||||
title=${detail.hint ?? ''}
|
||||
.icon=${detail.icon}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
<span>${detail.title}</span>
|
||||
</div>`,
|
||||
)}
|
||||
</div>`
|
||||
: ''}
|
||||
${heading ? renderDetail(heading, true) : ``}
|
||||
${details.length ? details.map((detail) => renderDetail(detail)) : ``}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,26 @@ import {
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { CameraManager } from '../../../camera-manager/manager';
|
||||
import { ViewItemManager } from '../../../card-controller/view/item-manager';
|
||||
import { ViewManagerEpoch } from '../../../card-controller/view/types';
|
||||
import {
|
||||
MediaDetailsController,
|
||||
OverlayControlsContext,
|
||||
} from '../../../components-lib/media/details-controller';
|
||||
import { ThumbnailFeatureController } from '../../../components-lib/thumbnail/feature/controller';
|
||||
import { HomeAssistant } from '../../../ha/types';
|
||||
import { localize } from '../../../localize/localize';
|
||||
import thumbnailFeatureStyle from '../../../scss/thumbnail-feature.scss';
|
||||
import { stopEventFromActivatingCardWideActions } from '../../../utils/action';
|
||||
import {
|
||||
downloadMedia,
|
||||
navigateToTimeline,
|
||||
toggleFavorite,
|
||||
toggleReviewed,
|
||||
} from '../../../utils/media-actions';
|
||||
import { dispatchShowOverlayMessageEvent } from '../../../utils/overlay-message';
|
||||
import { ViewItem } from '../../../view/item';
|
||||
import { ViewItemClassifier } from '../../../view/item-classifier';
|
||||
import '../../icon.js';
|
||||
import './thumbnail.js';
|
||||
|
||||
@@ -24,12 +40,33 @@ export class AdvancedCameraCardThumbnailFeature extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewItemManager?: ViewItemManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewManagerEpoch?: ViewManagerEpoch;
|
||||
|
||||
@property({ attribute: false })
|
||||
public item?: ViewItem;
|
||||
|
||||
@property({ attribute: false })
|
||||
public hasDetails?: boolean;
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_favorite_control = false;
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_timeline_control = false;
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_download_control = false;
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_review_control = false;
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_info_control = false;
|
||||
|
||||
private _controller = new ThumbnailFeatureController();
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
@@ -40,34 +77,158 @@ export class AdvancedCameraCardThumbnailFeature extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _getControlContext(): OverlayControlsContext {
|
||||
return {
|
||||
hass: this.hass,
|
||||
viewItemManager: this.viewItemManager,
|
||||
viewManagerEpoch: this.viewManagerEpoch,
|
||||
capabilities: this.item ? this.viewItemManager?.getCapabilities(this.item) : null,
|
||||
};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const starClasses = {
|
||||
star: true,
|
||||
starred: ViewItemClassifier.isMedia(this.item) && !!this.item?.isFavorite(),
|
||||
};
|
||||
|
||||
const shouldShowTimelineControl =
|
||||
this.show_timeline_control && ViewItemClassifier.supportsTimeline(this.item);
|
||||
|
||||
const mediaCapabilities = this.viewItemManager?.getCapabilities(this.item) ?? null;
|
||||
|
||||
const shouldShowFavoriteControl =
|
||||
this.show_favorite_control &&
|
||||
this.item &&
|
||||
this.hass &&
|
||||
mediaCapabilities?.canFavorite;
|
||||
|
||||
const shouldShowDownloadControl =
|
||||
this.show_download_control &&
|
||||
this.hass &&
|
||||
this.item.getID() &&
|
||||
mediaCapabilities?.canDownload;
|
||||
|
||||
const isReviewed = ViewItemClassifier.isReview(this.item)
|
||||
? this.item.isReviewed()
|
||||
: null;
|
||||
const shouldShowReviewControl = this.show_review_control && isReviewed !== null;
|
||||
|
||||
const shouldShowInfoControl =
|
||||
this.show_info_control && ViewItemClassifier.isMedia(this.item);
|
||||
|
||||
const title = this._controller.getTitle();
|
||||
const subtitles = this._controller.getSubtitles();
|
||||
const iconClasses = classMap({
|
||||
background: title || subtitles.length,
|
||||
});
|
||||
const hasText = !!title || !!subtitles.length;
|
||||
|
||||
const mainIconClasses = {
|
||||
placeholder: true,
|
||||
};
|
||||
|
||||
const thumbnailClass = this._controller.getThumbnailClass();
|
||||
const thumbnailClasses = classMap({
|
||||
...(thumbnailClass && { [thumbnailClass]: true }),
|
||||
'has-text': hasText,
|
||||
});
|
||||
|
||||
return html`
|
||||
${this._controller.getThumbnail()
|
||||
? html` <advanced-camera-card-thumbnail-feature-thumbnail
|
||||
class="${thumbnailClasses}"
|
||||
.hass=${this.hass}
|
||||
.thumbnail=${this._controller.getThumbnail()}
|
||||
aria-label=${this.item?.getTitle() ?? ''}
|
||||
title=${this.item?.getTitle() ?? ''}
|
||||
></advanced-camera-card-thumbnail-feature-thumbnail>`
|
||||
: this._controller.getIcon()
|
||||
? html`<advanced-camera-card-icon
|
||||
class="${iconClasses}"
|
||||
.icon=${{ icon: this._controller.getIcon() }}
|
||||
<div class=${classMap({ media: true, 'has-text': hasText })}>
|
||||
${this._controller.getThumbnail()
|
||||
? html` <advanced-camera-card-thumbnail-feature-thumbnail
|
||||
class="${thumbnailClasses}"
|
||||
.hass=${this.hass}
|
||||
.thumbnail=${this._controller.getThumbnail()}
|
||||
aria-label=${this.item?.getTitle() ?? ''}
|
||||
title=${this.item?.getTitle() ?? ''}
|
||||
></advanced-camera-card-thumbnail-feature-thumbnail>`
|
||||
: this._controller.getIcon()
|
||||
? html`<advanced-camera-card-icon
|
||||
class=${classMap(mainIconClasses)}
|
||||
.icon=${{ icon: this._controller.getIcon() }}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
</div>
|
||||
${shouldShowReviewControl
|
||||
? html`<advanced-camera-card-icon
|
||||
class="review"
|
||||
title=${isReviewed
|
||||
? localize('common.set_reviews.unreviewed')
|
||||
: localize('common.set_reviews.reviewed')}
|
||||
.icon=${{
|
||||
icon: isReviewed ? 'mdi:check-circle' : 'mdi:check-circle-outline',
|
||||
}}
|
||||
@click=${async (ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (this.item) {
|
||||
await toggleReviewed(this.item, this._getControlContext());
|
||||
}
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: shouldShowFavoriteControl
|
||||
? html` <advanced-camera-card-icon
|
||||
class="${classMap(starClasses)}"
|
||||
title=${localize('thumbnail.retain_indefinitely')}
|
||||
.icon=${{
|
||||
icon: this.item.isFavorite() ? 'mdi:star' : 'mdi:star-outline',
|
||||
}}
|
||||
@click=${async (ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (
|
||||
this.item &&
|
||||
(await toggleFavorite(this.item, this._getControlContext()))
|
||||
) {
|
||||
this.requestUpdate();
|
||||
}
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
${title || subtitles.length
|
||||
: ``}
|
||||
${shouldShowInfoControl
|
||||
? html`<advanced-camera-card-icon
|
||||
class="info"
|
||||
.icon=${{ icon: 'mdi:information-outline' }}
|
||||
title=${this.item?.getDescription() ?? ''}
|
||||
@click=${(ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
const detailsController = new MediaDetailsController();
|
||||
detailsController.calculate(this.cameraManager, this.item);
|
||||
dispatchShowOverlayMessageEvent(
|
||||
this,
|
||||
detailsController.getMessage(this._getControlContext()),
|
||||
);
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
${shouldShowTimelineControl
|
||||
? html`<advanced-camera-card-icon
|
||||
class="timeline"
|
||||
.icon=${{ icon: 'mdi:target' }}
|
||||
title=${localize('thumbnail.timeline')}
|
||||
@click=${(ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (this.item) {
|
||||
navigateToTimeline(this.item, this._getControlContext());
|
||||
}
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
${shouldShowDownloadControl
|
||||
? html` <advanced-camera-card-icon
|
||||
class="download"
|
||||
.icon=${{ icon: 'mdi:download' }}
|
||||
title=${localize('thumbnail.download')}
|
||||
@click=${async (ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (this.item) {
|
||||
await downloadMedia(this.item, this._getControlContext());
|
||||
}
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: ``}
|
||||
${hasText
|
||||
? html`
|
||||
${title ? html`<div class="title">${title}</div>` : ''}
|
||||
${subtitles.length
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import { CSSResult, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { CameraManager } from '../../camera-manager/manager.js';
|
||||
import { FoldersManager } from '../../card-controller/folders/manager.js';
|
||||
import { ViewItemManager } from '../../card-controller/view/item-manager.js';
|
||||
import { RemoveContextViewModifier } from '../../card-controller/view/modifiers/remove-context.js';
|
||||
import { ViewManagerEpoch } from '../../card-controller/view/types.js';
|
||||
import { dispatchAdvancedCameraCardErrorEvent } from '../../components-lib/message/dispatch.js';
|
||||
import { HomeAssistant } from '../../ha/types.js';
|
||||
import { localize } from '../../localize/localize.js';
|
||||
import thumbnailStyle from '../../scss/thumbnail.scss';
|
||||
import { stopEventFromActivatingCardWideActions } from '../../utils/action.js';
|
||||
import { errorToConsole } from '../../utils/basic.js';
|
||||
import { ViewItemClassifier } from '../../view/item-classifier.js';
|
||||
import { ViewItem } from '../../view/item.js';
|
||||
import './details.js';
|
||||
import './feature/feature.js';
|
||||
@@ -56,6 +49,12 @@ export class AdvancedCameraCardThumbnail extends LitElement {
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_download_control = false;
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_review_control = false;
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
public show_info_control = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public seek?: Date;
|
||||
|
||||
@@ -68,62 +67,21 @@ export class AdvancedCameraCardThumbnail extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const starClasses = {
|
||||
star: true,
|
||||
starred: ViewItemClassifier.isMedia(this.item) && !!this.item?.isFavorite(),
|
||||
};
|
||||
|
||||
const shouldShowTimelineControl =
|
||||
this.show_timeline_control &&
|
||||
((ViewItemClassifier.isEvent(this.item) && this.item.getStartTime()) ||
|
||||
(ViewItemClassifier.isRecording(this.item) &&
|
||||
this.item.getStartTime() &&
|
||||
this.item.getEndTime()));
|
||||
|
||||
const mediaCapabilities = this.viewItemManager?.getCapabilities(this.item) ?? null;
|
||||
|
||||
const shouldShowFavoriteControl =
|
||||
this.show_favorite_control &&
|
||||
this.item &&
|
||||
this.hass &&
|
||||
mediaCapabilities?.canFavorite;
|
||||
|
||||
const shouldShowDownloadControl =
|
||||
this.show_download_control &&
|
||||
this.hass &&
|
||||
this.item.getID() &&
|
||||
mediaCapabilities?.canDownload;
|
||||
|
||||
return html`
|
||||
<advanced-camera-card-thumbnail-feature
|
||||
.cameraManager=${this.cameraManager}
|
||||
.hasDetails=${this.details}
|
||||
.hass=${this.hass}
|
||||
.item=${this.item}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
.show_favorite_control=${this.show_favorite_control}
|
||||
.show_timeline_control=${this.show_timeline_control}
|
||||
.show_download_control=${this.show_download_control}
|
||||
.show_review_control=${this.show_review_control}
|
||||
.show_info_control=${this.show_info_control}
|
||||
>
|
||||
</advanced-camera-card-thumbnail-feature>
|
||||
${shouldShowFavoriteControl
|
||||
? html` <advanced-camera-card-icon
|
||||
class="${classMap(starClasses)}"
|
||||
title=${localize('thumbnail.retain_indefinitely')}
|
||||
.icon=${{ icon: this.item.isFavorite() ? 'mdi:star' : 'mdi:star-outline' }}
|
||||
@click=${async (ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (this.hass && this.item) {
|
||||
try {
|
||||
await this.viewItemManager?.favorite(
|
||||
this.item,
|
||||
!this.item.isFavorite(),
|
||||
);
|
||||
} catch (e) {
|
||||
errorToConsole(e as Error);
|
||||
return;
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
}}
|
||||
/></advanced-camera-card-icon>`
|
||||
: ``}
|
||||
${this.details
|
||||
? html`<advanced-camera-card-thumbnail-details
|
||||
.hass=${this.hass}
|
||||
@@ -132,46 +90,6 @@ export class AdvancedCameraCardThumbnail extends LitElement {
|
||||
.seek=${this.seek}
|
||||
></advanced-camera-card-thumbnail-details>`
|
||||
: ''}
|
||||
${shouldShowTimelineControl
|
||||
? html`<advanced-camera-card-icon
|
||||
class="timeline"
|
||||
.icon=${{ icon: 'mdi:target' }}
|
||||
title=${localize('thumbnail.timeline')}
|
||||
@click=${(ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (!this.viewManagerEpoch || !this.item) {
|
||||
return;
|
||||
}
|
||||
this.viewManagerEpoch.manager.setViewByParameters({
|
||||
params: {
|
||||
view: 'timeline',
|
||||
queryResults: this.viewManagerEpoch?.manager
|
||||
.getView()
|
||||
?.queryResults?.clone()
|
||||
.selectResultIfFound((media) => media === this.item),
|
||||
},
|
||||
modifiers: [new RemoveContextViewModifier(['timeline'])],
|
||||
});
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
${shouldShowDownloadControl
|
||||
? html` <advanced-camera-card-icon
|
||||
class="download"
|
||||
.icon=${{ icon: 'mdi:download' }}
|
||||
title=${localize('thumbnail.download')}
|
||||
@click=${async (ev: Event) => {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
if (this.hass && this.item) {
|
||||
try {
|
||||
this.viewItemManager?.download(this.item);
|
||||
} catch (error: unknown) {
|
||||
dispatchAdvancedCameraCardErrorEvent(this, error);
|
||||
}
|
||||
}
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: ``}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
ThumbnailDataRequest,
|
||||
ThumbnailDataRequestEvent,
|
||||
TimelineItemClickAction,
|
||||
TimelineKeys,
|
||||
} from '../components-lib/timeline/types';
|
||||
import { ConditionStateManagerReadonlyInterface } from '../conditions/types';
|
||||
import { ThumbnailsControlBaseConfig } from '../config/schema/common/controls/thumbnails';
|
||||
@@ -120,11 +119,6 @@ export class AdvancedCameraCardTimelineCore extends LitElement {
|
||||
@property({ attribute: true, type: Boolean, reflect: true })
|
||||
public mini = false;
|
||||
|
||||
// Which cameraIDs to include in the timeline. If not specified, all cameraIDs
|
||||
// are shown.
|
||||
@property({ attribute: false, hasChanged: contentsChanged })
|
||||
public keys?: TimelineKeys;
|
||||
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
|
||||
@@ -152,7 +146,22 @@ export class AdvancedCameraCardTimelineCore extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.keys) {
|
||||
const view = this.viewManagerEpoch?.manager.getView();
|
||||
const isLoading = !!view?.context?.loading?.query;
|
||||
|
||||
if (isLoading) {
|
||||
if (!this.mini) {
|
||||
return renderMessage({
|
||||
message: localize('error.awaiting_media'),
|
||||
icon: 'mdi:chart-gantt',
|
||||
type: 'info',
|
||||
dotdotdot: true,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!view?.query || !view.query.hasNodes()) {
|
||||
if (!this.mini) {
|
||||
return renderMessage({
|
||||
message: localize('error.no_camera_or_media_for_timeline'),
|
||||
@@ -234,13 +243,14 @@ export class AdvancedCameraCardTimelineCore extends LitElement {
|
||||
|
||||
if (
|
||||
[
|
||||
'viewManagerEpoch',
|
||||
'cameraManager',
|
||||
'viewItemManager',
|
||||
'timelineConfig',
|
||||
'mini',
|
||||
'thumbnailConfig',
|
||||
'keys',
|
||||
'conditionStateManager',
|
||||
'foldersManager',
|
||||
].some((prop) => changedProps.has(prop))
|
||||
) {
|
||||
this._controller.setOptions({
|
||||
@@ -251,7 +261,7 @@ export class AdvancedCameraCardTimelineCore extends LitElement {
|
||||
timelineConfig: this.timelineConfig,
|
||||
mini: this.mini,
|
||||
thumbnailConfig: this.thumbnailConfig,
|
||||
keys: this.keys,
|
||||
query: this.viewManagerEpoch?.manager.getView()?.query ?? undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,11 @@ import { CameraManager } from '../camera-manager/manager';
|
||||
import { FoldersManager } from '../card-controller/folders/manager';
|
||||
import { ViewItemManager } from '../card-controller/view/item-manager';
|
||||
import { ViewManagerEpoch } from '../card-controller/view/types';
|
||||
import { TimelineKeys } from '../components-lib/timeline/types';
|
||||
import { ConditionStateManagerReadonlyInterface } from '../conditions/types';
|
||||
import { TimelineConfig } from '../config/schema/timeline';
|
||||
import { CardWideConfig } from '../config/schema/types';
|
||||
import { HomeAssistant } from '../ha/types';
|
||||
import basicBlockStyle from '../scss/basic-block.scss';
|
||||
import { QueryClassifier } from '../view/query-classifier';
|
||||
import './surround.js';
|
||||
import './timeline-core.js';
|
||||
|
||||
@@ -40,47 +38,6 @@ export class AdvancedCameraCardTimeline extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cardWideConfig?: CardWideConfig;
|
||||
|
||||
protected _getKeys(): TimelineKeys | undefined {
|
||||
const query = this.viewManagerEpoch?.manager.getView()?.query;
|
||||
|
||||
// If there's a query, try to extract camera IDs or folder info from it.
|
||||
if (QueryClassifier.isMediaQuery(query)) {
|
||||
const cameraIDs = query.getQueryCameraIDs();
|
||||
if (cameraIDs && cameraIDs.size) {
|
||||
return {
|
||||
type: 'camera',
|
||||
cameraIDs,
|
||||
};
|
||||
}
|
||||
} else if (QueryClassifier.isFolderQuery(query)) {
|
||||
const folderConfig = query.getQuery()?.folder;
|
||||
if (folderConfig) {
|
||||
return {
|
||||
type: 'folder',
|
||||
folder: folderConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise fall back to all cameras that support media queries.
|
||||
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability({
|
||||
anyCapabilities: ['clips', 'snapshots', 'recordings'],
|
||||
});
|
||||
const folder = this.foldersManager?.getFolder() ?? null;
|
||||
|
||||
return cameraIDs?.size
|
||||
? {
|
||||
type: 'camera',
|
||||
cameraIDs,
|
||||
}
|
||||
: folder
|
||||
? {
|
||||
type: 'folder',
|
||||
folder,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.timelineConfig) {
|
||||
return html``;
|
||||
@@ -96,7 +53,6 @@ export class AdvancedCameraCardTimeline extends LitElement {
|
||||
.foldersManager=${this.foldersManager}
|
||||
.conditionStateManager=${this.conditionStateManager}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
.keys=${this._getKeys()}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.itemClickAction=${this.timelineConfig.controls.thumbnails.mode === 'none'
|
||||
? 'play'
|
||||
|
||||
@@ -53,7 +53,7 @@ export class AdvancedCameraCardVideoPlayer extends LitElement implements MediaPl
|
||||
);
|
||||
}
|
||||
}}
|
||||
@loadeddata=${(ev: Event) => {
|
||||
@loadeddata="${(ev: Event) => {
|
||||
dispatchMediaLoadedEvent(this, ev, {
|
||||
...(this._mediaPlayerController && {
|
||||
mediaPlayerController: this._mediaPlayerController,
|
||||
@@ -64,7 +64,7 @@ export class AdvancedCameraCardVideoPlayer extends LitElement implements MediaPl
|
||||
},
|
||||
technology: ['mp4'],
|
||||
});
|
||||
}}
|
||||
}}"
|
||||
@volumechange=${() => dispatchMediaVolumeChangeEvent(this)}
|
||||
@play=${() => dispatchMediaPlayEvent(this)}
|
||||
@pause=${() => dispatchMediaPauseEvent(this)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { CameraManager } from '../../camera-manager/manager.js';
|
||||
import { ViewItemManager } from '../../card-controller/view/item-manager.js';
|
||||
import { ViewManagerEpoch } from '../../card-controller/view/types.js';
|
||||
import { MediaGridSelected } from '../../components-lib/media-grid-controller.js';
|
||||
import { CardWideConfig } from '../../config/schema/types.js';
|
||||
@@ -39,6 +40,9 @@ export class AdvancedCameraCardViewerGrid extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cameraManager?: CameraManager;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewItemManager?: ViewItemManager;
|
||||
|
||||
protected _renderCarousel(filterCamera?: string): TemplateResult {
|
||||
const selectedCameraID = this.viewManagerEpoch?.manager.getView()?.camera;
|
||||
|
||||
@@ -60,6 +64,7 @@ export class AdvancedCameraCardViewerGrid extends LitElement {
|
||||
.cameraManager=${this.cameraManager}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.showControls=${!filterCamera || selectedCameraID === filterCamera}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
>
|
||||
</advanced-camera-card-viewer-carousel>
|
||||
`;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { CameraManager } from '../../camera-manager/manager.js';
|
||||
import { ViewItemManager } from '../../card-controller/view/item-manager.js';
|
||||
import { ViewManagerEpoch } from '../../card-controller/view/types.js';
|
||||
import { CardWideConfig } from '../../config/schema/types.js';
|
||||
import { ViewerConfig } from '../../config/schema/viewer.js';
|
||||
@@ -50,6 +51,9 @@ export class AdvancedCameraCardViewer extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public cardWideConfig?: CardWideConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
public viewItemManager?: ViewItemManager;
|
||||
|
||||
@property({ attribute: 'empty', reflect: true, type: Boolean })
|
||||
public isEmpty = false;
|
||||
|
||||
@@ -96,6 +100,7 @@ export class AdvancedCameraCardViewer extends LitElement {
|
||||
.resolvedMediaCache=${this.resolvedMediaCache}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
>
|
||||
</advanced-camera-card-viewer-grid>`;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||
import { CameraManager } from '../../camera-manager/manager.js';
|
||||
import { QueryType } from '../../camera-manager/types.js';
|
||||
import { ViewManagerEpoch } from '../../card-controller/view/types.js';
|
||||
import { LazyLoadController } from '../../components-lib/lazy-load-controller.js';
|
||||
import { ZoomSettingsObserved } from '../../components-lib/zoom/types.js';
|
||||
@@ -29,7 +30,7 @@ import { MediaPlayer, MediaPlayerController, MediaPlayerElement } from '../../ty
|
||||
import { errorToConsole } from '../../utils/basic.js';
|
||||
import { ViewItemClassifier } from '../../view/item-classifier.js';
|
||||
import { VideoContentType, ViewMedia } from '../../view/item.js';
|
||||
import { QueryClassifier } from '../../view/query-classifier.js';
|
||||
import { UnifiedQueryTransformer } from '../../view/unified-query-transformer.js';
|
||||
import '../image-player.js';
|
||||
import { renderProgressIndicator } from '../progress-indicator.js';
|
||||
import '../video-player.js';
|
||||
@@ -85,19 +86,13 @@ export class AdvancedCameraCardViewerProvider extends LitElement implements Medi
|
||||
// If this specific media item has no clip, then do nothing (even if all
|
||||
// the other media items do).
|
||||
!ViewItemClassifier.isEvent(this.media) ||
|
||||
!QueryClassifier.isEventQuery(view.query)
|
||||
!view.query?.hasMediaQueriesOfType(QueryType.Event)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the query to a clips equivalent.
|
||||
const clipQuery = view.query.clone();
|
||||
clipQuery.convertToClipsQueries();
|
||||
|
||||
const queries = clipQuery.getQuery();
|
||||
if (!queries) {
|
||||
return;
|
||||
}
|
||||
const clipQuery = UnifiedQueryTransformer.convertToClips(view.query);
|
||||
|
||||
await this.viewManagerEpoch?.manager.setViewByParametersWithExistingQuery({
|
||||
params: {
|
||||
|
||||
+8
-16
@@ -76,16 +76,14 @@ export class AdvancedCameraCardViews extends LitElement {
|
||||
if (view?.is('live') || this._shouldLivePreload()) {
|
||||
import('./live/index.js');
|
||||
}
|
||||
if (view?.isMediaGalleryView() && !view.is('folders')) {
|
||||
import('./gallery/media-gallery.js');
|
||||
if (view?.isGalleryView()) {
|
||||
import('./gallery/gallery.js');
|
||||
} else if (view?.isViewerView()) {
|
||||
import('./viewer/index.js');
|
||||
} else if (view?.is('image')) {
|
||||
import('./image.js');
|
||||
} else if (view?.is('timeline')) {
|
||||
import('./timeline.js');
|
||||
} else if (view?.is('folders')) {
|
||||
import('./gallery/folder-gallery.js');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,16 +165,18 @@ export class AdvancedCameraCardViews extends LitElement {
|
||||
>
|
||||
</advanced-camera-card-image>`
|
||||
: ``}
|
||||
${!this.hide && view?.isMediaGalleryView() && !view.is('folders')
|
||||
? html` <advanced-camera-card-media-gallery
|
||||
${!this.hide && view?.isGalleryView()
|
||||
? html` <advanced-camera-card-gallery
|
||||
.hass=${this.hass}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
.galleryConfig=${this.config.media_gallery}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.foldersManager=${this.foldersManager}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.conditionStateManager=${this.conditionStateManager}
|
||||
>
|
||||
</advanced-camera-card-media-gallery>`
|
||||
</advanced-camera-card-gallery>`
|
||||
: ``}
|
||||
${!this.hide && view?.isViewerView()
|
||||
? html`
|
||||
@@ -187,6 +187,7 @@ export class AdvancedCameraCardViews extends LitElement {
|
||||
.resolvedMediaCache=${this.resolvedMediaCache}
|
||||
.cameraManager=${this.cameraManager}
|
||||
.cardWideConfig=${this.cardWideConfig}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
>
|
||||
</advanced-camera-card-viewer>
|
||||
`
|
||||
@@ -212,15 +213,6 @@ export class AdvancedCameraCardViews extends LitElement {
|
||||
>
|
||||
</advanced-camera-card-diagnostics>`
|
||||
: ``}
|
||||
${!this.hide && view?.is('folders')
|
||||
? html` <advanced-camera-card-folder-gallery
|
||||
.hass=${this.hass}
|
||||
.viewManagerEpoch=${this.viewManagerEpoch}
|
||||
.viewItemManager=${this.viewItemManager}
|
||||
.galleryConfig=${this.config.media_gallery}
|
||||
.foldersManager=${this.foldersManager}
|
||||
></advanced-camera-card-folder-gallery>`
|
||||
: ``}
|
||||
${
|
||||
// Note: Subtle difference in condition below vs the other views in
|
||||
// order to always render the live view for live.preload mode.
|
||||
|
||||
Reference in New Issue
Block a user