feat: Make cameras optional (#2343)

This commit is contained in:
Dermot Duffy
2026-02-13 20:21:05 -08:00
committed by GitHub
parent 21f9469784
commit 93f1f08e51
61 changed files with 1312 additions and 557 deletions
+18 -21
View File
@@ -1,31 +1,28 @@
import { LitElement, unsafeCSS } from 'lit';
import { customElement } from 'lit/decorators.js';
import { EffectsController } from '../../components-lib/effects/effects-controller';
import { EffectOptions } from '../../components-lib/effects/types';
import { LitElement, PropertyValues, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { EffectsManager } from '../../card-controller/effects/effects-manager';
import effectsStyle from '../../scss/effects.scss';
import { EffectName, EffectsControllerAPI } from '../../types';
@customElement('advanced-camera-card-effects')
export class AdvancedCameraCardEffects
extends LitElement
implements EffectsControllerAPI
{
protected _controller = new EffectsController();
export class AdvancedCameraCardEffects extends LitElement {
@property({ attribute: false })
public effectsManager?: EffectsManager;
public async startEffect(effect: EffectName, options?: EffectOptions): Promise<void> {
await this._controller.startEffect(effect, options);
}
public stopEffect(effect: EffectName): void {
this._controller.stopEffect(effect);
}
public async toggleEffect(effect: EffectName, options?: EffectOptions): Promise<void> {
await this._controller.toggleEffect(effect, options);
protected willUpdate(changedProperties: PropertyValues): void {
if (changedProperties.has('effectsManager')) {
const previousManager: EffectsManager | undefined =
changedProperties.get('effectsManager');
previousManager?.removeContainer();
}
}
protected updated(): void {
this._controller.setContainer(this.renderRoot);
this.effectsManager?.setContainer(this.renderRoot);
}
public disconnectedCallback(): void {
this.effectsManager?.removeContainer();
super.disconnectedCallback();
}
static get styles() {
+17 -1
View File
@@ -10,11 +10,13 @@ import { CameraConfig } from '../config/schema/cameras';
import { ImageViewConfig } from '../config/schema/image';
import { IMAGE_VIEW_ZOOM_TARGET_SENTINEL } from '../const';
import { HomeAssistant } from '../ha/types';
import { localize } from '../localize/localize.js';
import imageStyle from '../scss/image.scss';
import { MediaPlayer, MediaPlayerController, MediaPlayerElement } from '../types.js';
import './image-updating-player';
import { resolveImageMode } from './image-updating-player';
import './media-dimensions-container';
import { renderMessage } from './message.js';
import './zoomer.js';
@customElement('advanced-camera-card-image')
@@ -81,10 +83,24 @@ export class AdvancedCameraCardImage extends LitElement implements MediaPlayer {
}
protected render(): TemplateResult | void {
if (!this.hass || !this.cameraConfig) {
if (!this.hass) {
return;
}
// Determine if this image mode requires a camera
const mode = resolveImageMode({
imageConfig: this.imageConfig,
cameraConfig: this.cameraConfig,
});
if (mode === 'camera' && !this.cameraConfig) {
return renderMessage({
type: 'info',
message: localize('error.no_camera_for_image'),
icon: 'mdi:camera-off',
});
}
return this._renderContainer(html`
<advanced-camera-card-image-updating-player
${ref(this._refImage)}
+4 -1
View File
@@ -109,7 +109,7 @@ export class AdvancedCameraCardLiveCarousel extends LitElement {
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability('live');
const view = this.viewManagerEpoch?.manager.getView();
if (!cameraIDs?.size || !view) {
if (!cameraIDs?.size || !view?.camera) {
return 0;
}
return Math.max(0, Array.from(cameraIDs).indexOf(view.camera));
@@ -248,6 +248,9 @@ export class AdvancedCameraCardLiveCarousel extends LitElement {
}
const cameraID = this.viewFilterCameraID ?? view.camera;
if (!cameraID) {
return {};
}
const currentIndex = cameraIDs.indexOf(cameraID);
if (currentIndex < 0) {
+4 -4
View File
@@ -1,7 +1,7 @@
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import loadingStyle from '../scss/loading.scss';
import { EffectName, EffectsControllerAPI } from '../types';
import { EffectName, EffectsManagerInterface } from '../types';
import { getReleaseVersion } from '../utils/diagnostics';
import './icon';
@@ -33,7 +33,7 @@ const getDateEffect = (): EffectName | null => {
@customElement('advanced-camera-card-loading')
export class AdvancedCameraCardLoading extends LitElement {
@property({ attribute: false })
public effectsControllerAPI?: EffectsControllerAPI | null;
public effectsManager?: EffectsManagerInterface;
@property({ type: Boolean, reflect: true })
public loaded = false;
@@ -60,13 +60,13 @@ export class AdvancedCameraCardLoading extends LitElement {
}
private _startEffect(effect: EffectName): void {
this.effectsControllerAPI?.startEffect(effect, { fadeIn: false });
this.effectsManager?.startEffect(effect, { fadeIn: false });
this._effectName = effect;
}
private _stopEffect(): void {
if (this._effectName) {
this.effectsControllerAPI?.stopEffect(this._effectName);
this.effectsManager?.stopEffect(this._effectName);
}
this._effectName = null;
}
+2 -2
View File
@@ -139,7 +139,7 @@ export class AdvancedCameraCardViews extends LitElement {
? this.config.media_viewer.controls.timeline
: undefined;
const cameraConfig = view
const cameraConfig = view?.camera
? this.cameraManager?.getStore().getCameraConfig(view.camera) ?? null
: null;
@@ -155,7 +155,7 @@ export class AdvancedCameraCardViews extends LitElement {
.viewItemManager=${this.viewItemManager}
.cardWideConfig=${this.cardWideConfig}
>
${!this.hide && view?.is('image') && cameraConfig
${!this.hide && view?.is('image')
? html` <advanced-camera-card-image
.imageConfig=${this.config.image}
.viewManagerEpoch=${this.viewManagerEpoch}