diff --git a/.hass_dev/views/media-player-view.yaml b/.hass_dev/views/media-player-view.yaml index 572f183b0..a41bc782a 100644 --- a/.hass_dev/views/media-player-view.yaml +++ b/.hass_dev/views/media-player-view.yaml @@ -59,7 +59,11 @@ cards: square: false - type: vertical-stack title: Controls - cards: + cards: + - type: custom:mushroom-media-player-card + entity: media_player.lounge_room + name: Source controls + show_source_controls: true - type: custom:mushroom-media-player-card entity: media_player.kitchen name: Volume controls diff --git a/docs/cards/media-player.md b/docs/cards/media-player.md index 54b8bcf80..ef020870e 100644 --- a/docs/cards/media-player.md +++ b/docs/cards/media-player.md @@ -12,7 +12,7 @@ A media player card allows you to control a media player entity. All the options are available in the lovelace editor but you can use `yaml` if you want. | Name | Type | Default | Description | -| :--------------------- | :-------------------------------------------------- | :---------- | :------------------------------------------------------------------------------------- | +|:-----------------------| :-------------------------------------------------- |:------------|:---------------------------------------------------------------------------------------| | `entity` | string | Required | Media Player entity | | `icon` | string | Optional | Custom icon | | `name` | string | Optional | Custom name | @@ -21,8 +21,9 @@ All the options are available in the lovelace editor but you can use `yaml` if y | `primary_info` | `name` `state` `last-changed` `last-updated` `none` | `name` | Info to show as primary info | | `secondary_info` | `name` `state` `last-changed` `last-updated` `none` | `state` | Info to show as secondary info | | `icon_type` | `icon` `entity-picture` `none` | `icon` | Type of icon to display | -| `use_media_info` | boolean | `false` | Use media info instead of name, state and icon when a media is playing | -| `show_volume_level` | boolean | `false` | Show volume level next to media state when media is playing | +| `use_media_info` | boolean | `false` | Use media info instead of name, state and icon when a media is playing | +| `show_volume_level` | boolean | `false` | Show volume level next to media state when media is playing | +| `show_source_controls` | boolean | `false` | Show available media sources | | `media_controls` | list | `[]` | List of controls to display (on_off, shuffle, previous, play_pause_stop, next, repeat) | | `volume_controls` | list | `[]` | List of controls to display (volume_mute, volume_set, volume_buttons) | | `collapsible_controls` | boolean | `false` | Collapse controls when off | diff --git a/src/cards/media-player-card/controls/media-player-source-control.ts b/src/cards/media-player-card/controls/media-player-source-control.ts new file mode 100644 index 000000000..d479b3a1d --- /dev/null +++ b/src/cards/media-player-card/controls/media-player-source-control.ts @@ -0,0 +1,49 @@ +import { html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { computeRTL, HomeAssistant, MediaPlayerEntity } from "../../../ha"; +import { styleMap } from "lit/directives/style-map.js"; + +@customElement("mushroom-media-player-source-control") +export class MediaPlayerSourceControls extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public entity!: MediaPlayerEntity; + + @property({ attribute: false }) public source!: string; + + @property({ attribute: false }) public icon!: string; + + @property() public fill: boolean = false; + + private _handleClick(e: MouseEvent): void { + e.stopPropagation(); + + this.hass.callService("media_player", "select_source", { + entity_id: this.entity.entity_id, + source: this.source + }); + } + + protected render(): TemplateResult { + const rtl = computeRTL(this.hass); + const buttonStyle = {}; + if (this.entity.attributes.source == this.source) { + buttonStyle["--bg-color"] = this.getSourceColor(); + } + + return html` + + + + `; + } + + private getSourceColor(): string { + return `rgb(var(--rgb-media-source-${this.source.toLowerCase()}, var(--rgb-state-media-player)))`; + } +} diff --git a/src/cards/media-player-card/media-player-card-config.ts b/src/cards/media-player-card/media-player-card-config.ts index 07bcaea7c..e1901ab7b 100644 --- a/src/cards/media-player-card/media-player-card-config.ts +++ b/src/cards/media-player-card/media-player-card-config.ts @@ -2,50 +2,51 @@ import { array, assign, boolean, enums, object, optional } from "superstruct"; import { LovelaceCardConfig } from "../../ha"; import { ActionsSharedConfig, actionsSharedConfigStruct } from "../../shared/config/actions-config"; import { - AppearanceSharedConfig, - appearanceSharedConfigStruct, + AppearanceSharedConfig, + appearanceSharedConfigStruct, } from "../../shared/config/appearance-config"; import { EntitySharedConfig, entitySharedConfigStruct } from "../../shared/config/entity-config"; import { lovelaceCardConfigStruct } from "../../shared/config/lovelace-card-config"; export const MEDIA_LAYER_MEDIA_CONTROLS = [ - "on_off", - "shuffle", - "previous", - "play_pause_stop", - "next", - "repeat", + "on_off", + "shuffle", + "previous", + "play_pause_stop", + "next", + "repeat", ] as const; export type MediaPlayerMediaControl = (typeof MEDIA_LAYER_MEDIA_CONTROLS)[number]; export const MEDIA_PLAYER_VOLUME_CONTROLS = [ - "volume_mute", - "volume_set", - "volume_buttons", + "volume_mute", + "volume_set", + "volume_buttons", ] as const; export type MediaPlayerVolumeControl = (typeof MEDIA_PLAYER_VOLUME_CONTROLS)[number]; export type MediaPlayerCardConfig = LovelaceCardConfig & - EntitySharedConfig & - AppearanceSharedConfig & - ActionsSharedConfig & { - use_media_info?: boolean; - show_volume_level?: boolean; - volume_controls?: MediaPlayerVolumeControl[]; - media_controls?: MediaPlayerMediaControl[]; - collapsible_controls?: boolean; - }; + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig & { + use_media_info?: boolean; + show_volume_level?: boolean; + show_source_controls?: boolean; + volume_controls?: MediaPlayerVolumeControl[]; + media_controls?: MediaPlayerMediaControl[]; + collapsible_controls?: boolean; +}; export const mediaPlayerCardConfigStruct = assign( - lovelaceCardConfigStruct, - assign(entitySharedConfigStruct, appearanceSharedConfigStruct, actionsSharedConfigStruct), - object({ - use_media_info: optional(boolean()), - show_volume_level: optional(boolean()), - volume_controls: optional(array(enums(MEDIA_PLAYER_VOLUME_CONTROLS))), - media_controls: optional(array(enums(MEDIA_LAYER_MEDIA_CONTROLS))), - collapsible_controls: optional(boolean()), - }) + lovelaceCardConfigStruct, + assign(entitySharedConfigStruct, appearanceSharedConfigStruct, actionsSharedConfigStruct), + object({ + use_media_info: optional(boolean()), + show_volume_level: optional(boolean()), + volume_controls: optional(array(enums(MEDIA_PLAYER_VOLUME_CONTROLS))), + media_controls: optional(array(enums(MEDIA_LAYER_MEDIA_CONTROLS))), + collapsible_controls: optional(boolean()), + }) ); diff --git a/src/cards/media-player-card/media-player-card-editor.ts b/src/cards/media-player-card/media-player-card-editor.ts index c135b0b74..dd2e2dc1d 100644 --- a/src/cards/media-player-card/media-player-card-editor.ts +++ b/src/cards/media-player-card/media-player-card-editor.ts @@ -23,6 +23,7 @@ export const MEDIA_LABELS = [ "use_media_info", "use_media_artwork", "show_volume_level", + "show_source_controls", "media_controls", "volume_controls", ]; @@ -38,6 +39,7 @@ const computeSchema = memoizeOne((localize: LocalizeFunc, icon?: string): HaForm schema: [ { name: "use_media_info", selector: { boolean: {} } }, { name: "show_volume_level", selector: { boolean: {} } }, + { name: "show_source_controls", selector: { boolean: {} } }, ], }, { diff --git a/src/cards/media-player-card/media-player-card.ts b/src/cards/media-player-card/media-player-card.ts index e3a3d78fc..4a957ef3b 100644 --- a/src/cards/media-player-card/media-player-card.ts +++ b/src/cards/media-player-card/media-player-card.ts @@ -2,16 +2,16 @@ import { css, CSSResultGroup, html, PropertyValues, TemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { - actionHandler, - ActionHandlerEvent, - computeRTL, - handleAction, - hasAction, - HomeAssistant, - isActive, - LovelaceCard, - LovelaceCardEditor, - MediaPlayerEntity, + actionHandler, + ActionHandlerEvent, + computeRTL, + handleAction, + hasAction, + HomeAssistant, + isActive, + LovelaceCard, + LovelaceCardEditor, + MediaPlayerEntity, } from "../../ha"; import "../../shared/badge-icon"; import "../../shared/card"; @@ -24,240 +24,273 @@ import { registerCustomCard } from "../../utils/custom-cards"; import { computeEntityPicture } from "../../utils/info"; import { Layout } from "../../utils/layout"; import { - MEDIA_PLAYER_CARD_EDITOR_NAME, - MEDIA_PLAYER_CARD_NAME, - MEDIA_PLAYER_ENTITY_DOMAINS, + MEDIA_PLAYER_CARD_EDITOR_NAME, + MEDIA_PLAYER_CARD_NAME, + MEDIA_PLAYER_ENTITY_DOMAINS, } from "./const"; import "./controls/media-player-media-control"; import { isMediaControlVisible } from "./controls/media-player-media-control"; import "./controls/media-player-volume-control"; import { isVolumeControlVisible } from "./controls/media-player-volume-control"; +import "./controls/media-player-source-control"; import { MediaPlayerCardConfig } from "./media-player-card-config"; import { - computeMediaIcon, - computeMediaNameDisplay, - computeMediaStateDisplay, - getVolumeLevel, + computeMediaIcon, + computeMediaNameDisplay, + computeMediaStateDisplay, + getVolumeLevel, } from "./utils"; +import { mediaSourceIcon } from "../../utils/icons/media-icon"; type MediaPlayerCardControl = "media_control" | "volume_control"; const CONTROLS_ICONS: Record = { - media_control: "mdi:play-pause", - volume_control: "mdi:volume-high", + media_control: "mdi:play-pause", + volume_control: "mdi:volume-high", }; registerCustomCard({ - type: MEDIA_PLAYER_CARD_NAME, - name: "Mushroom Media Card", - description: "Card for media player entity", + type: MEDIA_PLAYER_CARD_NAME, + name: "Mushroom Media Card", + description: "Card for media player entity", }); @customElement(MEDIA_PLAYER_CARD_NAME) export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { - public static async getConfigElement(): Promise { - await import("./media-player-card-editor"); - return document.createElement(MEDIA_PLAYER_CARD_EDITOR_NAME) as LovelaceCardEditor; - } - - public static async getStubConfig(hass: HomeAssistant): Promise { - const entities = Object.keys(hass.states); - const mediaPlayers = entities.filter((e) => - MEDIA_PLAYER_ENTITY_DOMAINS.includes(e.split(".")[0]) - ); - return { - type: `custom:${MEDIA_PLAYER_CARD_NAME}`, - entity: mediaPlayers[0], - }; - } - - @state() private _config?: MediaPlayerCardConfig; - - @state() private _activeControl?: MediaPlayerCardControl; - - @state() private _controls: MediaPlayerCardControl[] = []; - - _onControlTap(ctrl, e): void { - e.stopPropagation(); - this._activeControl = ctrl; - } - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: MediaPlayerCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; - this.updateControls(); - this.updateVolume(); - } - - protected updated(changedProperties: PropertyValues) { - super.updated(changedProperties); - if (this.hass && changedProperties.has("hass")) { - this.updateControls(); - this.updateVolume(); - } - } - - @state() - private volume?: number; - - updateVolume() { - this.volume = undefined; - if (!this._config || !this.hass || !this._config.entity) return; - - const entity_id = this._config.entity; - const entity = this.hass.states[entity_id] as MediaPlayerEntity; - - if (!entity) return; - const volume = getVolumeLevel(entity); - this.volume = volume != null ? Math.round(volume) : volume; - } - - private onCurrentVolumeChange(e: CustomEvent<{ value?: number }>): void { - if (e.detail.value != null) { - this.volume = e.detail.value; - } - } - - updateControls() { - if (!this._config || !this.hass || !this._config.entity) return; - - const entity_id = this._config.entity; - const entity = this.hass.states[entity_id] as MediaPlayerEntity; - - if (!entity) return; - - const controls: MediaPlayerCardControl[] = []; - if (!this._config.collapsible_controls || isActive(entity)) { - if (isMediaControlVisible(entity, this._config?.media_controls)) { - controls.push("media_control"); - } - if (isVolumeControlVisible(entity, this._config.volume_controls)) { - controls.push("volume_control"); - } - } - - this._controls = controls; - const isActiveControlSupported = this._activeControl - ? controls.includes(this._activeControl) - : false; - this._activeControl = isActiveControlSupported ? this._activeControl : controls[0]; - } - - private _handleAction(ev: ActionHandlerEvent) { - handleAction(this, this.hass!, this._config!, ev.detail.action!); - } - - protected render(): TemplateResult { - if (!this._config || !this.hass || !this._config.entity) { - return html``; - } - - const entity_id = this._config.entity; - const entity = this.hass.states[entity_id] as MediaPlayerEntity; - - const icon = computeMediaIcon(this._config, entity); - const nameDisplay = computeMediaNameDisplay(this._config, entity); - const stateDisplay = computeMediaStateDisplay(this._config, entity, this.hass); - const appearance = computeAppearance(this._config); - const picture = computeEntityPicture(entity, appearance.icon_type); - - const stateValue = - this.volume != null && this._config.show_volume_level - ? `${stateDisplay} - ${this.volume}%` - : stateDisplay; - - const rtl = computeRTL(this.hass); - - return html` + public static async getConfigElement(): Promise { + await import("./media-player-card-editor"); + return document.createElement(MEDIA_PLAYER_CARD_EDITOR_NAME) as LovelaceCardEditor; + } + + public static async getStubConfig(hass: HomeAssistant): Promise { + const entities = Object.keys(hass.states); + const mediaPlayers = entities.filter((e) => + MEDIA_PLAYER_ENTITY_DOMAINS.includes(e.split(".")[0]) + ); + return { + type: `custom:${MEDIA_PLAYER_CARD_NAME}`, + entity: mediaPlayers[0], + }; + } + + @state() private _config?: MediaPlayerCardConfig; + + @state() private _activeControl?: MediaPlayerCardControl; + + @state() private _controls: MediaPlayerCardControl[] = []; + + _onControlTap(ctrl, e): void { + e.stopPropagation(); + this._activeControl = ctrl; + } + + getCardSize(): number | Promise { + return 1; + } + + setConfig(config: MediaPlayerCardConfig): void { + this._config = { + tap_action: { + action: "more-info", + }, + hold_action: { + action: "more-info", + }, + ...config, + }; + this.updateControls(); + this.updateVolume(); + } + + protected updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + if (this.hass && changedProperties.has("hass")) { + this.updateControls(); + this.updateVolume(); + } + } + + @state() + private volume?: number; + + updateVolume() { + this.volume = undefined; + if (!this._config || !this.hass || !this._config.entity) return; + + const entity_id = this._config.entity; + const entity = this.hass.states[entity_id] as MediaPlayerEntity; + + if (!entity) return; + const volume = getVolumeLevel(entity); + this.volume = volume != null ? Math.round(volume) : volume; + } + + private onCurrentVolumeChange(e: CustomEvent<{ value?: number }>): void { + if (e.detail.value != null) { + this.volume = e.detail.value; + } + } + + updateControls() { + if (!this._config || !this.hass || !this._config.entity) return; + + const entity_id = this._config.entity; + const entity = this.hass.states[entity_id] as MediaPlayerEntity; + + if (!entity) return; + + const controls: MediaPlayerCardControl[] = []; + if (!this._config.collapsible_controls || isActive(entity)) { + if (isMediaControlVisible(entity, this._config?.media_controls)) { + controls.push("media_control"); + } + if (isVolumeControlVisible(entity, this._config.volume_controls)) { + controls.push("volume_control"); + } + } + + this._controls = controls; + const isActiveControlSupported = this._activeControl + ? controls.includes(this._activeControl) + : false; + this._activeControl = isActiveControlSupported ? this._activeControl : controls[0]; + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + protected render(): TemplateResult { + if (!this._config || !this.hass || !this._config.entity) { + return html``; + } + + const entity_id = this._config.entity; + const entity = this.hass.states[entity_id] as MediaPlayerEntity; + + const icon = computeMediaIcon(this._config, entity); + const nameDisplay = computeMediaNameDisplay(this._config, entity); + const stateDisplay = computeMediaStateDisplay(this._config, entity, this.hass); + const appearance = computeAppearance(this._config); + const picture = computeEntityPicture(entity, appearance.icon_type); + + const stateValue = + this.volume != null && this._config.show_volume_level + ? `${stateDisplay} - ${this.volume}%` + : stateDisplay; + + const rtl = computeRTL(this.hass); + + return html` ${picture ? this.renderPicture(picture) : this.renderIcon(entity, icon)} ${this.renderBadge(entity)} ${this.renderStateInfo(entity, appearance, nameDisplay, stateValue)}; ${this._controls.length > 0 - ? html` -
- ${this.renderActiveControl(entity, appearance.layout)} - ${this.renderOtherControls()} -
- ` - : null} + ? html` +
+ ${this.renderActiveControl(entity, appearance.layout)} + ${this.renderOtherControls()} +
+ ` + : null} + ${this.renderSourceControls(entity, appearance.layout)}
- `; - } + `; + } - private renderOtherControls(): TemplateResult | null { - const otherControls = this._controls.filter((control) => control != this._activeControl); + private renderOtherControls(): TemplateResult | null { + const otherControls = this._controls.filter((control) => control != this._activeControl); - return html` + return html` ${otherControls.map( - (ctrl) => html` - this._onControlTap(ctrl, e)} - /> - ` + (ctrl) => html` + this._onControlTap(ctrl, e)} + /> + ` )} - `; - } + `; + } - private renderActiveControl(entity: MediaPlayerEntity, layout: Layout): TemplateResult | null { - const media_controls = this._config?.media_controls ?? []; - const volume_controls = this._config?.volume_controls ?? []; + private renderActiveControl(entity: MediaPlayerEntity, layout: Layout): TemplateResult | null { + const mediaControls = this._config?.media_controls ?? []; + const volumeControls = this._config?.volume_controls ?? []; - switch (this._activeControl) { - case "media_control": - return html` + switch (this._activeControl) { + case "media_control": + return html` - `; - case "volume_control": - return html` + `; + case "volume_control": + return html` - `; - default: - return null; - } - } - - static get styles(): CSSResultGroup { - return [ - super.styles, - cardStyle, - css` + `; + default: + return null; + } + } + + private renderSourceControls(entity: MediaPlayerEntity, layout: Layout): TemplateResult | null { + if (!this._config?.show_source_controls) { + return null; + } + + const sourceList = entity.attributes.source_list?.filter((source) => mediaSourceIcon(source)); + if (!sourceList || sourceList?.length == 0) { + return null; + } + + return html` + + ${sourceList.map( + (source) => { + return html` + + + `; + } + )} + + `; + } + + static get styles(): CSSResultGroup { + return [ + super.styles, + cardStyle, + css` mushroom-state-item { cursor: pointer; } @@ -270,6 +303,6 @@ export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { flex: 1; } `, - ]; - } + ]; + } } diff --git a/src/translations/en.json b/src/translations/en.json index ff50e278e..cc9dca9a6 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1,168 +1,169 @@ { - "editor": { - "form": { - "color_picker": { - "values": { - "default": "Default color" - } - }, - "info_picker": { - "values": { - "default": "Default information", - "name": "Name", - "state": "State", - "last-changed": "Last Changed", - "last-updated": "Last Updated", - "none": "None" - } - }, - "icon_type_picker": { - "values": { - "default": "Default type", - "icon": "Icon", - "entity-picture": "Entity picture", - "none": "None" - } - }, - "layout_picker": { - "values": { - "default": "Default layout", - "vertical": "Vertical layout", - "horizontal": "Horizontal layout" - } - }, - "alignment_picker": { - "values": { - "default": "Default alignment", - "start": "Start", - "end": "End", - "center": "Center", - "justify": "Justify" - } - } - }, - "card": { - "generic": { - "icon_color": "Icon color", - "layout": "Layout", - "fill_container": "Fill container", - "primary_info": "Primary information", - "secondary_info": "Secondary information", - "icon_type": "Icon type", - "content_info": "Content", - "use_entity_picture": "Use entity picture?", - "collapsible_controls": "Collapse controls when off", - "icon_animation": "Animate icon when active?" - }, - "light": { - "show_brightness_control": "Brightness control?", - "use_light_color": "Use light color", - "show_color_temp_control": "Temperature color control?", - "show_color_control": "Color control?", - "incompatible_controls": "Some controls may not be displayed if your light does not support the feature." - }, - "fan": { - "show_percentage_control": "Percentage control?", - "show_oscillate_control": "Oscillate control?" - }, - "cover": { - "show_buttons_control": "Control buttons?", - "show_position_control": "Position control?", - "show_tilt_position_control": "Tilt control?" - }, - "alarm_control_panel": { - "show_keypad": "Show keypad" - }, - "template": { - "primary": "Primary information", - "secondary": "Secondary information", - "multiline_secondary": "Multiline secondary?", - "entity_extra": "Used in templates and actions", - "content": "Content", - "badge_icon": "Badge icon", - "badge_color": "Badge color", - "picture": "Picture (will replace the icon)" - }, - "title": { - "title": "Title", - "subtitle": "Subtitle", - "title_tap_action": "Title tap action", - "subtitle_tap_action": "Subtitle tap action" - }, - "chips": { - "alignment": "Alignment" - }, - "weather": { - "show_conditions": "Conditions?", - "show_temperature": "Temperature?" - }, - "update": { - "show_buttons_control": "Control buttons?" - }, - "vacuum": { - "commands": "Commands", - "commands_list": { - "on_off": "Turn on/off" - } - }, - "media-player": { - "use_media_info": "Use media info", - "use_media_artwork": "Use media artwork", - "show_volume_level": "Show volume level", - "media_controls": "Media controls", - "media_controls_list": { - "on_off": "Turn on/off", - "shuffle": "Shuffle", - "previous": "Previous track", - "play_pause_stop": "Play/pause/stop", - "next": "Next track", - "repeat": "Repeat mode" - }, - "volume_controls": "Volume controls", - "volume_controls_list": { - "volume_buttons": "Volume buttons", - "volume_set": "Volume level", - "volume_mute": "Mute" - } - }, - "lock": { - "lock": "Lock", - "unlock": "Unlock", - "open": "Open" - }, - "humidifier": { - "show_target_humidity_control": "Humidity control?" - }, - "climate": { - "show_temperature_control": "Temperature control?", - "hvac_modes": "HVAC Modes" - } + "editor": { + "form": { + "color_picker": { + "values": { + "default": "Default color" + } + }, + "info_picker": { + "values": { + "default": "Default information", + "name": "Name", + "state": "State", + "last-changed": "Last Changed", + "last-updated": "Last Updated", + "none": "None" + } + }, + "icon_type_picker": { + "values": { + "default": "Default type", + "icon": "Icon", + "entity-picture": "Entity picture", + "none": "None" + } + }, + "layout_picker": { + "values": { + "default": "Default layout", + "vertical": "Vertical layout", + "horizontal": "Horizontal layout" + } + }, + "alignment_picker": { + "values": { + "default": "Default alignment", + "start": "Start", + "end": "End", + "center": "Center", + "justify": "Justify" + } + } + }, + "card": { + "generic": { + "icon_color": "Icon color", + "layout": "Layout", + "fill_container": "Fill container", + "primary_info": "Primary information", + "secondary_info": "Secondary information", + "icon_type": "Icon type", + "content_info": "Content", + "use_entity_picture": "Use entity picture?", + "collapsible_controls": "Collapse controls when off", + "icon_animation": "Animate icon when active?" + }, + "light": { + "show_brightness_control": "Brightness control?", + "use_light_color": "Use light color", + "show_color_temp_control": "Temperature color control?", + "show_color_control": "Color control?", + "incompatible_controls": "Some controls may not be displayed if your light does not support the feature." + }, + "fan": { + "show_percentage_control": "Percentage control?", + "show_oscillate_control": "Oscillate control?" + }, + "cover": { + "show_buttons_control": "Control buttons?", + "show_position_control": "Position control?", + "show_tilt_position_control": "Tilt control?" + }, + "alarm_control_panel": { + "show_keypad": "Show keypad" + }, + "template": { + "primary": "Primary information", + "secondary": "Secondary information", + "multiline_secondary": "Multiline secondary?", + "entity_extra": "Used in templates and actions", + "content": "Content", + "badge_icon": "Badge icon", + "badge_color": "Badge color", + "picture": "Picture (will replace the icon)" + }, + "title": { + "title": "Title", + "subtitle": "Subtitle", + "title_tap_action": "Title tap action", + "subtitle_tap_action": "Subtitle tap action" + }, + "chips": { + "alignment": "Alignment" + }, + "weather": { + "show_conditions": "Conditions?", + "show_temperature": "Temperature?" + }, + "update": { + "show_buttons_control": "Control buttons?" + }, + "vacuum": { + "commands": "Commands", + "commands_list": { + "on_off": "Turn on/off" + } + }, + "media-player": { + "use_media_info": "Use media info", + "use_media_artwork": "Use media artwork", + "show_volume_level": "Show volume level", + "show_source_controls": "Show source controls", + "media_controls": "Media controls", + "media_controls_list": { + "on_off": "Turn on/off", + "shuffle": "Shuffle", + "previous": "Previous track", + "play_pause_stop": "Play/pause/stop", + "next": "Next track", + "repeat": "Repeat mode" }, - "chip": { - "sub_element_editor": { - "title": "Chip editor" - }, - "conditional": { - "chip": "Chip" - }, - "chip-picker": { - "chips": "Chips", - "add": "Add chip", - "edit": "Edit", - "clear": "Clear", - "select": "Select chip", - "types": { - "action": "Action", - "alarm-control-panel": "Alarm", - "back": "Back", - "conditional": "Conditional", - "entity": "Entity", - "light": "Light", - "menu": "Menu", - "template": "Template", - "weather": "Weather" - } - } + "volume_controls": "Volume controls", + "volume_controls_list": { + "volume_buttons": "Volume buttons", + "volume_set": "Volume level", + "volume_mute": "Mute" + } + }, + "lock": { + "lock": "Lock", + "unlock": "Unlock", + "open": "Open" + }, + "humidifier": { + "show_target_humidity_control": "Humidity control?" + }, + "climate": { + "show_temperature_control": "Temperature control?", + "hvac_modes": "HVAC Modes" + } + }, + "chip": { + "sub_element_editor": { + "title": "Chip editor" + }, + "conditional": { + "chip": "Chip" + }, + "chip-picker": { + "chips": "Chips", + "add": "Add chip", + "edit": "Edit", + "clear": "Clear", + "select": "Select chip", + "types": { + "action": "Action", + "alarm-control-panel": "Alarm", + "back": "Back", + "conditional": "Conditional", + "entity": "Entity", + "light": "Light", + "menu": "Menu", + "template": "Template", + "weather": "Weather" } + } } + } } diff --git a/src/utils/icons/media-icon.ts b/src/utils/icons/media-icon.ts new file mode 100644 index 000000000..c542a9eb6 --- /dev/null +++ b/src/utils/icons/media-icon.ts @@ -0,0 +1,19 @@ +export function mediaSourceIcon(source: string): string | undefined { + switch (source.toLowerCase()) { + case "bt": + case "bluetooth": + return "mdi:bluetooth-audio"; + case "cd": + case "disc": + case "dvd": + return "mdi:disc"; + case "netflix": + return "mdi:netflix"; + case "spotify": + return "mdi:spotify"; + case "youtube": + return "mdi:youtube"; + default: + return undefined; + } +} diff --git a/src/utils/theme.ts b/src/utils/theme.ts index d7fa5d919..da91d85e1 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -142,4 +142,10 @@ export const themeColorCss = css` --rgb-state-climate-heat-cool: var(--mush-rgb-state-climate-heat-cool, var(--rgb-green)); --rgb-state-climate-idle: var(--mush-rgb-state-climate-idle, var(--rgb-disabled)); --rgb-state-climate-off: var(--mush-rgb-state-climate-off, var(--rgb-disabled)); + + /* Media sources */ + --rgb-media-source-spotify: 30, 215, 96, 0.6; + --rgb-media-source-bluetooth: 0, 130, 252, 0.6; + --rgb-media-source-netflix: 229, 9, 20, 0.6; + --rgb-media-source-youtube: 255, 0, 0, 0.6; `;