diff --git a/docs/configuration/cameras/README.md b/docs/configuration/cameras/README.md
index be582f66..3a4a5cf9 100644
--- a/docs/configuration/cameras/README.md
+++ b/docs/configuration/cameras/README.md
@@ -334,6 +334,7 @@ cameras:
position:
x: 50
y: 50
+ always_error_if_entity_unavailable: false
- camera_entity: camera.entrance
icon: 'mdi:car'
title: 'Front entrance'
diff --git a/src/camera-manager/frigate/engine-frigate.ts b/src/camera-manager/frigate/engine-frigate.ts
index 615c311c..e0abc817 100644
--- a/src/camera-manager/frigate/engine-frigate.ts
+++ b/src/camera-manager/frigate/engine-frigate.ts
@@ -58,7 +58,6 @@ import {
RecordingSegmentsQueryResultsMap,
} from '../types';
import { getDefaultGo2RTCEndpoint } from '../utils/go2rtc-endpoint';
-import frigateLogo from './assets/frigate.svg';
import { FrigateCamera, isBirdseye } from './camera';
import { FrigateEventWatcher } from './event-watcher';
import { FrigateViewMediaFactory } from './media';
@@ -912,8 +911,8 @@ export class FrigateCameraManagerEngine
hass: HomeAssistant,
cameraConfig: CameraConfig,
): CameraManagerCameraMetadata {
- const metadata = super.getCameraMetadata(hass, cameraConfig);
return {
+ ...super.getCameraMetadata(hass, cameraConfig),
title:
cameraConfig.title ??
getEntityTitle(hass, cameraConfig.camera_entity) ??
@@ -921,8 +920,7 @@ export class FrigateCameraManagerEngine
prettifyTitle(cameraConfig.frigate?.camera_name) ??
cameraConfig.id ??
'',
- icon: metadata.icon,
- engineLogo: frigateLogo,
+ engineIcon: 'frigate',
};
}
diff --git a/src/camera-manager/generic/engine-generic.ts b/src/camera-manager/generic/engine-generic.ts
index f201c852..e756d67c 100644
--- a/src/camera-manager/generic/engine-generic.ts
+++ b/src/camera-manager/generic/engine-generic.ts
@@ -5,7 +5,7 @@ import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/st
import { PTZAction } from '../../config/ptz';
import { ActionPhase, CameraConfig } from '../../config/types';
import { ExtendedHomeAssistant } from '../../types';
-import { getEntityIcon, getEntityTitle } from '../../utils/ha';
+import { getEntityTitle } from '../../utils/ha';
import { ViewMedia } from '../../view/media';
import { Camera } from '../camera';
import { Capabilities } from '../capabilities';
@@ -202,9 +202,11 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
getEntityTitle(hass, cameraConfig.webrtc_card?.entity) ??
cameraConfig.id ??
'',
- icon:
- cameraConfig?.icon ??
- (cameraEntity ? getEntityIcon(hass, cameraEntity, 'mdi:video') : 'mdi:video'),
+ icon: {
+ entity: cameraEntity ?? undefined,
+ icon: cameraConfig.icon,
+ fallback: 'mdi:video',
+ },
};
}
diff --git a/src/camera-manager/motioneye/engine-motioneye.ts b/src/camera-manager/motioneye/engine-motioneye.ts
index 1d3eae28..95f2eca0 100644
--- a/src/camera-manager/motioneye/engine-motioneye.ts
+++ b/src/camera-manager/motioneye/engine-motioneye.ts
@@ -43,7 +43,6 @@ import {
QueryReturnType,
} from '../types';
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';
-import motioneyeLogo from './assets/motioneye.svg';
import { MotionEyeCamera } from './camera';
import { MotionEyeEventQueryResults } from './types';
@@ -420,10 +419,9 @@ export class MotionEyeCameraManagerEngine extends BrowseMediaCameraManagerEngine
hass: HomeAssistant,
cameraConfig: CameraConfig,
): CameraManagerCameraMetadata {
- const metadata = super.getCameraMetadata(hass, cameraConfig);
return {
- ...metadata,
- engineLogo: motioneyeLogo,
+ ...super.getCameraMetadata(hass, cameraConfig),
+ engineIcon: 'motioneye',
};
}
diff --git a/src/camera-manager/reolink/engine-reolink.ts b/src/camera-manager/reolink/engine-reolink.ts
index de53d98c..06815717 100644
--- a/src/camera-manager/reolink/engine-reolink.ts
+++ b/src/camera-manager/reolink/engine-reolink.ts
@@ -38,7 +38,6 @@ import {
QueryReturnType,
} from '../types';
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';
-import reolinkLogo from './assets/reolink.svg';
import { ReolinkCamera } from './camera';
import { BrowseMediaReolinkCameraMetadata, ReolinkEventQueryResults } from './types';
@@ -395,10 +394,9 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine {
hass: HomeAssistant,
cameraConfig: CameraConfig,
): CameraManagerCameraMetadata {
- const metadata = super.getCameraMetadata(hass, cameraConfig);
return {
- ...metadata,
- engineLogo: reolinkLogo,
+ ...super.getCameraMetadata(hass, cameraConfig),
+ engineIcon: 'reolink',
};
}
diff --git a/src/camera-manager/types.ts b/src/camera-manager/types.ts
index c80def31..e08d1052 100644
--- a/src/camera-manager/types.ts
+++ b/src/camera-manager/types.ts
@@ -1,4 +1,4 @@
-import { CapabilityKey } from '../types';
+import { CapabilityKey, Icon } from '../types';
import { FrigateCardView, SSLCiphers } from '../config/types';
import { ViewMedia } from '../view/media';
@@ -106,8 +106,10 @@ export interface CameraManagerMediaCapabilities {
export interface CameraManagerCameraMetadata {
title: string;
- icon: string;
- engineLogo?: string;
+ icon: Icon;
+
+ // Engine icon is just a string since it will never be entity-derived.
+ engineIcon?: string;
}
export interface CameraEndpointsContext {
diff --git a/src/card-controller/media-player-manager.ts b/src/card-controller/media-player-manager.ts
index 023415c7..fd0d065f 100644
--- a/src/card-controller/media-player-manager.ts
+++ b/src/card-controller/media-player-manager.ts
@@ -7,7 +7,7 @@ import {
import { localize } from '../localize/localize';
import { errorToConsole } from '../utils/basic';
import { Entity } from '../utils/ha/registry/entity/types';
-import { supportsFeature } from '../utils/ha/update';
+import { supportsFeature } from '../utils/ha';
import { ViewMedia } from '../view/media';
import { ViewMediaClassifier } from '../view/media-classifier';
import { CardMediaPlayerAPI } from './types';
diff --git a/src/card-controller/status-bar-item-manager.ts b/src/card-controller/status-bar-item-manager.ts
index 4168183f..11efce02 100644
--- a/src/card-controller/status-bar-item-manager.ts
+++ b/src/card-controller/status-bar-item-manager.ts
@@ -45,7 +45,7 @@ export class StatusBarItemManager {
const cameraMetadata = options?.view
? options?.cameraManager?.getCameraMetadata(options?.view?.camera)
: null;
- const engineLogoIcon = cameraMetadata?.engineLogo ?? null;
+ const engineIcon = cameraMetadata?.engineIcon ?? null;
const title = options?.view?.is('live')
? cameraMetadata?.title ?? null
: options?.view?.isViewerView()
@@ -99,11 +99,11 @@ export class StatusBarItemManager {
]
: []),
- ...(engineLogoIcon
+ ...(engineIcon
? [
{
- type: 'custom:frigate-card-status-bar-image' as const,
- image: engineLogoIcon,
+ type: 'custom:frigate-card-status-bar-icon' as const,
+ icon: engineIcon,
...options?.statusConfig?.items.engine,
},
]
diff --git a/src/components-lib/icon-controller.ts b/src/components-lib/icon-controller.ts
new file mode 100644
index 00000000..4b4b4ab7
--- /dev/null
+++ b/src/components-lib/icon-controller.ts
@@ -0,0 +1,44 @@
+import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
+import frigateSVG from '../camera-manager/frigate/assets/frigate.svg';
+import motioneyeSVG from '../camera-manager/motioneye/assets/motioneye.svg';
+import reolinkSVG from '../camera-manager/reolink/assets/reolink.svg';
+import { Icon } from '../types';
+import { HassEntity } from 'home-assistant-js-websocket';
+
+export class IconController {
+ public getCustomIcon(icon?: Icon): string | null {
+ switch (icon?.icon) {
+ case 'frigate':
+ return frigateSVG;
+ case 'motioneye':
+ return motioneyeSVG;
+ case 'reolink':
+ return reolinkSVG;
+ default:
+ return null;
+ }
+ }
+
+ public createStateObjectForStateBadge(
+ hass: HomeAssistant,
+ entityID: string,
+ ): HassEntity | null {
+ if (!hass.states[entityID]) {
+ return null;
+ }
+ return {
+ ...hass.states[entityID],
+ attributes: {
+ ...hass.states[entityID].attributes,
+
+ // State badge is the only available component that will allow the
+ // Home Assistant frontend to correctly color based on the state, but
+ // it also will render an image (instead of an icon) if one is present
+ // in the attributes. By overriding the below attributes, we avoid
+ // that behavior.
+ entity_picture: undefined,
+ entity_picture_local: undefined,
+ },
+ };
+ }
+}
diff --git a/src/components-lib/menu-button-controller.ts b/src/components-lib/menu-button-controller.ts
index e76aa22a..b6ff9eec 100644
--- a/src/components-lib/menu-button-controller.ts
+++ b/src/components-lib/menu-button-controller.ts
@@ -23,7 +23,7 @@ import {
} from '../utils/action';
import { isTruthy } from '../utils/basic';
import { isBeingCasted } from '../utils/casting';
-import { getEntityIcon, getEntityTitle } from '../utils/ha';
+import { getEntityTitle } from '../utils/ha';
import { getPTZTarget } from '../utils/ptz';
import { getStreamCameraID, hasSubstream } from '../utils/substream';
import { View } from '../view/view';
@@ -134,30 +134,27 @@ export class MenuButtonController {
// current view for a less surprising UX.
const menuCameraIDs = cameraManager.getStore().getCameraIDsWithCapability('menu');
if (menuCameraIDs.size > 1) {
- const menuItems = Array.from(
- cameraManager.getStore().getCameraConfigEntries(menuCameraIDs),
- ([cameraID, config]) => {
- const action = createCameraAction('camera_select', cameraID);
- const metadata = cameraManager.getCameraMetadata(cameraID);
+ const submenuItems = Array.from(menuCameraIDs, (cameraID) => {
+ const action = createCameraAction('camera_select', cameraID);
+ const metadata = cameraManager.getCameraMetadata(cameraID);
- return {
- enabled: true,
- icon: metadata?.icon,
- entity: config.camera_entity,
- state_color: true,
- title: metadata?.title,
- selected: view?.camera === cameraID,
- ...(action && { tap_action: action }),
- };
- },
- );
+ return {
+ enabled: true,
+ icon: metadata?.icon.icon,
+ entity: metadata?.icon.entity,
+ state_color: true,
+ title: metadata?.title,
+ selected: view?.camera === cameraID,
+ ...(action && { tap_action: action }),
+ };
+ });
return {
icon: 'mdi:video-switch',
...config.menu.buttons.cameras,
type: 'custom:frigate-card-menu-submenu',
title: localize('config.menu.buttons.cameras'),
- items: menuItems,
+ items: submenuItems,
};
}
return null;
@@ -201,11 +198,10 @@ export class MenuButtonController {
const menuItems = Array.from(streams, (streamID) => {
const action = createCameraAction('live_substream_select', streamID);
const metadata = cameraManager.getCameraMetadata(streamID) ?? undefined;
- const cameraConfig = cameraManager.getStore().getCameraConfig(streamID);
return {
enabled: true,
- icon: metadata?.icon,
- entity: cameraConfig?.camera_entity,
+ icon: metadata?.icon.icon,
+ entity: metadata?.icon.entity,
state_color: true,
title: metadata?.title,
selected: substreamAwareCameraID === streamID,
@@ -465,7 +461,6 @@ export class MenuButtonController {
return {
enabled: true,
selected: false,
- icon: getEntityIcon(hass, playerEntityID),
entity: playerEntityID,
state_color: false,
title: title,
diff --git a/src/components-lib/menu-controller.ts b/src/components-lib/menu-controller.ts
index 9e93d9fa..a5fb4a8f 100644
--- a/src/components-lib/menu-controller.ts
+++ b/src/components-lib/menu-controller.ts
@@ -1,4 +1,4 @@
-import { HASSDomEvent, HomeAssistant } from '@dermotduffy/custom-card-helpers';
+import { HASSDomEvent } from '@dermotduffy/custom-card-helpers';
import { LitElement } from 'lit';
import { orderBy } from 'lodash-es';
import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js';
@@ -9,13 +9,11 @@ import {
type MenuConfig,
type MenuItem,
} from '../config/types.js';
-import { StateParameters } from '../types.js';
import {
convertActionToCardCustomAction,
getActionConfigGivenAction,
} from '../utils/action';
import { arrayify, isTruthy, setOrRemoveAttribute } from '../utils/basic.js';
-import { refreshDynamicStateParameters } from '../utils/ha/index.js';
export class MenuController {
protected _host: LitElement;
@@ -158,13 +156,6 @@ export class MenuController {
}
}
- public getFreshButtonState(hass: HomeAssistant, button: MenuItem): StateParameters {
- const stateParameters = { ...button } as StateParameters;
- return hass && button.type === 'custom:frigate-card-menu-state-icon'
- ? refreshDynamicStateParameters(hass, stateParameters)
- : stateParameters;
- }
-
protected _sortButtons(): void {
this._buttons = orderBy(
this._buttons,
diff --git a/src/components/date-picker.ts b/src/components/date-picker.ts
index 24c7456c..6401f719 100644
--- a/src/components/date-picker.ts
+++ b/src/components/date-picker.ts
@@ -5,6 +5,7 @@ import { localize } from '../localize/localize';
import datePickerStyle from '../scss/date-picker.scss';
import { stopEventFromActivatingCardWideActions } from '../utils/action';
import { dispatchFrigateCardEvent } from '../utils/basic';
+import './icon';
export interface DatePickerEvent {
date: Date | null;
@@ -44,16 +45,16 @@ export class FrigateCardDatePicker extends LitElement {
@input=${() => changed()}
@change=${() => changed()}
/>
- {
stopEventFromActivatingCardWideActions(ev);
this._refInput.value?.showPicker();
}}
>
- `;
+ `;
}
static get styles(): CSSResultGroup {
diff --git a/src/components/drawer.ts b/src/components/drawer.ts
index 6ea33139..2913ef69 100644
--- a/src/components/drawer.ts
+++ b/src/components/drawer.ts
@@ -14,6 +14,7 @@ import drawerInjectStyle from '../scss/drawer-inject.scss';
import drawerStyle from '../scss/drawer.scss';
import { stopEventFromActivatingCardWideActions } from '../utils/action';
import { getChildrenFromElement, isHoverableDevice } from '../utils/basic';
+import './icon';
export interface DrawerIcons {
open?: string;
@@ -121,11 +122,13 @@ export class FrigateCardDrawer extends LitElement {
this.open = !this.open;
}}
>
- {
// Only open the drawer on mousenter when the device
// supports hover (otherwise iOS may end up passing on
@@ -136,7 +139,7 @@ export class FrigateCardDrawer extends LitElement {
}
}}
>
-
+
`
: ''}
diff --git a/src/components/icon.ts b/src/components/icon.ts
new file mode 100644
index 00000000..e540d3b0
--- /dev/null
+++ b/src/components/icon.ts
@@ -0,0 +1,54 @@
+import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
+import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { IconController } from '../components-lib/icon-controller';
+import iconStyle from '../scss/icon.scss';
+import { Icon } from '../types';
+
+@customElement('frigate-card-icon')
+export class FrigateCardIcon extends LitElement {
+ @property({ attribute: false })
+ public hass?: HomeAssistant;
+
+ @property({ attribute: false })
+ public icon?: Icon;
+
+ private _controller = new IconController();
+
+ protected render(): TemplateResult {
+ const customIconURL = this._controller.getCustomIcon(this.icon);
+ if (customIconURL) {
+ return html`
`;
+ }
+ if (this.icon?.icon) {
+ return html``;
+ }
+ if (this.hass && this.icon?.entity) {
+ const stateObj = this._controller.createStateObjectForStateBadge(
+ this.hass,
+ this.icon.entity,
+ );
+ if (stateObj) {
+ return html``;
+ }
+ }
+ if (this.icon?.fallback) {
+ return html``;
+ }
+ return html``;
+ }
+
+ static get styles(): CSSResultGroup {
+ return unsafeCSS(iconStyle);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'frigate-card-icon': FrigateCardIcon;
+ }
+}
diff --git a/src/components/key-assigner.ts b/src/components/key-assigner.ts
index 3312dfec..d5af5717 100644
--- a/src/components/key-assigner.ts
+++ b/src/components/key-assigner.ts
@@ -12,6 +12,7 @@ import { KeyAssignerController } from '../components-lib/key-assigner-controller
import { KeyboardShortcut } from '../config/keyboard-shortcuts';
import keyAssignerStyle from '../scss/key-assigner.scss';
import { localize } from '../localize/localize';
+import './icon';
@customElement('frigate-card-key-assigner')
export class FrigateCardKeyAssigner extends LitElement {
@@ -48,15 +49,11 @@ export class FrigateCardKeyAssigner extends LitElement {
this._controller.toggleAssigning();
}}
>
-
+
- ${
- this._controller.isAssigning()
- ? localize('key_assigner.assigning')
- : localize('key_assigner.assign')
- }
+ ${this._controller.isAssigning() ? '' : localize('key_assigner.assign')}
${
@@ -66,7 +63,9 @@ export class FrigateCardKeyAssigner extends LitElement {
this._controller.setValue(null);
}}
>
-
+
${localize('key_assigner.unassign')}
`
: ''
diff --git a/src/components/live/provider.ts b/src/components/live/provider.ts
index 39b33c62..3d3ac811 100644
--- a/src/components/live/provider.ts
+++ b/src/components/live/provider.ts
@@ -11,6 +11,7 @@ import { classMap } from 'lit/directives/class-map.js';
import { guard } from 'lit/directives/guard.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraEndpoints } from '../../camera-manager/types.js';
+import { dispatchLiveErrorEvent } from '../../components-lib/live/utils/dispatch-live-error.js';
import { PartialZoomSettings } from '../../components-lib/zoom/types.js';
import {
CameraConfig,
@@ -26,11 +27,11 @@ import { aspectRatioToString } from '../../utils/basic.js';
import { dispatchMediaUnloadedEvent } from '../../utils/media-info.js';
import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js';
import { playMediaMutingIfNecessary } from '../../utils/media.js';
+import '../icon.js';
import { renderMessage } from '../message.js';
import '../next-prev-control.js';
import '../ptz.js';
import '../surround.js';
-import { dispatchLiveErrorEvent } from '../../components-lib/live/utils/dispatch-live-error.js';
@customElement('frigate-card-live-provider')
export class FrigateCardLiveProvider
@@ -385,10 +386,10 @@ export class FrigateCardLiveProvider
: html``}
`)}
${showImageDuringLoading && !this._isVideoMediaLoaded
- ? html``
+ .icon=${{ icon: 'mdi:progress-helper' }}
+ >`
: ''} `;
}
diff --git a/src/components/menu.ts b/src/components/menu.ts
index dc20faf8..df47f1d9 100644
--- a/src/components/menu.ts
+++ b/src/components/menu.ts
@@ -1,15 +1,15 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators.js';
-import { ifDefined } from 'lit/directives/if-defined.js';
import { styleMap } from 'lit/directives/style-map.js';
import { actionHandler } from '../action-handler-directive.js';
import { MenuController } from '../components-lib/menu-controller.js';
import type { MenuConfig, MenuItem } from '../config/types.js';
import menuStyle from '../scss/menu.scss';
import { frigateCardHasAction } from '../utils/action.js';
-import { getCustomIconURL } from '../utils/custom-icons.js';
+import { getEntityTitle } from '../utils/ha/index.js';
import { EntityRegistryManager } from '../utils/ha/registry/entity/index.js';
+import './icon.js';
import './submenu.js';
@customElement('frigate-card-menu')
@@ -60,38 +60,30 @@ export class FrigateCardMenu extends LitElement {
`;
}
- // =====================================================================================
- // For `data-domain` and `data-state`, see: See
- // https://github.com/home-assistant/frontend/blob/dev/src/components/entity/state-badge.ts#L54
- // =====================================================================================
- // Buttons are styled in a few ways (in order of precedence):
- //
- // - User provided style
- // - Color/Brightness styling for the `light` domain (calculated in
- // `refreshDynamicStateParameters`)
- // - Static styling based on domain (`data-domain`) and state
- // (`data-state`). This looks up a CSS style in `menu.scss`.
-
- const buttonState = this._controller.getFreshButtonState(this.hass, button);
- const customImageURL = getCustomIconURL(buttonState.icon);
+ const title =
+ this.hass && button.type === 'custom:frigate-card-menu-state-icon' && !button.title
+ ? getEntityTitle(this.hass, button.entity)
+ : button.title;
return html` this._controller.actionHandler(ev, button)}
>
- ${customImageURL
- ? html`
`
- : html``}
+
`;
}
diff --git a/src/components/message.ts b/src/components/message.ts
index 20fd56b6..41b344cd 100644
--- a/src/components/message.ts
+++ b/src/components/message.ts
@@ -9,6 +9,7 @@ import { localize } from '../localize/localize.js';
import messageStyle from '../scss/message.scss';
import { FrigateCardError, Message, MessageType } from '../types.js';
import { dispatchFrigateCardEvent } from '../utils/basic.js';
+import './icon.js';
@customElement('frigate-card-message')
export class FrigateCardMessage extends LitElement {
@@ -38,7 +39,7 @@ export class FrigateCardMessage extends LitElement {
return html`
-
+
@@ -103,9 +104,11 @@ export class FrigateCardProgressIndicator extends LitElement {
protected render(): TemplateResult {
return html`
${this.animated
- ? html`
+ ? html`
`
- : html``}
+ : html``}
${this.message ? html`${this.message}` : html``}
`;
}
diff --git a/src/components/next-prev-control.ts b/src/components/next-prev-control.ts
index 83b5695b..7b1c4a6d 100644
--- a/src/components/next-prev-control.ts
+++ b/src/components/next-prev-control.ts
@@ -4,6 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { NextPreviousControlConfig } from '../config/types.js';
import controlStyle from '../scss/next-previous-control.scss';
+import { Icon } from '../types.js';
import { renderTask } from '../utils/task.js';
import { createFetchThumbnailTask } from '../utils/thumbnail.js';
@@ -29,7 +30,7 @@ export class FrigateCardNextPreviousControl extends LitElement {
public thumbnail?: string;
@property({ attribute: false })
- public icon?: string;
+ public icon?: Icon;
@property({ attribute: true, type: Boolean })
public disabled = false;
@@ -67,17 +68,14 @@ export class FrigateCardNextPreviousControl extends LitElement {
if (renderIcon) {
const icon =
- !this.thumbnail ||
- !this.icon ||
- this._controlConfig.style === 'chevrons' ||
- this._thumbnailError
- ? this.side === 'left'
- ? 'mdi:chevron-left'
- : 'mdi:chevron-right'
- : this.icon;
+ this.icon && !this._thumbnailError && this._controlConfig.style !== 'chevrons'
+ ? this.icon
+ : this.side === 'left'
+ ? { icon: 'mdi:chevron-left' }
+ : { icon: 'mdi:chevron-right' };
return html`
-
+
`;
}
diff --git a/src/components/ptz.ts b/src/components/ptz.ts
index 45355010..d9639a50 100644
--- a/src/components/ptz.ts
+++ b/src/components/ptz.ts
@@ -17,6 +17,7 @@ import { Actions, PTZControlsConfig } from '../config/types.js';
import { localize } from '../localize/localize.js';
import ptzStyle from '../scss/ptz.scss';
import { frigateCardHasAction } from '../utils/action.js';
+import './icon.js';
@customElement('frigate-card-ptz')
export class FrigateCardPTZ extends LitElement {
@@ -67,9 +68,9 @@ export class FrigateCardPTZ extends LitElement {
};
return actions
- ? html`) =>
this._controller.handleAction(ev, actions)}
- >`
+ >`
: html``;
};
diff --git a/src/components/status-bar.ts b/src/components/status-bar.ts
index 3948c1be..33750278 100644
--- a/src/components/status-bar.ts
+++ b/src/components/status-bar.ts
@@ -13,7 +13,7 @@ import { StatusBarController } from '../components-lib/status-bar-controller';
import { StatusBarConfig, StatusBarItem } from '../config/types';
import statusStyle from '../scss/status.scss';
import { frigateCardHasAction } from '../utils/action';
-import { getCustomIconURL } from '../utils/custom-icons.js';
+import './icon.js';
@customElement('frigate-card-status-bar')
export class FrigateCardStatusBar extends LitElement {
@@ -68,20 +68,12 @@ export class FrigateCardStatusBar extends LitElement {
${item.string}
`;
} else if (item.type === 'custom:frigate-card-status-bar-icon') {
- const customIconURL = getCustomIconURL(item.icon);
- return customIconURL
- ? html`

this._controller.actionHandler(ev, item.actions)}
- />`
- : html`
this._controller.actionHandler(ev, item.actions)}
- >`;
+ return html`
this._controller.actionHandler(ev, item.actions)}
+ >`;
} else if (item.type === 'custom:frigate-card-status-bar-image') {
return html`
![]()
{
- if (stateParameters.icon) {
- const url = getCustomIconURL(stateParameters.icon);
- return url
- ? html`

`
- : html`
- `;
- }
- return html``;
- };
+ const title = item.title ?? getEntityTitle(this.hass, item.entity);
return html`
{
// Attach the action config so ascendants have access to it.
ev.detail.config = item;
@@ -80,9 +61,17 @@ export class FrigateCardSubmenu extends LitElement {
hasDoubleClick: frigateCardHasAction(item.double_tap_action),
})}
>
- ${stateParameters.title || ''}
+ ${title ?? ''}
${item.subtitle ? html`${item.subtitle}` : ''}
- ${getIcon(stateParameters)}
+
`;
}
@@ -118,7 +107,14 @@ export class FrigateCardSubmenu extends LitElement {
hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
})}
>
-
+
${items.map(this._renderItem.bind(this))}
@@ -207,18 +203,19 @@ export class FrigateCardSubmenuSelect extends LitElement {
return;
}
+ const title = getEntityTitle(this.hass, entityID);
const submenu: MenuSubmenu = {
- // Default icon. It should be impossible for this to be used, since
- // this.submenuSelect will always have an entity, which means
- // refreshDynamicStateParameters will always return an icon.
- icon: domainIcon('select'),
-
- // Pull out the dynamic properties (like icon, and title) from the state.
- ...refreshDynamicStateParameters(this.hass, this.submenuSelect as StateParameters),
+ ...(title && { title }),
// Override it with anything explicitly set in the submenuSelect.
...this.submenuSelect,
+ icon: {
+ icon: this.submenuSelect.icon,
+ entity: entityID,
+ fallback: 'mdi:format-list-bulleted',
+ },
+
type: 'custom:frigate-card-menu-submenu',
items: [],
};
diff --git a/src/components/thumbnail.ts b/src/components/thumbnail.ts
index 6070487b..056c692f 100644
--- a/src/components/thumbnail.ts
+++ b/src/components/thumbnail.ts
@@ -105,10 +105,10 @@ export class FrigateCardThumbnailFeatureThumbnail extends LitElement {
}
protected render(): TemplateResult | void {
- const imageOff = html`
`;
+ > `;
if (!this._embedThumbnailTask || this._thumbnailError) {
return imageOff;
@@ -150,8 +150,11 @@ export class FrigateCardThumbnailFeatureText extends LitElement {
return;
}
return html`
- ${this.cameraMetadata?.engineLogo
- ? html`

`
+ ${this.cameraMetadata?.engineIcon
+ ? html`
`
: ''}
${format(this.date, 'HH:mm')}
@@ -211,18 +214,18 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement {
${startTime
? html`
-
+ .icon=${{ icon: 'mdi:calendar-clock-outline' }}
+ >
${startTime}
${duration || inProgress
? html`
-
+ .icon=${{ icon: 'mdi:clock-outline' }}
+ >
${duration ? html`
${duration}` : ''}
${inProgress
? html`
${inProgress}`
@@ -232,31 +235,37 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement {
: ''}
${this.cameraTitle
? html`
-
+
${this.cameraTitle}
`
: ''}
${where
? html`
-
+ .icon=${{ icon: 'mdi:map-marker-outline' }}
+ >
${where}
`
: html``}
${tags
? html`
-
+
${tags}
`
: html``}
${seek
? html`
-
+ .icon=${{ icon: 'mdi:clock-fast' }}
+ >
${seek}
`
: html``}
@@ -306,18 +315,18 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement {
${startTime
? html`
-
+ .icon=${{ icon: 'mdi:calendar-clock-outline' }}
+ >
${startTime}
${duration || inProgress
? html`
-
+ .icon=${{ icon: 'mdi:clock-outline' }}
+ >
${duration ? html`
${duration}` : ''}
${inProgress
? html`
${inProgress}`
@@ -327,19 +336,19 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement {
: ''}
${seek
? html`
-
+ .icon=${{ icon: 'mdi:clock-fast' }}
+ >
${seek}
`
: html``}
${eventCount !== null
? html`
-
+ .icon=${{ icon: 'mdi:shield-alert' }}
+ >
${eventCount}
`
: ``}
@@ -448,10 +457,10 @@ export class FrigateCardThumbnail extends LitElement {
>`
: html``}
${shouldShowFavoriteControl
- ? html`
{
stopEventFromActivatingCardWideActions(ev);
if (this.hass && this.media) {
@@ -467,7 +476,7 @@ export class FrigateCardThumbnail extends LitElement {
this.requestUpdate();
}
}}
- />`
+ />`
: ``}
${this.details && ViewMediaClassifier.isEvent(this.media)
? html`
`
: html``}
${shouldShowTimelineControl
- ? html` {
stopEventFromActivatingCardWideActions(ev);
@@ -503,12 +512,12 @@ export class FrigateCardThumbnail extends LitElement {
modifiers: [new RemoveContextViewModifier(['timeline'])],
});
}}
- >`
+ >`
: ''}
${shouldShowDownloadControl
- ? html` {
stopEventFromActivatingCardWideActions(ev);
@@ -520,7 +529,7 @@ export class FrigateCardThumbnail extends LitElement {
}
}
}}
- >`
+ >`
: ``}
`;
}
diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts
index d204c58d..d0525222 100644
--- a/src/components/timeline-core.ts
+++ b/src/components/timeline-core.ts
@@ -69,6 +69,7 @@ import { MediaQueriesResults } from '../view/media-queries-results';
import { mergeViewContext } from '../view/view';
import './date-picker.js';
import { DatePickerEvent, FrigateCardDatePicker } from './date-picker.js';
+import './icon';
import './thumbnail.js';
interface FrigateCardGroupData {
@@ -304,8 +305,8 @@ export class FrigateCardTimelineCore extends LitElement {
>