mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 10:16:46 +00:00
✨ add volume slider to media_player row (#4743)
* ✨ add volume slider to media_player row
* add more controls
* flex slider
* override width
* volume buttons when narrow
* address comments
* Updates for rebase
* attempt to use debounce. not working
* remove log
* fix observer
* address some review comments
* unobserve
* address comments
This commit is contained in:
parent
375abfb95e
commit
54b57e6222
@ -21,14 +21,32 @@ import {
|
|||||||
SUPPORTS_PLAY,
|
SUPPORTS_PLAY,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
|
SUPPORT_TURN_ON,
|
||||||
|
SUPPORT_TURN_OFF,
|
||||||
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
|
SUPPORT_VOLUME_SET,
|
||||||
|
SUPPORT_VOLUME_MUTE,
|
||||||
|
SUPPORT_VOLUME_BUTTONS,
|
||||||
} from "../../../data/media-player";
|
} from "../../../data/media-player";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
|
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||||
|
import { debounce } from "../../../common/util/debounce";
|
||||||
|
|
||||||
@customElement("hui-media-player-entity-row")
|
@customElement("hui-media-player-entity-row")
|
||||||
class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() private _config?: EntityConfig;
|
@property() private _config?: EntityConfig;
|
||||||
|
@property() private _narrow?: boolean = false;
|
||||||
|
@property() private _veryNarrow?: boolean = false;
|
||||||
|
private _resizeObserver?: ResizeObserver;
|
||||||
|
private _debouncedResizeListener = debounce(
|
||||||
|
() => {
|
||||||
|
this._narrow = (this.parentElement?.clientWidth || 0) < 350;
|
||||||
|
this._veryNarrow = (this.parentElement?.clientWidth || 0) < 300;
|
||||||
|
},
|
||||||
|
250,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
public setConfig(config: EntityConfig): void {
|
public setConfig(config: EntityConfig): void {
|
||||||
if (!config || !config.entity) {
|
if (!config || !config.entity) {
|
||||||
@ -38,6 +56,21 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (!this._resizeObserver) {
|
||||||
|
this._attachObserver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
this._resizeObserver?.unobserve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
this._attachObserver();
|
||||||
|
}
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
return hasConfigOrEntityChanged(this, changedProps);
|
return hasConfigOrEntityChanged(this, changedProps);
|
||||||
}
|
}
|
||||||
@ -68,46 +101,128 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
.secondaryText=${this._computeMediaTitle(stateObj)}
|
.secondaryText=${this._computeMediaTitle(stateObj)}
|
||||||
>
|
>
|
||||||
${stateObj.state === "off" || stateObj.state === "idle"
|
${stateObj.state === "off" || stateObj.state === "idle"
|
||||||
|
? supportsFeature(stateObj, SUPPORT_TURN_ON)
|
||||||
|
: supportsFeature(stateObj, SUPPORT_TURN_OFF)
|
||||||
? html`
|
? html`
|
||||||
<div class="text-content">
|
<paper-icon-button
|
||||||
${this.hass!.localize(`state.media_player.${stateObj.state}`) ||
|
icon="hass:power"
|
||||||
this.hass!.localize(`state.default.${stateObj.state}`) ||
|
@click=${this._togglePower}
|
||||||
stateObj.state}
|
></paper-icon-button>
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
: html`
|
: ""}
|
||||||
<div class="controls">
|
|
||||||
${stateObj.state !== "playing" &&
|
|
||||||
!supportsFeature(stateObj, SUPPORTS_PLAY)
|
|
||||||
? ""
|
|
||||||
: html`
|
|
||||||
<paper-icon-button
|
|
||||||
icon="${this._computeControlIcon(stateObj)}"
|
|
||||||
@click="${this._playPause}"
|
|
||||||
></paper-icon-button>
|
|
||||||
`}
|
|
||||||
${supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
|
||||||
? html`
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:skip-next"
|
|
||||||
@click="${this._nextTrack}"
|
|
||||||
></paper-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
</hui-generic-entity-row>
|
</hui-generic-entity-row>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="volume">
|
||||||
|
${supportsFeature(stateObj, SUPPORT_VOLUME_MUTE)
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
.icon=${stateObj.attributes.is_volume_muted
|
||||||
|
? "hass:volume-off"
|
||||||
|
: "hass:volume-high"}
|
||||||
|
@click=${this._toggleMute}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${!this._narrow && supportsFeature(stateObj, SUPPORT_VOLUME_SET)
|
||||||
|
? html`
|
||||||
|
<ha-slider
|
||||||
|
.dir=${computeRTLDirection(this.hass!)}
|
||||||
|
.value=${Number(stateObj.attributes.volume_level) * 100}
|
||||||
|
pin
|
||||||
|
@change=${this._selectedValueChanged}
|
||||||
|
ignore-bar-touch
|
||||||
|
id="input"
|
||||||
|
></ha-slider>
|
||||||
|
`
|
||||||
|
: !this._veryNarrow &&
|
||||||
|
supportsFeature(stateObj, SUPPORT_VOLUME_BUTTONS)
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:volume-minus"
|
||||||
|
@click=${this._volumeDown}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:volume-plus"
|
||||||
|
@click=${this._volumeUp}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
${!this._veryNarrow &&
|
||||||
|
supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:skip-previous"
|
||||||
|
@click=${this._previousTrack}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${stateObj.state !== "playing" &&
|
||||||
|
!supportsFeature(stateObj, SUPPORTS_PLAY)
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon=${this._computeControlIcon(stateObj)}
|
||||||
|
@click=${this._playPause}
|
||||||
|
></paper-icon-button>
|
||||||
|
`}
|
||||||
|
${supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:skip-next"
|
||||||
|
@click=${this._nextTrack}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 48px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.volume {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 2;
|
||||||
|
flex-shrink: 2;
|
||||||
|
}
|
||||||
.controls {
|
.controls {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
ha-slider {
|
||||||
|
flex-grow: 2;
|
||||||
|
flex-shrink: 2;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _attachObserver(): void {
|
||||||
|
if (typeof ResizeObserver !== "function") {
|
||||||
|
import("resize-observer").then((modules) => {
|
||||||
|
modules.install();
|
||||||
|
this._attachObserver();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._resizeObserver = new ResizeObserver(() =>
|
||||||
|
this._debouncedResizeListener()
|
||||||
|
);
|
||||||
|
|
||||||
|
this._resizeObserver.observe(this);
|
||||||
|
}
|
||||||
|
|
||||||
private _computeControlIcon(stateObj: HassEntity): string {
|
private _computeControlIcon(stateObj: HassEntity): string {
|
||||||
if (stateObj.state !== "playing") {
|
if (stateObj.state !== "playing") {
|
||||||
return "hass:play";
|
return "hass:play";
|
||||||
@ -143,19 +258,64 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
return prefix && suffix ? `${prefix}: ${suffix}` : prefix || suffix || "";
|
return prefix && suffix ? `${prefix}: ${suffix}` : prefix || suffix || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _playPause(ev: MouseEvent): void {
|
private _togglePower(): void {
|
||||||
ev.stopPropagation();
|
const stateObj = this.hass!.states[this._config!.entity];
|
||||||
|
|
||||||
|
this.hass!.callService(
|
||||||
|
"media_player",
|
||||||
|
stateObj.state === "off" || stateObj.state === "idle"
|
||||||
|
? "turn_on"
|
||||||
|
: "turn_off",
|
||||||
|
{
|
||||||
|
entity_id: this._config!.entity,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _playPause(): void {
|
||||||
this.hass!.callService("media_player", "media_play_pause", {
|
this.hass!.callService("media_player", "media_play_pause", {
|
||||||
entity_id: this._config!.entity,
|
entity_id: this._config!.entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _nextTrack(ev: MouseEvent): void {
|
private _previousTrack(): void {
|
||||||
ev.stopPropagation();
|
this.hass!.callService("media_player", "media_previous_track", {
|
||||||
|
entity_id: this._config!.entity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _nextTrack(): void {
|
||||||
this.hass!.callService("media_player", "media_next_track", {
|
this.hass!.callService("media_player", "media_next_track", {
|
||||||
entity_id: this._config!.entity,
|
entity_id: this._config!.entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _toggleMute() {
|
||||||
|
this.hass!.callService("media_player", "volume_mute", {
|
||||||
|
entity_id: this._config!.entity,
|
||||||
|
is_volume_muted: !this.hass!.states[this._config!.entity].attributes
|
||||||
|
.is_volume_muted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _volumeDown() {
|
||||||
|
this.hass!.callService("media_player", "volume_down", {
|
||||||
|
entity_id: this._config!.entity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _volumeUp() {
|
||||||
|
this.hass!.callService("media_player", "volume_up", {
|
||||||
|
entity_id: this._config!.entity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _selectedValueChanged(ev): void {
|
||||||
|
this.hass!.callService("media_player", "volume_set", {
|
||||||
|
entity_id: this._config!.entity,
|
||||||
|
volume_level: ev.target.value / 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user