import { html, LitElement, unsafeCSS, type CSSResultGroup, type PropertyValues, type TemplateResult, } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import type { CameraManager } from '../camera-manager/manager.js'; import type { FoldersManager } from '../card-controller/folders/manager.js'; import type { ViewItemManager } from '../card-controller/view/item-manager.js'; import { RemoveContextViewModifier } from '../card-controller/view/modifiers/remove-context.js'; import type { ViewManagerEpoch } from '../card-controller/view/types.js'; import { getUpFolderItem, navigateToFolder, navigateToMedia, navigateUp, type FolderNavigationParamaters, } from '../components-lib/navigation.js'; import type { ThumbnailsControlConfig } from '../config/schema/common/controls/thumbnails.js'; import type { CardWideConfig } from '../config/schema/types.js'; import type { HomeAssistant } from '../ha/types.js'; import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss?inline'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; import type { 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 type { ViewItem, ViewMedia } from '../view/item.js'; import { UnifiedQueryBuilder } from '../view/unified-query-builder.js'; import { getReviewedQueryFilterFromQuery } from '../view/utils/query-filter.js'; import './carousel.js'; import './thumbnail/thumbnail.js'; interface ThumbnailMediaSelect { media: ViewMedia; } @customElement('advanced-camera-card-thumbnail-carousel') export class AdvancedCameraCardThumbnailCarousel extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public viewManagerEpoch?: ViewManagerEpoch; @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; @property({ type: Boolean, reflect: true }) public locked?: boolean; private _thumbnails: TemplateResult[] = []; private _builder: UnifiedQueryBuilder | null = null; private _getFolderNavOptions(): FolderNavigationParamaters | undefined { return this._builder && this.viewManagerEpoch ? { builder: this._builder, viewManagerEpoch: this.viewManagerEpoch, } : 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( '--advanced-camera-card-thumbnail-size', `${this.config.size}px`, ); } const direction = this._getDirection(); if (direction) { this.setAttribute('direction', direction); } else { this.removeAttribute('direction'); } } const renderProperties = [ 'cameraManager', 'config', 'transitionEffect', 'viewManagerEpoch', ] as const; if (renderProperties.some((prop) => changedProps.has(prop))) { this._thumbnails = this._renderThumbnails(); } if (changedProps.has('viewManagerEpoch')) { this.style.setProperty( '--advanced-camera-card-carousel-thumbnail-opacity', !this.fadeThumbnails || this._getSelectedSlide() === null ? '1.0' : '0.4', ); } } private _getSelectedSlide(): number | null { const view = this.viewManagerEpoch?.manager.getView(); const selectedIndex = view?.queryResults?.getSelectedIndex() ?? null; if (selectedIndex === null) { return null; } const hasUpFolder = !!getUpFolderItem(view?.query); return hasUpFolder ? selectedIndex + 1 : selectedIndex; } private _handleMediaClick(item: ViewMedia): void { fireAdvancedCameraCardEvent( this, 'thumbnails-carousel:media-select', { media: item }, ); if (this.viewManagerEpoch) { navigateToMedia(item, { viewManagerEpoch: this.viewManagerEpoch, modifiers: [new RemoveContextViewModifier(['timeline', 'mediaViewer'])], }); } } private _renderThumbnail( item: ViewItem, selected: boolean, clickCallback: (item: ViewItem, ev: Event) => void, seekTarget?: Date, filterReviewed?: boolean, ): TemplateResult { const classes = { embla__slide: true, 'slide-selected': selected, }; return html` clickCallback(item, ev)} > `; } private _renderThumbnails(): TemplateResult[] { 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, clickHandler, view?.context?.mediaViewer?.seek, getReviewedQueryFilterFromQuery(view?.query, item), ), ); } return thumbnails; } private _getDirection(): CarouselDirection | null { if (this.config?.mode === 'left' || this.config?.mode === 'right') { return 'vertical'; } else if (this.config?.mode === 'above' || this.config?.mode === 'below') { return 'horizontal'; } return null; } protected render(): TemplateResult | void { if (!this._thumbnails.length || !this.config?.mode || this.config.mode === 'none') { return; } return html` ${this._thumbnails} `; } static get styles(): CSSResultGroup { return unsafeCSS(thumbnailCarouselStyle); } } declare global { interface HTMLElementTagNameMap { 'advanced-camera-card-thumbnail-carousel': AdvancedCameraCardThumbnailCarousel; } }