const t$5 = globalThis, e$6 = t$5.ShadowRoot && (void 0 === t$5.ShadyCSS || t$5.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$5 = /* @__PURE__ */ Symbol(), o$6 = /* @__PURE__ */ new WeakMap();
let n$7 = class n {
constructor(t2, e2, o2) {
if (this._$cssResult$ = true, o2 !== s$5) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
this.cssText = t2, this.t = e2;
}
get styleSheet() {
let t2 = this.o;
const s2 = this.t;
if (e$6 && void 0 === t2) {
const e2 = void 0 !== s2 && 1 === s2.length;
e2 && (t2 = o$6.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$6.set(s2, t2));
}
return t2;
}
toString() {
return this.cssText;
}
};
const r$7 = (t2) => new n$7("string" == typeof t2 ? t2 : t2 + "", void 0, s$5), i$8 = (t2, ...e2) => {
const o2 = 1 === t2.length ? t2[0] : e2.reduce(((e3, s2, o3) => e3 + ((t3) => {
if (true === t3._$cssResult$) return t3.cssText;
if ("number" == typeof t3) return t3;
throw Error("Value passed to 'css' function must be a 'css' function result: " + t3 + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.");
})(s2) + t2[o3 + 1]), t2[0]);
return new n$7(o2, t2, s$5);
}, S$1 = (s2, o2) => {
if (e$6) s2.adoptedStyleSheets = o2.map(((t2) => t2 instanceof CSSStyleSheet ? t2 : t2.styleSheet));
else for (const e2 of o2) {
const o3 = document.createElement("style"), n3 = t$5.litNonce;
void 0 !== n3 && o3.setAttribute("nonce", n3), o3.textContent = e2.cssText, s2.appendChild(o3);
}
}, c$5 = e$6 ? (t2) => t2 : (t2) => t2 instanceof CSSStyleSheet ? ((t3) => {
let e2 = "";
for (const s2 of t3.cssRules) e2 += s2.cssText;
return r$7(e2);
})(t2) : t2;
const { is: i$7, defineProperty: e$5, getOwnPropertyDescriptor: h$3, getOwnPropertyNames: r$6, getOwnPropertySymbols: o$5, getPrototypeOf: n$6 } = Object, a$1 = globalThis, c$4 = a$1.trustedTypes, l$1 = c$4 ? c$4.emptyScript : "", p$2 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$3 = { toAttribute(t2, s2) {
switch (s2) {
case Boolean:
t2 = t2 ? l$1 : null;
break;
case Object:
case Array:
t2 = null == t2 ? t2 : JSON.stringify(t2);
}
return t2;
}, fromAttribute(t2, s2) {
let i5 = t2;
switch (s2) {
case Boolean:
i5 = null !== t2;
break;
case Number:
i5 = null === t2 ? null : Number(t2);
break;
case Object:
case Array:
try {
i5 = JSON.parse(t2);
} catch (t3) {
i5 = null;
}
}
return i5;
} }, f$3 = (t2, s2) => !i$7(t2, s2), b = { attribute: true, type: String, converter: u$3, reflect: false, useDefault: false, hasChanged: f$3 };
Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), a$1.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap();
let y$1 = class y extends HTMLElement {
static addInitializer(t2) {
this._$Ei(), (this.l ??= []).push(t2);
}
static get observedAttributes() {
return this.finalize(), this._$Eh && [...this._$Eh.keys()];
}
static createProperty(t2, s2 = b) {
if (s2.state && (s2.attribute = false), this._$Ei(), this.prototype.hasOwnProperty(t2) && ((s2 = Object.create(s2)).wrapped = true), this.elementProperties.set(t2, s2), !s2.noAccessor) {
const i5 = /* @__PURE__ */ Symbol(), h2 = this.getPropertyDescriptor(t2, i5, s2);
void 0 !== h2 && e$5(this.prototype, t2, h2);
}
}
static getPropertyDescriptor(t2, s2, i5) {
const { get: e2, set: r2 } = h$3(this.prototype, t2) ?? { get() {
return this[s2];
}, set(t3) {
this[s2] = t3;
} };
return { get: e2, set(s3) {
const h2 = e2?.call(this);
r2?.call(this, s3), this.requestUpdate(t2, h2, i5);
}, configurable: true, enumerable: true };
}
static getPropertyOptions(t2) {
return this.elementProperties.get(t2) ?? b;
}
static _$Ei() {
if (this.hasOwnProperty(d$1("elementProperties"))) return;
const t2 = n$6(this);
t2.finalize(), void 0 !== t2.l && (this.l = [...t2.l]), this.elementProperties = new Map(t2.elementProperties);
}
static finalize() {
if (this.hasOwnProperty(d$1("finalized"))) return;
if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d$1("properties"))) {
const t3 = this.properties, s2 = [...r$6(t3), ...o$5(t3)];
for (const i5 of s2) this.createProperty(i5, t3[i5]);
}
const t2 = this[Symbol.metadata];
if (null !== t2) {
const s2 = litPropertyMetadata.get(t2);
if (void 0 !== s2) for (const [t3, i5] of s2) this.elementProperties.set(t3, i5);
}
this._$Eh = /* @__PURE__ */ new Map();
for (const [t3, s2] of this.elementProperties) {
const i5 = this._$Eu(t3, s2);
void 0 !== i5 && this._$Eh.set(i5, t3);
}
this.elementStyles = this.finalizeStyles(this.styles);
}
static finalizeStyles(s2) {
const i5 = [];
if (Array.isArray(s2)) {
const e2 = new Set(s2.flat(1 / 0).reverse());
for (const s3 of e2) i5.unshift(c$5(s3));
} else void 0 !== s2 && i5.push(c$5(s2));
return i5;
}
static _$Eu(t2, s2) {
const i5 = s2.attribute;
return false === i5 ? void 0 : "string" == typeof i5 ? i5 : "string" == typeof t2 ? t2.toLowerCase() : void 0;
}
constructor() {
super(), this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, this._$Em = null, this._$Ev();
}
_$Ev() {
this._$ES = new Promise(((t2) => this.enableUpdating = t2)), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach(((t2) => t2(this)));
}
addController(t2) {
(this._$EO ??= /* @__PURE__ */ new Set()).add(t2), void 0 !== this.renderRoot && this.isConnected && t2.hostConnected?.();
}
removeController(t2) {
this._$EO?.delete(t2);
}
_$E_() {
const t2 = /* @__PURE__ */ new Map(), s2 = this.constructor.elementProperties;
for (const i5 of s2.keys()) this.hasOwnProperty(i5) && (t2.set(i5, this[i5]), delete this[i5]);
t2.size > 0 && (this._$Ep = t2);
}
createRenderRoot() {
const t2 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);
return S$1(t2, this.constructor.elementStyles), t2;
}
connectedCallback() {
this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(true), this._$EO?.forEach(((t2) => t2.hostConnected?.()));
}
enableUpdating(t2) {
}
disconnectedCallback() {
this._$EO?.forEach(((t2) => t2.hostDisconnected?.()));
}
attributeChangedCallback(t2, s2, i5) {
this._$AK(t2, i5);
}
_$ET(t2, s2) {
const i5 = this.constructor.elementProperties.get(t2), e2 = this.constructor._$Eu(t2, i5);
if (void 0 !== e2 && true === i5.reflect) {
const h2 = (void 0 !== i5.converter?.toAttribute ? i5.converter : u$3).toAttribute(s2, i5.type);
this._$Em = t2, null == h2 ? this.removeAttribute(e2) : this.setAttribute(e2, h2), this._$Em = null;
}
}
_$AK(t2, s2) {
const i5 = this.constructor, e2 = i5._$Eh.get(t2);
if (void 0 !== e2 && this._$Em !== e2) {
const t3 = i5.getPropertyOptions(e2), h2 = "function" == typeof t3.converter ? { fromAttribute: t3.converter } : void 0 !== t3.converter?.fromAttribute ? t3.converter : u$3;
this._$Em = e2, this[e2] = h2.fromAttribute(s2, t3.type) ?? this._$Ej?.get(e2) ?? null, this._$Em = null;
}
}
requestUpdate(t2, s2, i5) {
if (void 0 !== t2) {
const e2 = this.constructor, h2 = this[t2];
if (i5 ??= e2.getPropertyOptions(t2), !((i5.hasChanged ?? f$3)(h2, s2) || i5.useDefault && i5.reflect && h2 === this._$Ej?.get(t2) && !this.hasAttribute(e2._$Eu(t2, i5)))) return;
this.C(t2, s2, i5);
}
false === this.isUpdatePending && (this._$ES = this._$EP());
}
C(t2, s2, { useDefault: i5, reflect: e2, wrapped: h2 }, r2) {
i5 && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t2) && (this._$Ej.set(t2, r2 ?? s2 ?? this[t2]), true !== h2 || void 0 !== r2) || (this._$AL.has(t2) || (this.hasUpdated || i5 || (s2 = void 0), this._$AL.set(t2, s2)), true === e2 && this._$Em !== t2 && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t2));
}
async _$EP() {
this.isUpdatePending = true;
try {
await this._$ES;
} catch (t3) {
Promise.reject(t3);
}
const t2 = this.scheduleUpdate();
return null != t2 && await t2, !this.isUpdatePending;
}
scheduleUpdate() {
return this.performUpdate();
}
performUpdate() {
if (!this.isUpdatePending) return;
if (!this.hasUpdated) {
if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) {
for (const [t4, s3] of this._$Ep) this[t4] = s3;
this._$Ep = void 0;
}
const t3 = this.constructor.elementProperties;
if (t3.size > 0) for (const [s3, i5] of t3) {
const { wrapped: t4 } = i5, e2 = this[s3];
true !== t4 || this._$AL.has(s3) || void 0 === e2 || this.C(s3, void 0, i5, e2);
}
}
let t2 = false;
const s2 = this._$AL;
try {
t2 = this.shouldUpdate(s2), t2 ? (this.willUpdate(s2), this._$EO?.forEach(((t3) => t3.hostUpdate?.())), this.update(s2)) : this._$EM();
} catch (s3) {
throw t2 = false, this._$EM(), s3;
}
t2 && this._$AE(s2);
}
willUpdate(t2) {
}
_$AE(t2) {
this._$EO?.forEach(((t3) => t3.hostUpdated?.())), this.hasUpdated || (this.hasUpdated = true, this.firstUpdated(t2)), this.updated(t2);
}
_$EM() {
this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = false;
}
get updateComplete() {
return this.getUpdateComplete();
}
getUpdateComplete() {
return this._$ES;
}
shouldUpdate(t2) {
return true;
}
update(t2) {
this._$Eq &&= this._$Eq.forEach(((t3) => this._$ET(t3, this[t3]))), this._$EM();
}
updated(t2) {
}
firstUpdated(t2) {
}
};
y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = /* @__PURE__ */ new Map(), y$1[d$1("finalized")] = /* @__PURE__ */ new Map(), p$2?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.0");
const t$4 = globalThis, i$6 = t$4.trustedTypes, s$4 = i$6 ? i$6.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, e$4 = "$lit$", h$2 = `lit$${Math.random().toFixed(9).slice(2)}$`, o$4 = "?" + h$2, n$5 = `<${o$4}>`, r$5 = document, l = () => r$5.createComment(""), c$3 = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, a = Array.isArray, u$2 = (t2) => a(t2) || "function" == typeof t2?.[Symbol.iterator], d = "[ \n\f\r]", f$2 = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, v$1 = /-->/g, _ = />/g, m$2 = RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`, "g"), p$1 = /'/g, g = /"/g, $ = /^(?:script|style|textarea|title)$/i, y2 = (t2) => (i5, ...s2) => ({ _$litType$: t2, strings: i5, values: s2 }), x = y2(1), T = /* @__PURE__ */ Symbol.for("lit-noChange"), E = /* @__PURE__ */ Symbol.for("lit-nothing"), A = /* @__PURE__ */ new WeakMap(), C = r$5.createTreeWalker(r$5, 129);
function P(t2, i5) {
if (!a(t2) || !t2.hasOwnProperty("raw")) throw Error("invalid template strings array");
return void 0 !== s$4 ? s$4.createHTML(i5) : i5;
}
const V = (t2, i5) => {
const s2 = t2.length - 1, o2 = [];
let r2, l2 = 2 === i5 ? "" : 3 === i5 ? "" : "")), o2];
};
class N {
constructor({ strings: t2, _$litType$: s2 }, n3) {
let r2;
this.parts = [];
let c3 = 0, a2 = 0;
const u2 = t2.length - 1, d2 = this.parts, [f2, v2] = V(t2, s2);
if (this.el = N.createElement(f2, n3), C.currentNode = this.el.content, 2 === s2 || 3 === s2) {
const t3 = this.el.content.firstChild;
t3.replaceWith(...t3.childNodes);
}
for (; null !== (r2 = C.nextNode()) && d2.length < u2; ) {
if (1 === r2.nodeType) {
if (r2.hasAttributes()) for (const t3 of r2.getAttributeNames()) if (t3.endsWith(e$4)) {
const i5 = v2[a2++], s3 = r2.getAttribute(t3).split(h$2), e2 = /([.?@])?(.*)/.exec(i5);
d2.push({ type: 1, index: c3, name: e2[2], strings: s3, ctor: "." === e2[1] ? H : "?" === e2[1] ? I : "@" === e2[1] ? L : k }), r2.removeAttribute(t3);
} else t3.startsWith(h$2) && (d2.push({ type: 6, index: c3 }), r2.removeAttribute(t3));
if ($.test(r2.tagName)) {
const t3 = r2.textContent.split(h$2), s3 = t3.length - 1;
if (s3 > 0) {
r2.textContent = i$6 ? i$6.emptyScript : "";
for (let i5 = 0; i5 < s3; i5++) r2.append(t3[i5], l()), C.nextNode(), d2.push({ type: 2, index: ++c3 });
r2.append(t3[s3], l());
}
}
} else if (8 === r2.nodeType) if (r2.data === o$4) d2.push({ type: 2, index: c3 });
else {
let t3 = -1;
for (; -1 !== (t3 = r2.data.indexOf(h$2, t3 + 1)); ) d2.push({ type: 7, index: c3 }), t3 += h$2.length - 1;
}
c3++;
}
}
static createElement(t2, i5) {
const s2 = r$5.createElement("template");
return s2.innerHTML = t2, s2;
}
}
function S(t2, i5, s2 = t2, e2) {
if (i5 === T) return i5;
let h2 = void 0 !== e2 ? s2._$Co?.[e2] : s2._$Cl;
const o2 = c$3(i5) ? void 0 : i5._$litDirective$;
return h2?.constructor !== o2 && (h2?._$AO?.(false), void 0 === o2 ? h2 = void 0 : (h2 = new o2(t2), h2._$AT(t2, s2, e2)), void 0 !== e2 ? (s2._$Co ??= [])[e2] = h2 : s2._$Cl = h2), void 0 !== h2 && (i5 = S(t2, h2._$AS(t2, i5.values), h2, e2)), i5;
}
let M$1 = class M {
constructor(t2, i5) {
this._$AV = [], this._$AN = void 0, this._$AD = t2, this._$AM = i5;
}
get parentNode() {
return this._$AM.parentNode;
}
get _$AU() {
return this._$AM._$AU;
}
u(t2) {
const { el: { content: i5 }, parts: s2 } = this._$AD, e2 = (t2?.creationScope ?? r$5).importNode(i5, true);
C.currentNode = e2;
let h2 = C.nextNode(), o2 = 0, n3 = 0, l2 = s2[0];
for (; void 0 !== l2; ) {
if (o2 === l2.index) {
let i6;
2 === l2.type ? i6 = new R(h2, h2.nextSibling, this, t2) : 1 === l2.type ? i6 = new l2.ctor(h2, l2.name, l2.strings, this, t2) : 6 === l2.type && (i6 = new z(h2, this, t2)), this._$AV.push(i6), l2 = s2[++n3];
}
o2 !== l2?.index && (h2 = C.nextNode(), o2++);
}
return C.currentNode = r$5, e2;
}
p(t2) {
let i5 = 0;
for (const s2 of this._$AV) void 0 !== s2 && (void 0 !== s2.strings ? (s2._$AI(t2, s2, i5), i5 += s2.strings.length - 2) : s2._$AI(t2[i5])), i5++;
}
};
class R {
get _$AU() {
return this._$AM?._$AU ?? this._$Cv;
}
constructor(t2, i5, s2, e2) {
this.type = 2, this._$AH = E, this._$AN = void 0, this._$AA = t2, this._$AB = i5, this._$AM = s2, this.options = e2, this._$Cv = e2?.isConnected ?? true;
}
get parentNode() {
let t2 = this._$AA.parentNode;
const i5 = this._$AM;
return void 0 !== i5 && 11 === t2?.nodeType && (t2 = i5.parentNode), t2;
}
get startNode() {
return this._$AA;
}
get endNode() {
return this._$AB;
}
_$AI(t2, i5 = this) {
t2 = S(this, t2, i5), c$3(t2) ? t2 === E || null == t2 || "" === t2 ? (this._$AH !== E && this._$AR(), this._$AH = E) : t2 !== this._$AH && t2 !== T && this._(t2) : void 0 !== t2._$litType$ ? this.$(t2) : void 0 !== t2.nodeType ? this.T(t2) : u$2(t2) ? this.k(t2) : this._(t2);
}
O(t2) {
return this._$AA.parentNode.insertBefore(t2, this._$AB);
}
T(t2) {
this._$AH !== t2 && (this._$AR(), this._$AH = this.O(t2));
}
_(t2) {
this._$AH !== E && c$3(this._$AH) ? this._$AA.nextSibling.data = t2 : this.T(r$5.createTextNode(t2)), this._$AH = t2;
}
$(t2) {
const { values: i5, _$litType$: s2 } = t2, e2 = "number" == typeof s2 ? this._$AC(t2) : (void 0 === s2.el && (s2.el = N.createElement(P(s2.h, s2.h[0]), this.options)), s2);
if (this._$AH?._$AD === e2) this._$AH.p(i5);
else {
const t3 = new M$1(e2, this), s3 = t3.u(this.options);
t3.p(i5), this.T(s3), this._$AH = t3;
}
}
_$AC(t2) {
let i5 = A.get(t2.strings);
return void 0 === i5 && A.set(t2.strings, i5 = new N(t2)), i5;
}
k(t2) {
a(this._$AH) || (this._$AH = [], this._$AR());
const i5 = this._$AH;
let s2, e2 = 0;
for (const h2 of t2) e2 === i5.length ? i5.push(s2 = new R(this.O(l()), this.O(l()), this, this.options)) : s2 = i5[e2], s2._$AI(h2), e2++;
e2 < i5.length && (this._$AR(s2 && s2._$AB.nextSibling, e2), i5.length = e2);
}
_$AR(t2 = this._$AA.nextSibling, i5) {
for (this._$AP?.(false, true, i5); t2 && t2 !== this._$AB; ) {
const i6 = t2.nextSibling;
t2.remove(), t2 = i6;
}
}
setConnected(t2) {
void 0 === this._$AM && (this._$Cv = t2, this._$AP?.(t2));
}
}
class k {
get tagName() {
return this.element.tagName;
}
get _$AU() {
return this._$AM._$AU;
}
constructor(t2, i5, s2, e2, h2) {
this.type = 1, this._$AH = E, this._$AN = void 0, this.element = t2, this.name = i5, this._$AM = e2, this.options = h2, s2.length > 2 || "" !== s2[0] || "" !== s2[1] ? (this._$AH = Array(s2.length - 1).fill(new String()), this.strings = s2) : this._$AH = E;
}
_$AI(t2, i5 = this, s2, e2) {
const h2 = this.strings;
let o2 = false;
if (void 0 === h2) t2 = S(this, t2, i5, 0), o2 = !c$3(t2) || t2 !== this._$AH && t2 !== T, o2 && (this._$AH = t2);
else {
const e3 = t2;
let n3, r2;
for (t2 = h2[0], n3 = 0; n3 < h2.length - 1; n3++) r2 = S(this, e3[s2 + n3], i5, n3), r2 === T && (r2 = this._$AH[n3]), o2 ||= !c$3(r2) || r2 !== this._$AH[n3], r2 === E ? t2 = E : t2 !== E && (t2 += (r2 ?? "") + h2[n3 + 1]), this._$AH[n3] = r2;
}
o2 && !e2 && this.j(t2);
}
j(t2) {
t2 === E ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t2 ?? "");
}
}
class H extends k {
constructor() {
super(...arguments), this.type = 3;
}
j(t2) {
this.element[this.name] = t2 === E ? void 0 : t2;
}
}
class I extends k {
constructor() {
super(...arguments), this.type = 4;
}
j(t2) {
this.element.toggleAttribute(this.name, !!t2 && t2 !== E);
}
}
class L extends k {
constructor(t2, i5, s2, e2, h2) {
super(t2, i5, s2, e2, h2), this.type = 5;
}
_$AI(t2, i5 = this) {
if ((t2 = S(this, t2, i5, 0) ?? E) === T) return;
const s2 = this._$AH, e2 = t2 === E && s2 !== E || t2.capture !== s2.capture || t2.once !== s2.once || t2.passive !== s2.passive, h2 = t2 !== E && (s2 === E || e2);
e2 && this.element.removeEventListener(this.name, this, s2), h2 && this.element.addEventListener(this.name, this, t2), this._$AH = t2;
}
handleEvent(t2) {
"function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t2) : this._$AH.handleEvent(t2);
}
}
class z {
constructor(t2, i5, s2) {
this.element = t2, this.type = 6, this._$AN = void 0, this._$AM = i5, this.options = s2;
}
get _$AU() {
return this._$AM._$AU;
}
_$AI(t2) {
S(this, t2);
}
}
const Z = { I: R }, j = t$4.litHtmlPolyfillSupport;
j?.(N, R), (t$4.litHtmlVersions ??= []).push("3.3.0");
const B = (t2, i5, s2) => {
const e2 = s2?.renderBefore ?? i5;
let h2 = e2._$litPart$;
if (void 0 === h2) {
const t3 = s2?.renderBefore ?? null;
e2._$litPart$ = h2 = new R(i5.insertBefore(l(), t3), t3, void 0, s2 ?? {});
}
return h2._$AI(t2), h2;
};
const s$3 = globalThis;
let i$5 = class i extends y$1 {
constructor() {
super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0;
}
createRenderRoot() {
const t2 = super.createRenderRoot();
return this.renderOptions.renderBefore ??= t2.firstChild, t2;
}
update(t2) {
const r2 = this.render();
this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t2), this._$Do = B(r2, this.renderRoot, this.renderOptions);
}
connectedCallback() {
super.connectedCallback(), this._$Do?.setConnected(true);
}
disconnectedCallback() {
super.disconnectedCallback(), this._$Do?.setConnected(false);
}
render() {
return T;
}
};
i$5._$litElement$ = true, i$5["finalized"] = true, s$3.litElementHydrateSupport?.({ LitElement: i$5 });
const o$3 = s$3.litElementPolyfillSupport;
o$3?.({ LitElement: i$5 });
(s$3.litElementVersions ??= []).push("4.2.0");
const t$3 = (t2) => (e2, o2) => {
void 0 !== o2 ? o2.addInitializer((() => {
customElements.define(t2, e2);
})) : customElements.define(t2, e2);
};
const o$2 = { attribute: true, type: String, converter: u$3, reflect: false, hasChanged: f$3 }, r$4 = (t2 = o$2, e2, r2) => {
const { kind: n3, metadata: i5 } = r2;
let s2 = globalThis.litPropertyMetadata.get(i5);
if (void 0 === s2 && globalThis.litPropertyMetadata.set(i5, s2 = /* @__PURE__ */ new Map()), "setter" === n3 && ((t2 = Object.create(t2)).wrapped = true), s2.set(r2.name, t2), "accessor" === n3) {
const { name: o2 } = r2;
return { set(r3) {
const n4 = e2.get.call(this);
e2.set.call(this, r3), this.requestUpdate(o2, n4, t2);
}, init(e3) {
return void 0 !== e3 && this.C(o2, void 0, t2, e3), e3;
} };
}
if ("setter" === n3) {
const { name: o2 } = r2;
return function(r3) {
const n4 = this[o2];
e2.call(this, r3), this.requestUpdate(o2, n4, t2);
};
}
throw Error("Unsupported decorator location: " + n3);
};
function n$4(t2) {
return (e2, o2) => "object" == typeof o2 ? r$4(t2, e2, o2) : ((t3, e3, o3) => {
const r2 = e3.hasOwnProperty(o3);
return e3.constructor.createProperty(o3, t3), r2 ? Object.getOwnPropertyDescriptor(e3, o3) : void 0;
})(t2, e2, o2);
}
function r$3(r2) {
return n$4({ ...r2, state: true, attribute: false });
}
function t$2(t2) {
return (n3, o2) => {
const c3 = "function" == typeof n3 ? n3 : n3[o2];
Object.assign(c3, t2);
};
}
const e$3 = (e2, t2, c3) => (c3.configurable = true, c3.enumerable = true, Reflect.decorate && "object" != typeof t2 && Object.defineProperty(e2, t2, c3), c3);
function e$2(e2, r2) {
return (n3, s2, i5) => {
const o2 = (t2) => t2.renderRoot?.querySelector(e2) ?? null;
return e$3(n3, s2, { get() {
return o2(this);
} });
};
}
const playerSectionStyles = i$8`
.container {
position: relative;
display: grid;
grid-template-columns: 100%;
grid-template-rows: min-content auto min-content;
grid-template-areas:
'header'
'artwork'
'controls';
min-height: 100%;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.container.blurred-background {
background: none;
isolation: isolate;
overflow: hidden;
}
.container.blurred-background::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: var(--blur-background-image);
background-position: center;
background-repeat: no-repeat;
background-size: cover;
filter: blur(var(--blur-amount));
transform: scale(1.1);
z-index: -1;
}
.header {
grid-area: header;
margin: 0.75rem 1.25rem;
padding: 0.5rem;
position: relative;
}
.controls {
grid-area: controls;
overflow-y: auto;
margin: 0.25rem;
padding: 0.5rem;
position: relative;
}
.artwork {
grid-area: artwork;
align-self: center;
flex-grow: 1;
flex-shrink: 0;
width: 100%;
height: 100%;
min-height: 5rem;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
position: relative;
}
[hidden] {
display: none !important;
}
*[background] {
background-color: var(--background-overlay-color, rgba(var(--rgb-card-background-color), var(--background-opacity, 0.9)));
border-radius: 10px;
}
`;
const dispatchPrefix = "mxmp-dispatch-event-";
const ACTIVE_PLAYER_EVENT_INTERNAL = "active-player";
const ACTIVE_PLAYER_EVENT = dispatchPrefix + ACTIVE_PLAYER_EVENT_INTERNAL;
const SHOW_SECTION = "show-section";
const CALL_MEDIA_STARTED = `${dispatchPrefix}call-media-started`;
const CALL_MEDIA_DONE = `${dispatchPrefix}call-media-done`;
const MEDIA_ITEM_SELECTED = "item-selected";
const SESSION_STORAGE_PLAYER_ID = "mxmp-player-id";
const HASS_MORE_INFO = "hass-more-info";
const TV_BASE64_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAAAXNSR0IArs4c6QAAAJZlWElmTU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgExAAIAAAARAAAAWodpAAQAAAABAAAAbAAAAAAAAABiAAAAAQAAAGIAAAABd3d3Lmlua3NjYXBlLm9yZwAAAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAgCgAwAEAAAAAQAAAgAAAAAA+uiskAAAAAlwSFlzAAAPEgAADxIBIZvyMwAAAi1pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+d3d3Lmlua3NjYXBlLm9yZzwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj45ODwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+OTg8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgrH52sKAAA/4klEQVR4Ae3dCZAc13nY8dfHzOwNYAHiJAACIMAD4iFRlE3JsSSrSrLiWLEdQ7FNy5KllBLHRcV27NiupFKoVBLfsUNVJVVWJNqMFCuCY7ssu2jZkgWVbFG2RJGmSIgkTuIGcew1uztHH/m+Hiw0A+wxs9Mz093zb2mJObpfv/frmXlfv379njEsCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkAgBKxG5IBMI9JnA29520L3zzbn1VjW3xbeDET8IQjNgT7rF0oWP//bBCeEIM0ZifehnD67zRga22BWzRn54bCewi2GueuHYV6pXDx8+6GWsvBQHgcQLEAAk/hCRwawJ/MQv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AQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBGIQ+P9v6JX0ZgeS/gAAAABJRU5ErkJggg==";
const MUSIC_NOTES_BASE64_IMAGE = "data:image/png;base64,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AQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIEGFvj/AdpNbG+v3HHWAAAAAElFTkSuQmCC";
const listStyle = i$8`
.list {
--mdc-theme-primary: var(--accent-color);
--mdc-list-vertical-padding: 0px;
overflow: hidden;
}
`;
const mediaItemTitleStyle = i$8`
.title {
color: var(--secondary-text-color);
font-weight: bold;
padding: 0 0.5rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
`;
function matchesString(value, expected) {
return !!value && value === expected;
}
function matchesRegexp(value, pattern) {
if (!value || !pattern) {
return false;
}
try {
return new RegExp(pattern).test(value);
} catch {
return false;
}
}
function findMatchingOverride(overrides, attrs, entityImage) {
let override = overrides.find(
(value) => matchesString(attrs.media_title, value.mediaTitleEquals) || matchesString(attrs.media_artist, value.mediaArtistEquals) || matchesString(attrs.media_album_name, value.mediaAlbumNameEquals) || matchesString(attrs.media_channel, value.mediaChannelEquals) || matchesString(attrs.media_content_id, value.mediaContentIdEquals) || matchesString(attrs.app_name, value.appNameEquals) || matchesString(attrs.source, value.sourceEquals) || matchesString(attrs.app_id, value.appIdEquals) || matchesRegexp(attrs.media_title, value.mediaTitleRegexp) || matchesRegexp(attrs.media_artist, value.mediaArtistRegexp) || matchesRegexp(attrs.media_album_name, value.mediaAlbumNameRegexp) || matchesRegexp(attrs.media_channel, value.mediaChannelRegexp) || matchesRegexp(attrs.media_content_id, value.mediaContentIdRegexp) || matchesRegexp(attrs.app_name, value.appNameRegexp) || matchesRegexp(attrs.source, value.sourceRegexp) || matchesRegexp(attrs.app_id, value.appIdRegexp)
);
if (!override) {
override = overrides.find((value) => !entityImage && value.ifMissing);
}
return override;
}
function findArtworkOverride(store, entityImage) {
const overrides = store.config.player?.mediaArtworkOverrides;
if (!overrides) {
return void 0;
}
const { media_title, media_artist, media_album_name, media_content_id, media_channel, app_name, source, app_id } = store.activePlayer.attributes;
return findMatchingOverride(
overrides,
{ media_title, media_artist, media_album_name, media_content_id, media_channel, app_name, source, app_id },
entityImage
);
}
function getArtworkImage(store, resolvedImageUrl) {
const prefix = store.config.player?.artworkHostname || "";
const { entity_picture, entity_picture_local, app_id } = store.activePlayer.attributes;
let entityImage = entity_picture ? prefix + entity_picture : entity_picture;
if (app_id === "music_assistant") {
entityImage = entity_picture_local ? prefix + entity_picture_local : entity_picture;
}
let sizePercentage = void 0;
const override = findArtworkOverride(store, entityImage);
if (override?.imageUrl) {
if (override.imageUrl.includes("{{")) {
entityImage = resolvedImageUrl ?? "";
} else {
entityImage = override.imageUrl;
}
sizePercentage = override?.sizePercentage ?? sizePercentage;
}
return { entityImage, sizePercentage };
}
function getFallbackImage(store) {
return store.config.player?.fallbackArtwork ?? (store.activePlayer.attributes.media_title === "TV" ? TV_BASE64_IMAGE : MUSIC_NOTES_BASE64_IMAGE);
}
function getBackgroundImage(store, imageLoaded, resolvedImageUrl) {
const image = getArtworkImage(store, resolvedImageUrl);
if (image?.entityImage && imageLoaded) {
const sizeStyle = image.sizePercentage ? `; background-size: ${image.sizePercentage}%` : "";
return `background-image: url(${image.entityImage})${sizeStyle}`;
}
return `background-image: url(${getFallbackImage(store)})`;
}
function getBackgroundImageUrl(store, imageLoaded, resolvedImageUrl) {
const image = getArtworkImage(store, resolvedImageUrl);
if (image?.entityImage && imageLoaded) {
return `url(${image.entityImage})`;
}
return `url(${getFallbackImage(store)})`;
}
function getArtworkStyle(store, imageLoaded, resolvedImageUrl) {
const { artworkMinHeight: minHeight = 5, artworkBorderRadius: borderRadius = 0 } = store.config.player ?? {};
const bg = getBackgroundImage(store, imageLoaded, resolvedImageUrl);
if (borderRadius > 0) {
return `${bg}; border-radius: ${borderRadius}px; background-size: cover; aspect-ratio: 1; height: 100%; max-height: 50vh; width: auto; margin: 0 auto;`;
}
return `${bg}; min-height: ${minHeight}rem`;
}
var mdiAccessPoint = "M4.93,4.93C3.12,6.74 2,9.24 2,12C2,14.76 3.12,17.26 4.93,19.07L6.34,17.66C4.89,16.22 4,14.22 4,12C4,9.79 4.89,7.78 6.34,6.34L4.93,4.93M19.07,4.93L17.66,6.34C19.11,7.78 20,9.79 20,12C20,14.22 19.11,16.22 17.66,17.66L19.07,19.07C20.88,17.26 22,14.76 22,12C22,9.24 20.88,6.74 19.07,4.93M7.76,7.76C6.67,8.85 6,10.35 6,12C6,13.65 6.67,15.15 7.76,16.24L9.17,14.83C8.45,14.11 8,13.11 8,12C8,10.89 8.45,9.89 9.17,9.17L7.76,7.76M16.24,7.76L14.83,9.17C15.55,9.89 16,10.89 16,12C16,13.11 15.55,14.11 14.83,14.83L16.24,16.24C17.33,15.15 18,13.65 18,12C18,10.35 17.33,8.85 16.24,7.76M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z";
var mdiAccount = "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z";
var mdiAccountMusic = "M11,14C12,14 13.05,14.16 14.2,14.44C13.39,15.31 13,16.33 13,17.5C13,18.39 13.25,19.23 13.78,20H3V18C3,16.81 3.91,15.85 5.74,15.12C7.57,14.38 9.33,14 11,14M11,12C9.92,12 9,11.61 8.18,10.83C7.38,10.05 7,9.11 7,8C7,6.92 7.38,6 8.18,5.18C9,4.38 9.92,4 11,4C12.11,4 13.05,4.38 13.83,5.18C14.61,6 15,6.92 15,8C15,9.11 14.61,10.05 13.83,10.83C13.05,11.61 12.11,12 11,12M18.5,10H20L22,10V12H20V17.5A2.5,2.5 0 0,1 17.5,20A2.5,2.5 0 0,1 15,17.5A2.5,2.5 0 0,1 17.5,15C17.86,15 18.19,15.07 18.5,15.21V10Z";
var mdiAccountMusicOutline = "M11,4A4,4 0 0,1 15,8A4,4 0 0,1 11,12A4,4 0 0,1 7,8A4,4 0 0,1 11,4M11,6A2,2 0 0,0 9,8A2,2 0 0,0 11,10A2,2 0 0,0 13,8A2,2 0 0,0 11,6M11,13C12.1,13 13.66,13.23 15.11,13.69C14.5,14.07 14,14.6 13.61,15.23C12.79,15.03 11.89,14.9 11,14.9C8.03,14.9 4.9,16.36 4.9,17V18.1H13.04C13.13,18.8 13.38,19.44 13.76,20H3V17C3,14.34 8.33,13 11,13M18.5,10H20L22,10V12H20V17.5A2.5,2.5 0 0,1 17.5,20A2.5,2.5 0 0,1 15,17.5A2.5,2.5 0 0,1 17.5,15C17.86,15 18.19,15.07 18.5,15.21V10Z";
var mdiAlarm = "M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M12.5,8H11V14L15.75,16.85L16.5,15.62L12.5,13.25V8M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72Z";
var mdiAlbum = "M12,11A1,1 0 0,0 11,12A1,1 0 0,0 12,13A1,1 0 0,0 13,12A1,1 0 0,0 12,11M12,16.5C9.5,16.5 7.5,14.5 7.5,12C7.5,9.5 9.5,7.5 12,7.5C14.5,7.5 16.5,9.5 16.5,12C16.5,14.5 14.5,16.5 12,16.5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z";
var mdiAlphaABoxOutline = "M3,5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5M5,5V19H19V5H5M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11Z";
var mdiApplication = "M21 2H3C1.9 2 1 2.9 1 4V20C1 21.1 1.9 22 3 22H21C22.1 22 23 21.1 23 20V4C23 2.9 22.1 2 21 2M21 7H3V4H21V7Z";
var mdiArrowLeft = "M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z";
var mdiArrowUpRight = "M21.5 9.5L20.09 10.92L17 7.83V13.5C17 17.09 14.09 20 10.5 20H4V18H10.5C13 18 15 16 15 13.5V7.83L11.91 10.91L10.5 9.5L16 4L21.5 9.5Z";
var mdiBookmark = "M17,3H7A2,2 0 0,0 5,5V21L12,18L19,21V5C19,3.89 18.1,3 17,3Z";
var mdiBookshelf = "M9 3V18H12V3H9M12 5L16 18L19 17L15 4L12 5M5 5V18H8V5H5M3 19V21H21V19H3Z";
var mdiCheck = "M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z";
var mdiCheckAll = "M0.41,13.41L6,19L7.41,17.58L1.83,12M22.24,5.58L11.66,16.17L7.5,12L6.07,13.41L11.66,19L23.66,7M18,7L16.59,5.58L10.24,11.93L11.66,13.34L18,7Z";
var mdiCheckCircle = "M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z";
var mdiCheckboxMultipleMarkedOutline = "M20,16V10H22V16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H16V4H8V16H20M10.91,7.08L14,10.17L20.59,3.58L22,5L14,13L9.5,8.5L10.91,7.08M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z";
var mdiChevronDown = "M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z";
var mdiChevronLeft = "M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z";
var mdiChevronRight = "M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z";
var mdiChevronUp = "M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z";
var mdiClose = "M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z";
var mdiCloseBoxMultipleOutline = "M20 2H8C6.9 2 6 2.9 6 4V16C6 17.11 6.9 18 8 18H20C21.11 18 22 17.11 22 16V4C22 2.9 21.11 2 20 2M20 16H8V4H20V16M4 6V20H18V22H4C2.9 22 2 21.11 2 20V6H4M9.77 12.84L12.6 10L9.77 7.15L11.17 5.75L14 8.6L16.84 5.77L18.24 7.17L15.4 10L18.23 12.84L16.83 14.24L14 11.4L11.17 14.24L9.77 12.84Z";
var mdiCloseCircle = "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
var mdiCog = "M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z";
var mdiDelete = "M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z";
var mdiDotsVertical = "M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z";
var mdiDramaMasks = "M8.11,19.45C5.94,18.65 4.22,16.78 3.71,14.35L2.05,6.54C1.81,5.46 2.5,4.4 3.58,4.17L13.35,2.1L13.38,2.09C14.45,1.88 15.5,2.57 15.72,3.63L16.07,5.3L20.42,6.23H20.45C21.5,6.47 22.18,7.53 21.96,8.59L20.3,16.41C19.5,20.18 15.78,22.6 12,21.79C10.42,21.46 9.08,20.61 8.11,19.45V19.45M20,8.18L10.23,6.1L8.57,13.92V13.95C8,16.63 9.73,19.27 12.42,19.84C15.11,20.41 17.77,18.69 18.34,16L20,8.18M16,16.5C15.37,17.57 14.11,18.16 12.83,17.89C11.56,17.62 10.65,16.57 10.5,15.34L16,16.5M8.47,5.17L4,6.13L5.66,13.94L5.67,13.97C5.82,14.68 6.12,15.32 6.53,15.87C6.43,15.1 6.45,14.3 6.62,13.5L7.05,11.5C6.6,11.42 6.21,11.17 6,10.81C6.06,10.2 6.56,9.66 7.25,9.5C7.33,9.5 7.4,9.5 7.5,9.5L8.28,5.69C8.32,5.5 8.38,5.33 8.47,5.17M15.03,12.23C15.35,11.7 16.03,11.42 16.72,11.57C17.41,11.71 17.91,12.24 18,12.86C17.67,13.38 17,13.66 16.3,13.5C15.61,13.37 15.11,12.84 15.03,12.23M10.15,11.19C10.47,10.66 11.14,10.38 11.83,10.53C12.5,10.67 13.03,11.21 13.11,11.82C12.78,12.34 12.11,12.63 11.42,12.5C10.73,12.33 10.23,11.8 10.15,11.19M11.97,4.43L13.93,4.85L13.77,4.05L11.97,4.43Z";
var mdiEyeCheck = "M23.5,17L18.5,22L15,18.5L16.5,17L18.5,19L22,15.5L23.5,17M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,17C12.5,17 12.97,16.93 13.42,16.79C13.15,17.5 13,18.22 13,19V19.45L12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5C17,4.5 21.27,7.61 23,12C22.75,12.64 22.44,13.26 22.08,13.85C21.18,13.31 20.12,13 19,13C18.22,13 17.5,13.15 16.79,13.42C16.93,12.97 17,12.5 17,12A5,5 0 0,0 12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17Z";
var mdiFastForward = "M13,6V18L21.5,12M4,18L12.5,12L4,6V18Z";
var mdiFileMusic = "M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M13,13H11V18A2,2 0 0,1 9,20A2,2 0 0,1 7,18A2,2 0 0,1 9,16C9.4,16 9.7,16.1 10,16.3V11H13V13M13,9V3.5L18.5,9H13Z";
var mdiFolder = "M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z";
var mdiFolderStar = "M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6M17.94,17L15,15.28L12.06,17L12.84,13.67L10.25,11.43L13.66,11.14L15,8L16.34,11.14L19.75,11.43L17.16,13.67L17.94,17Z";
var mdiFolderStarOutline = "M10.78 12.05L13.81 11.79L15 9L16.19 11.79L19.22 12.05L16.92 14.04L17.61 17L15 15.47L12.39 17L13.08 14.04L10.78 12.05M22 8V18C22 19.11 21.11 20 20 20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.9 4 4 4H10L12 6H20C21.11 6 22 6.9 22 8M20 8H4V18H20V8Z";
var mdiGamepadVariant = "M7,6H17A6,6 0 0,1 23,12A6,6 0 0,1 17,18C15.22,18 13.63,17.23 12.53,16H11.47C10.37,17.23 8.78,18 7,18A6,6 0 0,1 1,12A6,6 0 0,1 7,6M6,9V11H4V13H6V15H8V13H10V11H8V9H6M15.5,12A1.5,1.5 0 0,0 14,13.5A1.5,1.5 0 0,0 15.5,15A1.5,1.5 0 0,0 17,13.5A1.5,1.5 0 0,0 15.5,12M18.5,9A1.5,1.5 0 0,0 17,10.5A1.5,1.5 0 0,0 18.5,12A1.5,1.5 0 0,0 20,10.5A1.5,1.5 0 0,0 18.5,9Z";
var mdiGrid = "M10,4V8H14V4H10M16,4V8H20V4H16M16,10V14H20V10H16M16,16V20H20V16H16M14,20V16H10V20H14M8,20V16H4V20H8M8,14V10H4V14H8M8,8V4H4V8H8M10,14H14V10H10V14M4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H4C2.92,22 2,21.1 2,20V4A2,2 0 0,1 4,2Z";
var mdiHeart = "M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z";
var mdiHeartOutline = "M12.1,18.55L12,18.65L11.89,18.55C7.14,14.24 4,11.39 4,8.5C4,6.5 5.5,5 7.5,5C9.04,5 10.54,6 11.07,7.36H12.93C13.46,6 14.96,5 16.5,5C18.5,5 20,6.5 20,8.5C20,11.39 16.86,14.24 12.1,18.55M16.5,3C14.76,3 13.09,3.81 12,5.08C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.41 2,8.5C2,12.27 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C18.6,15.36 22,12.27 22,8.5C22,5.41 19.58,3 16.5,3Z";
var mdiImage = "M8.5,13.5L11,16.5L14.5,12L19,18H5M21,19V5C21,3.89 20.1,3 19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19Z";
var mdiKeyboard = "M19,10H17V8H19M19,13H17V11H19M16,10H14V8H16M16,13H14V11H16M16,17H8V15H16M7,10H5V8H7M7,13H5V11H7M8,11H10V13H8M8,8H10V10H8M11,11H13V13H11M11,8H13V10H11M20,5H4C2.89,5 2,5.89 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7C22,5.89 21.1,5 20,5Z";
var mdiListBoxOutline = "M11 15H17V17H11V15M9 7H7V9H9V7M11 13H17V11H11V13M11 9H17V7H11V9M9 11H7V13H9V11M21 5V19C21 20.1 20.1 21 19 21H5C3.9 21 3 20.1 3 19V5C3 3.9 3.9 3 5 3H19C20.1 3 21 3.9 21 5M19 5H5V19H19V5M9 15H7V17H9V15Z";
var mdiMagnify = "M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z";
var mdiMovie = "M18,4L20,8H17L15,4H13L15,8H12L10,4H8L10,8H7L5,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V4H18Z";
var mdiMusic = "M21,3V15.5A3.5,3.5 0 0,1 17.5,19A3.5,3.5 0 0,1 14,15.5A3.5,3.5 0 0,1 17.5,12C18.04,12 18.55,12.12 19,12.34V6.47L9,8.6V17.5A3.5,3.5 0 0,1 5.5,21A3.5,3.5 0 0,1 2,17.5A3.5,3.5 0 0,1 5.5,14C6.04,14 6.55,14.12 7,14.34V6L21,3Z";
var mdiPauseCircle = "M15,16H13V8H15M11,16H9V8H11M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z";
var mdiPen = "M20.71,7.04C20.37,7.38 20.04,7.71 20.03,8.04C20,8.36 20.34,8.69 20.66,9C21.14,9.5 21.61,9.95 21.59,10.44C21.57,10.93 21.06,11.44 20.55,11.94L16.42,16.08L15,14.66L19.25,10.42L18.29,9.46L16.87,10.87L13.12,7.12L16.96,3.29C17.35,2.9 18,2.9 18.37,3.29L20.71,5.63C21.1,6 21.1,6.65 20.71,7.04M3,17.25L12.56,7.68L16.31,11.43L6.75,21H3V17.25Z";
var mdiPlay = "M8,5.14V19.14L19,12.14L8,5.14Z";
var mdiPlayBoxMultiple = "M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M12,14.5V5.5L18,10L12,14.5Z";
var mdiPlayCircle = "M10,16.5V7.5L16,12M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z";
var mdiPlaylistMusic = "M15,6H3V8H15V6M15,10H3V12H15V10M3,16H11V14H3V16M17,6V14.18C16.69,14.07 16.35,14 16,14A3,3 0 0,0 13,17A3,3 0 0,0 16,20A3,3 0 0,0 19,17V8H22V6H17Z";
var mdiPlaylistPlus = "M3 16H10V14H3M18 14V10H16V14H12V16H16V20H18V16H22V14M14 6H3V8H14M14 10H3V12H14V10Z";
var mdiPlus = "M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z";
var mdiPodcast = "M17,18.25V21.5H7V18.25C7,16.87 9.24,15.75 12,15.75C14.76,15.75 17,16.87 17,18.25M12,5.5A6.5,6.5 0 0,1 18.5,12C18.5,13.25 18.15,14.42 17.54,15.41L16,14.04C16.32,13.43 16.5,12.73 16.5,12C16.5,9.5 14.5,7.5 12,7.5C9.5,7.5 7.5,9.5 7.5,12C7.5,12.73 7.68,13.43 8,14.04L6.46,15.41C5.85,14.42 5.5,13.25 5.5,12A6.5,6.5 0 0,1 12,5.5M12,1.5A10.5,10.5 0 0,1 22.5,12C22.5,14.28 21.77,16.39 20.54,18.11L19.04,16.76C19.96,15.4 20.5,13.76 20.5,12A8.5,8.5 0 0,0 12,3.5A8.5,8.5 0 0,0 3.5,12C3.5,13.76 4.04,15.4 4.96,16.76L3.46,18.11C2.23,16.39 1.5,14.28 1.5,12A10.5,10.5 0 0,1 12,1.5M12,9.5A2.5,2.5 0 0,1 14.5,12A2.5,2.5 0 0,1 12,14.5A2.5,2.5 0 0,1 9.5,12A2.5,2.5 0 0,1 12,9.5Z";
var mdiPower = "M16.56,5.44L15.11,6.89C16.84,7.94 18,9.83 18,12A6,6 0 0,1 12,18A6,6 0 0,1 6,12C6,9.83 7.16,7.94 8.88,6.88L7.44,5.44C5.36,6.88 4,9.28 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12C20,9.28 18.64,6.88 16.56,5.44M13,3H11V13H13";
var mdiRadio = "M20,6A2,2 0 0,1 22,8V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V8C2,7.15 2.53,6.42 3.28,6.13L15.71,1L16.47,2.83L8.83,6H20M20,8H4V12H16V10H18V12H20V8M7,14A3,3 0 0,0 4,17A3,3 0 0,0 7,20A3,3 0 0,0 10,17A3,3 0 0,0 7,14Z";
var mdiRepeat = "M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z";
var mdiRepeatOff = "M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z";
var mdiRepeatOnce = "M13,15V9H12L10,10V11H11.5V15M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z";
var mdiRewind = "M11.5,12L20,18V6M11,18V6L2.5,12L11,18Z";
var mdiSelectInverse = "M5,3H7V5H9V3H11V5H13V3H15V5H17V3H19V5H21V7H19V9H21V11H19V13H21V15H19V17H21V19H19V21H17V19H15V21H13V19H11V21H9V19H7V21H5V19H3V17H5V15H3V13H5V11H3V9H5V7H3V5H5V3Z";
var mdiShuffle = "M14.83,13.41L13.42,14.82L16.55,17.95L14.5,20H20V14.5L17.96,16.54L14.83,13.41M14.5,4L16.54,6.04L4,18.59L5.41,20L17.96,7.46L20,9.5V4M10.59,9.17L5.41,4L4,5.41L9.17,10.58L10.59,9.17Z";
var mdiShuffleDisabled = "M16,4.5V7H5V9H16V11.5L19.5,8M16,12.5V15H5V17H16V19.5L19.5,16";
var mdiSkipNext = "M16,18H18V6H16M6,18L14.5,12L6,6V18Z";
var mdiSkipNextCircle = "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M8,8L13,12L8,16M14,8H16V16H14";
var mdiSkipPrevious = "M6,18V6H8V18H6M9.5,12L18,6V18L9.5,12Z";
var mdiStar = "M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z";
var mdiStopCircle = "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M9,9H15V15H9";
var mdiTelevisionClassic = "M8.16,3L6.75,4.41L9.34,7H4C2.89,7 2,7.89 2,9V19C2,20.11 2.89,21 4,21H20C21.11,21 22,20.11 22,19V9C22,7.89 21.11,7 20,7H14.66L17.25,4.41L15.84,3L12,6.84L8.16,3M4,9H17V19H4V9M19.5,9A1,1 0 0,1 20.5,10A1,1 0 0,1 19.5,11A1,1 0 0,1 18.5,10A1,1 0 0,1 19.5,9M19.5,12A1,1 0 0,1 20.5,13A1,1 0 0,1 19.5,14A1,1 0 0,1 18.5,13A1,1 0 0,1 19.5,12Z";
var mdiTrashCanOutline = "M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z";
var mdiVideo = "M17,10.5V7A1,1 0 0,0 16,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16A1,1 0 0,0 17,17V13.5L21,17.5V6.5L17,10.5Z";
var mdiViewGrid = "M3,11H11V3H3M3,21H11V13H3M13,21H21V13H13M13,3V11H21V3";
var mdiViewList = "M9,5V9H21V5M9,19H21V15H9M9,14H21V10H9M4,9H8V5H4M4,19H8V15H4M4,14H8V10H4V14Z";
var mdiVolumeHigh = "M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z";
var mdiVolumeMinus = "M3,9H7L12,4V20L7,15H3V9M14,11H22V13H14V11Z";
var mdiVolumeMute = "M3,9H7L12,4V20L7,15H3V9M16.59,12L14,9.41L15.41,8L18,10.59L20.59,8L22,9.41L19.41,12L22,14.59L20.59,16L18,13.41L15.41,16L14,14.59L16.59,12Z";
var mdiVolumePlus = "M3,9H7L12,4V20L7,15H3V9M14,11H17V8H19V11H22V13H19V16H17V13H14V11Z";
var mdiWeb = "M16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14M14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56M14.34,14H9.66C9.56,13.34 9.5,12.68 9.5,12C9.5,11.32 9.56,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14M12,19.96C11.17,18.76 10.5,17.43 10.09,16H13.91C13.5,17.43 12.83,18.76 12,19.96M8,8H5.08C6.03,6.34 7.57,5.06 9.4,4.44C8.8,5.55 8.35,6.75 8,8M5.08,16H8C8.35,17.25 8.8,18.45 9.4,19.56C7.57,18.93 6.03,17.65 5.08,16M4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14M12,4.03C12.83,5.23 13.5,6.57 13.91,8H10.09C10.5,6.57 11.17,5.23 12,4.03M18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.34 18.92,8M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z";
var Section = /* @__PURE__ */ ((Section2) => {
Section2["GROUPS"] = "groups";
Section2["MEDIA_BROWSER"] = "media browser";
Section2["PLAYER"] = "player";
Section2["GROUPING"] = "grouping";
Section2["VOLUMES"] = "volumes";
Section2["QUEUE"] = "queue";
Section2["SEARCH"] = "search";
return Section2;
})(Section || {});
const MASS_QUEUE_NOT_INSTALLED = "MASS_QUEUE_NOT_INSTALLED";
const isUnavailableState = (state) => {
return state === "unavailable" || state === "unknown" || !state;
};
const isTTSMediaSource = (mediaContentId) => {
return mediaContentId?.startsWith("media-source://tts/") ?? false;
};
var MediaPlayerEntityFeature = /* @__PURE__ */ ((MediaPlayerEntityFeature2) => {
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["PAUSE"] = 1] = "PAUSE";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["SEEK"] = 2] = "SEEK";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["VOLUME_SET"] = 4] = "VOLUME_SET";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["VOLUME_MUTE"] = 8] = "VOLUME_MUTE";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["PREVIOUS_TRACK"] = 16] = "PREVIOUS_TRACK";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["NEXT_TRACK"] = 32] = "NEXT_TRACK";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["TURN_ON"] = 128] = "TURN_ON";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["TURN_OFF"] = 256] = "TURN_OFF";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["PLAY_MEDIA"] = 512] = "PLAY_MEDIA";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["VOLUME_STEP"] = 1024] = "VOLUME_STEP";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["SELECT_SOURCE"] = 2048] = "SELECT_SOURCE";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["STOP"] = 4096] = "STOP";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["CLEAR_PLAYLIST"] = 8192] = "CLEAR_PLAYLIST";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["PLAY"] = 16384] = "PLAY";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["SHUFFLE_SET"] = 32768] = "SHUFFLE_SET";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["SELECT_SOUND_MODE"] = 65536] = "SELECT_SOUND_MODE";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["BROWSE_MEDIA"] = 131072] = "BROWSE_MEDIA";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["REPEAT_SET"] = 262144] = "REPEAT_SET";
MediaPlayerEntityFeature2[MediaPlayerEntityFeature2["GROUPING"] = 524288] = "GROUPING";
return MediaPlayerEntityFeature2;
})(MediaPlayerEntityFeature || {});
const BROWSER_PLAYER = "browser";
const MediaClassBrowserSettings = {
album: { icon: mdiAlbum, layout: "grid" },
app: { icon: mdiApplication, layout: "grid", show_list_images: true },
artist: { icon: mdiAccountMusic, layout: "grid", show_list_images: true },
channel: {
icon: mdiTelevisionClassic,
thumbnail_ratio: "portrait",
layout: "grid",
show_list_images: true
},
composer: {
icon: mdiAccountMusicOutline,
layout: "grid",
show_list_images: true
},
contributing_artist: {
icon: mdiAccountMusic,
layout: "grid",
show_list_images: true
},
directory: { icon: mdiFolder, layout: "grid", show_list_images: true },
episode: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
show_list_images: true
},
game: {
icon: mdiGamepadVariant,
layout: "grid",
thumbnail_ratio: "portrait"
},
genre: { icon: mdiDramaMasks, layout: "grid", show_list_images: true },
image: { icon: mdiImage, layout: "grid", show_list_images: true },
movie: {
icon: mdiMovie,
thumbnail_ratio: "portrait",
layout: "grid",
show_list_images: true
},
music: { icon: mdiMusic, show_list_images: true },
playlist: { icon: mdiPlaylistMusic, layout: "grid", show_list_images: true },
podcast: { icon: mdiPodcast, layout: "grid" },
season: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
show_list_images: true
},
track: { icon: mdiFileMusic },
tv_show: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait"
},
url: { icon: mdiWeb },
video: { icon: mdiVideo, layout: "grid", show_list_images: true }
};
const browseMediaPlayer = (hass, entityId, mediaContentId, mediaContentType) => hass.callWS({
type: "media_player/browse_media",
entity_id: entityId,
media_content_id: mediaContentId,
media_content_type: mediaContentType
});
function getSpeakerList(mainPlayer, predefinedGroups = []) {
const playerIds = mainPlayer.members.map((member) => member.id).sort();
if (predefinedGroups?.length) {
const found = predefinedGroups.find(
(pg) => pg.entities.map((p2) => p2.player.id).sort().toString() === playerIds.toString()
);
if (found) {
return found.name;
}
}
const otherMembers = mainPlayer.members.filter((member) => member.id !== mainPlayer.id);
return [mainPlayer.name, ...otherMembers.map((member) => member.name)].join(" + ");
}
function dispatchActivePlayerId(playerId, config, element) {
if (cardDoesNotContainAllSections(config)) {
dispatch(ACTIVE_PLAYER_EVENT, { entityId: playerId });
} else {
element.dispatchEvent(customEvent(ACTIVE_PLAYER_EVENT_INTERNAL, { entityId: playerId }));
}
}
function cardDoesNotContainAllSections(config) {
return config.sections && config.sections.length < Object.keys(Section).length;
}
function customEvent(type, detail) {
return new CustomEvent(type, {
bubbles: true,
composed: true,
detail
});
}
function dispatch(type, detail) {
const event = customEvent(type, detail);
window.dispatchEvent(event);
}
const HEIGHT_AND_WIDTH = 40;
function getWidthOrHeight(confValue) {
if (confValue) {
return confValue / 100 * HEIGHT_AND_WIDTH;
}
return HEIGHT_AND_WIDTH;
}
function getHeight(config) {
return getWidthOrHeight(config.heightPercentage);
}
function getWidth(config) {
return getWidthOrHeight(config.widthPercentage);
}
function getGroupPlayerIds(hassEntity) {
let groupMembers = hassEntity.attributes.group_members;
groupMembers = groupMembers?.filter((id) => id !== null && id !== void 0);
return groupMembers?.length ? groupMembers : [hassEntity.entity_id];
}
function supportsTurnOn(player) {
return ((player.attributes.supported_features || 0) & MediaPlayerEntityFeature.TURN_ON) === MediaPlayerEntityFeature.TURN_ON;
}
function getGroupingChanges(groupingItems, joinedPlayers, activePlayerId) {
const isSelected = groupingItems.filter((item) => item.isSelected);
const unJoin = groupingItems.filter((item) => !item.isSelected && joinedPlayers.includes(item.player.id)).map((item) => item.player.id);
const join = groupingItems.filter((item) => item.isSelected && !joinedPlayers.includes(item.player.id)).map((item) => item.player.id);
let newMainPlayer = activePlayerId;
if (unJoin.includes(activePlayerId)) {
newMainPlayer = isSelected[0].player.id;
}
return { unJoin, join, newMainPlayer };
}
function entityMatchSonos(config, entity, hassWithEntities) {
const entityId = entity.entity_id;
const configEntities = [...new Set(config.entities)];
let includeEntity = true;
if (configEntities.length) {
const includesEntity = configEntities.includes(entityId);
includeEntity = !!config.excludeItemsInEntitiesList !== includesEntity;
}
let matchesPlatform = true;
entity.attributes.platform = hassWithEntities.entities?.[entityId]?.platform;
if (config.entityPlatform) {
matchesPlatform = entity.attributes.platform === config.entityPlatform;
}
return includeEntity && matchesPlatform;
}
function entityMatchMxmp(config, entity, hassWithEntities) {
const entityId = entity.entity_id;
const configEntities = [...new Set(config.entities)];
let matchesPlatform = false;
entity.attributes.platform = hassWithEntities.entities?.[entityId]?.platform;
if (config.entityPlatform) {
matchesPlatform = entity.attributes.platform === config.entityPlatform;
}
let includeEntity = false;
if (configEntities.length) {
const includesEntity = configEntities.includes(entityId);
includeEntity = !!config.excludeItemsInEntitiesList !== includesEntity;
}
if (config.entityPlatform && configEntities.length) {
return matchesPlatform && includeEntity;
}
return matchesPlatform || includeEntity;
}
function isSonosCard(config) {
return config.type.indexOf("sonos") > -1;
}
function isQueueSupported(config) {
const effectivePlatform = config.entityPlatform ?? (isSonosCard(config) ? "sonos" : void 0);
return effectivePlatform === "sonos" || effectivePlatform === "music_assistant";
}
function sortEntities(config, filtered) {
if (config.entities) {
return filtered.sort((a2, b2) => {
const aIndex = config.entities?.indexOf(a2.entity_id) ?? -1;
const bIndex = config.entities?.indexOf(b2.entity_id) ?? -1;
return aIndex - bIndex;
});
} else {
return filtered.sort((a2, b2) => a2.entity_id.localeCompare(b2.entity_id));
}
}
function findPlayer(mediaPlayers, playerId) {
return mediaPlayers.find((member) => member.id === playerId);
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
var __defProp$R = Object.defineProperty;
var __decorateClass$R = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$R(target, key, result);
return result;
};
class IconButton extends i$5 {
constructor() {
super(...arguments);
this.disabled = false;
this.title = "";
}
render() {
return x`
`;
}
static get styles() {
return i$8`
:host {
display: inline-flex;
align-items: center;
justify-content: center;
align-self: center;
--sc-icon-button-size: var(--icon-button-size, 3rem);
--sc-icon-size: var(--icon-size, 2rem);
}
:host([hide]),
:host([hidden]) {
display: none !important;
}
button {
cursor: pointer;
background: none;
border: none;
padding: 0;
margin: 0;
color: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--sc-icon-button-size);
height: var(--sc-icon-button-size);
border-radius: 50%;
outline: none;
-webkit-tap-highlight-color: transparent;
position: relative;
overflow: hidden;
}
button::before {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
opacity: 0;
background: currentColor;
transition: opacity 0.12s;
}
button:hover::before {
opacity: 0.08;
}
button:active::before {
opacity: 0.12;
}
button:disabled {
cursor: default;
opacity: 0.38;
}
button:disabled::before {
display: none;
}
ha-svg-icon {
display: flex;
--mdc-icon-size: var(--sc-icon-size);
width: var(--sc-icon-size);
height: var(--sc-icon-size);
}
::slotted(ha-icon) {
--mdc-icon-size: var(--sc-icon-size);
}
`;
}
}
__decorateClass$R([
n$4()
], IconButton.prototype, "path");
__decorateClass$R([
n$4({ type: Boolean, reflect: true })
], IconButton.prototype, "disabled");
__decorateClass$R([
n$4({ reflect: true })
], IconButton.prototype, "selected");
__decorateClass$R([
n$4()
], IconButton.prototype, "title");
customElements.define("mxmp-icon-button", IconButton);
var __defProp$Q = Object.defineProperty;
var __decorateClass$Q = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$Q(target, key, result);
return result;
};
class PlayerFavoriteButton extends i$5 {
constructor() {
super(...arguments);
this.isFavorite = null;
this.favoriteLoading = false;
this.toggleFavorite = async () => {
if (this.favoriteLoading) {
return;
}
const songIdAtStart = this.store.activePlayer.attributes.media_content_id;
this.favoriteLoading = true;
try {
if (this.isFavorite) {
const success = await this.store.hassService.musicAssistantService.unfavoriteCurrentSong(this.store.activePlayer);
if (success && this.store.activePlayer.attributes.media_content_id === songIdAtStart) {
this.isFavorite = false;
}
} else {
const success = await this.store.hassService.musicAssistantService.favoriteCurrentSong(this.store.activePlayer);
if (success && this.store.activePlayer.attributes.media_content_id === songIdAtStart) {
this.isFavorite = true;
}
}
} finally {
this.favoriteLoading = false;
}
};
}
willUpdate(changedProperties) {
if (changedProperties.has("store")) {
const isMusicAssistant = this.store.hassService.musicAssistantService.isMusicAssistantPlayer(this.store.activePlayer);
const currentMediaContentId = this.store.activePlayer.attributes.media_content_id;
if (isMusicAssistant && currentMediaContentId !== this.lastMediaContentId) {
this.lastMediaContentId = currentMediaContentId;
this.isFavorite = null;
this.favoriteLoading = false;
this.refreshFavoriteStatus();
}
}
}
render() {
const isMusicAssistant = this.store.hassService.musicAssistantService.isMusicAssistantPlayer(this.store.activePlayer);
const favoriteClass = `favorite-button ${this.isFavorite ? "is-favorite" : ""} ${this.favoriteLoading ? "loading" : ""}`;
const favoriteTitle = this.isFavorite ? "Remove from favorites" : "Add to favorites";
return x`
`;
}
async refreshFavoriteStatus() {
const songIdAtStart = this.store.activePlayer.attributes.media_content_id;
const favorite = await this.store.hassService.musicAssistantService.getCurrentSongFavorite(this.store.activePlayer);
if (this.store.activePlayer.attributes.media_content_id === songIdAtStart) {
this.isFavorite = favorite;
}
}
static get styles() {
return i$8`
[hidden] {
display: none !important;
}
.favorite-button.is-favorite {
color: var(--accent-color);
}
.favorite-button.loading {
opacity: 0.5;
}
`;
}
}
__decorateClass$Q([
n$4({ attribute: false })
], PlayerFavoriteButton.prototype, "store");
__decorateClass$Q([
r$3()
], PlayerFavoriteButton.prototype, "isFavorite");
__decorateClass$Q([
r$3()
], PlayerFavoriteButton.prototype, "favoriteLoading");
customElements.define("mxmp-player-favorite-button", PlayerFavoriteButton);
var __defProp$P = Object.defineProperty;
var __decorateClass$P = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$P(target, key, result);
return result;
};
class PlayerControls extends i$5 {
constructor() {
super(...arguments);
this.prev = async () => await this.store.mediaControlService.prev(this.store.activePlayer);
this.play = async () => await this.store.mediaControlService.play(this.store.activePlayer);
this.pauseOrStop = async () => {
return this.store.config.player?.stopInsteadOfPause ? await this.store.mediaControlService.stop(this.store.activePlayer) : await this.store.mediaControlService.pause(this.store.activePlayer);
};
this.next = async () => await this.store.mediaControlService.next(this.store.activePlayer);
this.browseMedia = async () => this.store.mediaBrowseService.showBrowseMedia(this.store.activePlayer, this);
this.togglePower = async () => await this.store.mediaControlService.togglePower(this.store.activePlayer);
this.volDown = async () => {
await this.store.mediaControlService.volumeDown(this.getVolumePlayer(), !this.store.config.player?.volumeEntityId);
};
this.volUp = async () => {
await this.store.mediaControlService.volumeUp(this.getVolumePlayer(), !this.store.config.player?.volumeEntityId);
};
this.rewind = async () => {
const stepSize = this.store.config.player?.fastForwardAndRewindStepSizeSeconds || 15;
await this.store.mediaControlService.seek(this.store.activePlayer, this.store.activePlayer.attributes.media_position - stepSize);
};
this.fastForward = async () => {
const stepSize = this.store.config.player?.fastForwardAndRewindStepSizeSeconds || 15;
await this.store.mediaControlService.seek(this.store.activePlayer, this.store.activePlayer.attributes.media_position + stepSize);
};
}
render() {
const {
stopInsteadOfPause,
volumeEntityId,
controlsColor,
controlsLargeIcons,
showVolumeUpAndDownButtons,
hideControlShuffleButton,
hideControlPrevTrackButton,
showFastForwardAndRewindButtons,
hideControlNextTrackButton,
hideControlRepeatButton,
showBrowseMediaButton,
hideVolume
} = this.store.config.player ?? {};
const playing = this.store.activePlayer.isPlaying();
const pauseOrStopIcon = stopInsteadOfPause ? mdiStopCircle : mdiPauseCircle;
const playPauseIcon = playing ? pauseOrStopIcon : mdiPlayCircle;
const playPauseHandler = playing ? this.pauseOrStop : this.play;
const volumePlayer = this.getVolumePlayer();
const updateMemberVolumes = !volumeEntityId;
const hidePower = this.store.hidePower(true) === true;
const controlsColorStyle = controlsColor ? `--controls-color: ${controlsColor}` : "";
return x`
`;
}
getVolumePlayer() {
const volumeEntityId = this.store.config.player?.volumeEntityId;
if (volumeEntityId) {
if (this.store.config.allowPlayerVolumeEntityOutsideOfGroup) {
return findPlayer(this.store.allMediaPlayers, volumeEntityId) ?? this.store.activePlayer;
}
return this.store.activePlayer.getMember(volumeEntityId) ?? this.store.activePlayer;
}
return this.store.activePlayer;
}
static get styles() {
return i$8`
.main {
overflow: hidden auto;
}
.icons {
justify-content: center;
display: flex;
align-items: center;
}
.icons * {
color: var(--controls-color, inherit);
}
[hidden] {
display: none !important;
}
.big-icon {
--icon-button-size: 5rem;
--icon-size: 5rem;
}
.large-icons mxmp-icon-button {
--icon-size: 3rem;
--icon-button-size: 4rem;
}
.large-icons .big-icon {
--icon-size: 5rem;
--icon-button-size: 5rem;
}
.flex-1 {
flex: 1;
}
.browse-button {
float: right;
}
.large-icons {
margin-bottom: 2rem;
}
`;
}
}
__decorateClass$P([
n$4({ attribute: false })
], PlayerControls.prototype, "store");
customElements.define("mxmp-player-controls", PlayerControls);
const { I: t$1 } = Z, i$4 = (o2) => null === o2 || "object" != typeof o2 && "function" != typeof o2, f$1 = (o2) => void 0 === o2.strings, s$2 = () => document.createComment(""), r$2 = (o2, i5, n3) => {
const e2 = o2._$AA.parentNode, l2 = void 0 === i5 ? o2._$AB : i5._$AA;
if (void 0 === n3) {
const i6 = e2.insertBefore(s$2(), l2), c3 = e2.insertBefore(s$2(), l2);
n3 = new t$1(i6, c3, o2, o2.options);
} else {
const t2 = n3._$AB.nextSibling, i6 = n3._$AM, c3 = i6 !== o2;
if (c3) {
let t3;
n3._$AQ?.(o2), n3._$AM = o2, void 0 !== n3._$AP && (t3 = o2._$AU) !== i6._$AU && n3._$AP(t3);
}
if (t2 !== l2 || c3) {
let o3 = n3._$AA;
for (; o3 !== t2; ) {
const t3 = o3.nextSibling;
e2.insertBefore(o3, l2), o3 = t3;
}
}
}
return n3;
}, v = (o2, t2, i5 = o2) => (o2._$AI(t2, i5), o2), u$1 = {}, m$1 = (o2, t2 = u$1) => o2._$AH = t2, p = (o2) => o2._$AH, M2 = (o2) => {
o2._$AP?.(false, true);
let t2 = o2._$AA;
const i5 = o2._$AB.nextSibling;
for (; t2 !== i5; ) {
const o3 = t2.nextSibling;
t2.remove(), t2 = o3;
}
};
const t = { ATTRIBUTE: 1, CHILD: 2 }, e$1 = (t2) => (...e2) => ({ _$litDirective$: t2, values: e2 });
let i$3 = class i2 {
constructor(t2) {
}
get _$AU() {
return this._$AM._$AU;
}
_$AT(t2, e2, i5) {
this._$Ct = t2, this._$AM = e2, this._$Ci = i5;
}
_$AS(t2, e2) {
return this.update(t2, e2);
}
update(t2, e2) {
return this.render(...e2);
}
};
const s$1 = (i5, t2) => {
const e2 = i5._$AN;
if (void 0 === e2) return false;
for (const i6 of e2) i6._$AO?.(t2, false), s$1(i6, t2);
return true;
}, o$1 = (i5) => {
let t2, e2;
do {
if (void 0 === (t2 = i5._$AM)) break;
e2 = t2._$AN, e2.delete(i5), i5 = t2;
} while (0 === e2?.size);
}, r$1 = (i5) => {
for (let t2; t2 = i5._$AM; i5 = t2) {
let e2 = t2._$AN;
if (void 0 === e2) t2._$AN = e2 = /* @__PURE__ */ new Set();
else if (e2.has(i5)) break;
e2.add(i5), c$2(t2);
}
};
function h$1(i5) {
void 0 !== this._$AN ? (o$1(this), this._$AM = i5, r$1(this)) : this._$AM = i5;
}
function n$3(i5, t2 = false, e2 = 0) {
const r2 = this._$AH, h2 = this._$AN;
if (void 0 !== h2 && 0 !== h2.size) if (t2) if (Array.isArray(r2)) for (let i6 = e2; i6 < r2.length; i6++) s$1(r2[i6], false), o$1(r2[i6]);
else null != r2 && (s$1(r2, false), o$1(r2));
else s$1(this, i5);
}
const c$2 = (i5) => {
i5.type == t.CHILD && (i5._$AP ??= n$3, i5._$AQ ??= h$1);
};
class f extends i$3 {
constructor() {
super(...arguments), this._$AN = void 0;
}
_$AT(i5, t2, e2) {
super._$AT(i5, t2, e2), r$1(this), this.isConnected = i5._$AU;
}
_$AO(i5, t2 = true) {
i5 !== this.isConnected && (this.isConnected = i5, i5 ? this.reconnected?.() : this.disconnected?.()), t2 && (s$1(this, i5), o$1(this));
}
setValue(t2) {
if (f$1(this._$Ct)) this._$Ct._$AI(t2, this);
else {
const i5 = [...this._$Ct._$AH];
i5[this._$Ci] = t2, this._$Ct._$AI(i5, this, 0);
}
}
disconnected() {
}
reconnected() {
}
}
class s {
constructor(t2) {
this.G = t2;
}
disconnect() {
this.G = void 0;
}
reconnect(t2) {
this.G = t2;
}
deref() {
return this.G;
}
}
let i$2 = class i3 {
constructor() {
this.Y = void 0, this.Z = void 0;
}
get() {
return this.Y;
}
pause() {
this.Y ??= new Promise(((t2) => this.Z = t2));
}
resume() {
this.Z?.(), this.Y = this.Z = void 0;
}
};
const n$2 = (t2) => !i$4(t2) && "function" == typeof t2.then, h = 1073741823;
let c$1 = class c extends f {
constructor() {
super(...arguments), this._$Cwt = h, this._$Cbt = [], this._$CK = new s(this), this._$CX = new i$2();
}
render(...s2) {
return s2.find(((t2) => !n$2(t2))) ?? T;
}
update(s2, i5) {
const e2 = this._$Cbt;
let r2 = e2.length;
this._$Cbt = i5;
const o2 = this._$CK, c3 = this._$CX;
this.isConnected || this.disconnected();
for (let t2 = 0; t2 < i5.length && !(t2 > this._$Cwt); t2++) {
const s3 = i5[t2];
if (!n$2(s3)) return this._$Cwt = t2, s3;
t2 < r2 && s3 === e2[t2] || (this._$Cwt = h, r2 = 0, Promise.resolve(s3).then((async (t3) => {
for (; c3.get(); ) await c3.get();
const i6 = o2.deref();
if (void 0 !== i6) {
const e3 = i6._$Cbt.indexOf(s3);
e3 > -1 && e3 < i6._$Cwt && (i6._$Cwt = e3, i6.setValue(t3));
}
})));
}
return T;
}
disconnected() {
this._$CK.disconnect(), this._$CX.pause();
}
reconnected() {
this._$CK.reconnect(this), this._$CX.resume();
}
};
const m = e$1(c$1);
function n$1(n3, r2, t2) {
return n3 ? r2(n3) : t2?.(n3);
}
const n2 = "important", i$1 = " !" + n2, o = e$1(class extends i$3 {
constructor(t$12) {
if (super(t$12), t$12.type !== t.ATTRIBUTE || "style" !== t$12.name || t$12.strings?.length > 2) throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.");
}
render(t2) {
return Object.keys(t2).reduce(((e2, r2) => {
const s2 = t2[r2];
return null == s2 ? e2 : e2 + `${r2 = r2.includes("-") ? r2 : r2.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g, "-$&").toLowerCase()}:${s2};`;
}), "");
}
update(e2, [r2]) {
const { style: s2 } = e2.element;
if (void 0 === this.ft) return this.ft = new Set(Object.keys(r2)), this.render(r2);
for (const t2 of this.ft) null == r2[t2] && (this.ft.delete(t2), t2.includes("-") ? s2.removeProperty(t2) : s2[t2] = null);
for (const t2 in r2) {
const e3 = r2[t2];
if (null != e3) {
this.ft.add(t2);
const r3 = "string" == typeof e3 && e3.endsWith(i$1);
t2.includes("-") || r3 ? s2.setProperty(t2, r3 ? e3.slice(0, -11) : e3, r3 ? n2 : "") : s2[t2] = e3;
}
}
return T;
}
});
var __defProp$O = Object.defineProperty;
var __decorateClass$O = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$O(target, key, result);
return result;
};
class PlayerHeader extends i$5 {
render() {
const { headerEntityFontSize, headerSongFontSize, hideEntityName, hideArtistAlbum, showAudioInputFormat } = this.store.config.player ?? {};
const entityStyle = headerEntityFontSize ? { fontSize: `${headerEntityFontSize}rem` } : {};
const songStyle = headerSongFontSize ? { fontSize: `${headerSongFontSize}rem` } : {};
return x`
${getSpeakerList(this.store.activePlayer, this.store.predefinedGroups)}
${this.getSong()}
${this.getAlbum()} ${n$1(showAudioInputFormat, () => m(this.getAudioInputFormat()))}
`;
}
getSong() {
const { labelWhenNoMediaIsSelected, showSource } = this.store.config.player ?? {};
let song = this.store.activePlayer.getCurrentTrack();
song = song || labelWhenNoMediaIsSelected || "No media selected";
if (showSource && this.store.activePlayer.attributes.source) {
song = `${song} (${this.store.activePlayer.attributes.source})`;
}
return song;
}
getAlbum() {
const { showChannel, hidePlaylist } = this.store.config.player ?? {};
let album = this.store.activePlayer.attributes.media_album_name;
if (showChannel && this.store.activePlayer.attributes.media_channel) {
album = this.store.activePlayer.attributes.media_channel;
} else if (!hidePlaylist && this.store.activePlayer.attributes.media_playlist) {
album = `${this.store.activePlayer.attributes.media_playlist} - ${album}`;
}
return album;
}
async getAudioInputFormat() {
const sensors = await this.store.hassService.getRelatedEntities(this.store.activePlayer, "sensor");
const audioInputFormat = sensors.find((sensor) => sensor.entity_id.includes("audio_input_format"));
return audioInputFormat && audioInputFormat.state && audioInputFormat.state !== "No audio" ? x`${audioInputFormat.state}` : "";
}
static get styles() {
return i$8`
.info {
text-align: center;
}
.entity {
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--mxmp-font-size, 1rem);
font-weight: 500;
color: var(--secondary-text-color);
white-space: nowrap;
}
.song {
overflow: hidden;
text-overflow: ellipsis;
font-size: calc(var(--mxmp-font-size, 1rem) * 1.15);
font-weight: 400;
color: var(--accent-color);
}
.artist-album {
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--mxmp-font-size, 1rem);
font-weight: 300;
color: var(--secondary-text-color);
}
.audio-input-format {
color: var(--card-background-color);
background: var(--disabled-text-color);
white-space: nowrap;
font-size: smaller;
line-height: normal;
padding: 3px;
margin-left: 8px;
}
[hidden] {
display: none !important;
}
`;
}
}
__decorateClass$O([
n$4({ attribute: false })
], PlayerHeader.prototype, "store");
customElements.define("mxmp-player-header", PlayerHeader);
var __defProp$N = Object.defineProperty;
var __decorateClass$N = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$N(target, key, result);
return result;
};
class PlayerProgress extends i$5 {
constructor() {
super(...arguments);
this.mediaDuration = 0;
}
disconnectedCallback() {
if (this.tracker) {
clearInterval(this.tracker);
this.tracker = void 0;
}
super.disconnectedCallback();
}
render() {
this.mediaDuration = this.store.activePlayer?.attributes.media_duration || 0;
const showProgress = this.mediaDuration > 0;
if (showProgress) {
this.trackProgress();
}
return x`
${convertProgress(this.playingProgress)}
-${convertProgress(this.mediaDuration - this.playingProgress)}
`;
}
async handleSeek(e2) {
const progressWidth = this.progressBar.offsetWidth;
const percent = e2.offsetX / progressWidth;
const position = this.mediaDuration * percent;
await this.store.mediaControlService.seek(this.store.activePlayer, position);
}
progressBarStyle(mediaDuration) {
return o({ width: `${this.playingProgress / mediaDuration * 100}%` });
}
trackProgress() {
const position = this.store.activePlayer?.attributes.media_position || 0;
const playing = this.store.activePlayer?.isPlaying();
const updatedAt = this.store.activePlayer?.attributes.media_position_updated_at || 0;
if (playing) {
this.playingProgress = position + (Date.now() - new Date(updatedAt).getTime()) / 1e3;
} else {
this.playingProgress = position;
}
if (!this.tracker) {
this.tracker = setInterval(() => this.trackProgress(), 1e3);
}
if (!playing) {
clearInterval(this.tracker);
this.tracker = void 0;
}
}
static get styles() {
return i$8`
.progress {
width: 100%;
font-size: x-small;
display: flex;
--paper-progress-active-color: lightgray;
}
[hidden] {
display: none !important;
}
.bar {
display: flex;
flex-grow: 1;
align-items: center;
padding: 5px;
cursor: pointer;
}
.progress-bar {
background-color: var(--accent-color);
height: 50%;
transition: width 0.1s linear;
}
`;
}
}
__decorateClass$N([
n$4({ attribute: false })
], PlayerProgress.prototype, "store");
__decorateClass$N([
r$3()
], PlayerProgress.prototype, "playingProgress");
__decorateClass$N([
e$2(".bar")
], PlayerProgress.prototype, "progressBar");
const convertProgress = (duration) => {
if (duration == null || !isFinite(duration)) {
return "";
}
const date = new Date(duration * 1e3).toISOString().substring(11, 19);
const time = date.startsWith("00:") ? date.substring(3) : date;
return time.replace(/^0(\d)/, "$1");
};
customElements.define("mxmp-progress", PlayerProgress);
var __defProp$M = Object.defineProperty;
var __decorateClass$M = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$M(target, key, result);
return result;
};
class Volume extends i$5 {
constructor() {
super(...arguments);
this.updateMembers = true;
this.slim = false;
this.isPlayer = false;
this.sliderMoving = false;
this.startVolumeSliderMoving = 0;
this.togglePower = async () => await this.mediaControlService.togglePower(this.player);
}
render() {
this.config = this.store.config;
this.playerConfig = this.config.player ?? {};
this.mediaControlService = this.store.mediaControlService;
const volume = this.player.getVolume();
const max = this.getMax();
const isMuted = this.updateMembers ? this.player.isGroupMuted() : this.player.isMemberMuted();
const muteIcon = isMuted ? mdiVolumeMute : mdiVolumeHigh;
const disabled = this.player.ignoreVolume;
const sliderHeight = this.isPlayer && this.playerConfig.volumeSliderHeight;
const muteButtonSize = this.isPlayer && this.playerConfig.volumeMuteButtonSize;
return x`
${volume > 0 ? "0%" : ""}
${volume}%
${volume < max ? `${max}%` : ""}
${volume}%
`;
}
getMax() {
const volume = this.sliderMoving ? this.startVolumeSliderMoving : this.player.getVolume();
const dynamicThreshold = Math.max(0, Math.min(this.config.dynamicVolumeSliderThreshold ?? 20, 100));
const dynamicMax = Math.max(0, Math.min(this.config.dynamicVolumeSliderMax ?? 30, 100));
return volume < dynamicThreshold && this.config.dynamicVolumeSlider ? dynamicMax : 100;
}
async sliderMoved(e2) {
if (this.config.changeVolumeOnSlide) {
console.log("slider moved", this.config.changeVolumeOnSlide);
if (!this.sliderMoving) {
this.startVolumeSliderMoving = this.player.getVolume();
}
this.sliderMoving = true;
return await this.setVolume(e2);
}
}
async volumeChanged(e2) {
this.sliderMoving = false;
return await this.setVolume(e2);
}
async setVolume(e2) {
const newVolume = numberFromEvent(e2);
return await this.mediaControlService.volumeSet(this.player, newVolume, this.updateMembers);
}
async mute() {
return await this.mediaControlService.toggleMute(this.player, this.updateMembers);
}
static get styles() {
return i$8`
ha-control-slider {
--control-slider-color: var(--accent-color);
}
ha-control-slider.over-threshold {
--control-slider-color: var(--primary-text-color);
}
ha-control-slider[disabled] {
--control-slider-color: var(--disabled-text-color);
}
*[slim] * {
--control-slider-thickness: 10px;
--icon-button-size: 30px;
--icon-size: 20px;
}
*[slim] .volume-level {
display: none;
}
.volume {
display: flex;
flex: 1;
}
.volume-slider {
flex: 1;
padding-right: 0.6rem;
}
*[slim] .volume-slider {
display: flex;
align-items: center;
}
.volume-level {
font-size: calc(var(--mxmp-font-size, 1rem) * 0.75);
display: flex;
}
.percentage {
flex: 2;
}
.percentage,
.percentage-slim {
font-weight: bold;
align-self: center;
}
*[hide] {
display: none;
}
mxmp-icon-button {
height: 40px;
align-self: start;
}
`;
}
}
__decorateClass$M([
n$4({ attribute: false })
], Volume.prototype, "store");
__decorateClass$M([
n$4({ attribute: false })
], Volume.prototype, "player");
__decorateClass$M([
n$4({ type: Boolean })
], Volume.prototype, "updateMembers");
__decorateClass$M([
n$4()
], Volume.prototype, "volumeClicked");
__decorateClass$M([
n$4()
], Volume.prototype, "slim");
__decorateClass$M([
n$4()
], Volume.prototype, "isPlayer");
__decorateClass$M([
r$3()
], Volume.prototype, "sliderMoving");
__decorateClass$M([
r$3()
], Volume.prototype, "startVolumeSliderMoving");
function numberFromEvent(e2) {
return Number.parseInt(e2?.target?.value);
}
customElements.define("mxmp-volume", Volume);
var __defProp$L = Object.defineProperty;
var __decorateClass$L = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$L(target, key, result);
return result;
};
class Player extends i$5 {
constructor() {
super(...arguments);
this.imageLoaded = false;
}
render() {
this.resolveTemplateImageUrlIfNeeded();
this.preloadImageIfNeeded();
const {
artworkAsBackgroundBlur: blurAmount = 0,
artworkAsBackground: artworkAsBackgroundConfig,
controlsAndHeaderBackgroundOpacity: backgroundOpacity = 0.9,
backgroundOverlayColor: overlayColor,
hideArtwork: hideArtworkConfig,
controlsMargin,
hideHeader,
hideControls
} = this.store.config.player ?? {};
const hasRealArtwork = getArtworkImage(this.store, this.resolvedImageUrl).entityImage && this.imageLoaded;
const artworkAsBackground = (!!artworkAsBackgroundConfig || blurAmount > 0) && hasRealArtwork;
const opacityStyle = `--background-opacity: ${backgroundOpacity}${overlayColor ? `; --background-overlay-color: ${overlayColor}` : ""}`;
const containerStyle = artworkAsBackground ? blurAmount > 0 ? `--blur-background-image: ${getBackgroundImageUrl(this.store, this.imageLoaded, this.resolvedImageUrl)}; --blur-amount: ${blurAmount}px; ${opacityStyle}` : `${getBackgroundImage(this.store, this.imageLoaded, this.resolvedImageUrl)}; ${opacityStyle}` : "";
const hideArtwork = artworkAsBackground && !blurAmount || !!hideArtworkConfig;
const controlsMarginStyle = controlsMargin ? `margin: ${controlsMargin}` : "";
return x`
`;
}
static get styles() {
return playerSectionStyles;
}
preloadImageIfNeeded() {
const imageUrl = getArtworkImage(this.store, this.resolvedImageUrl)?.entityImage;
if (imageUrl === this.lastCheckedImageUrl) {
return;
}
this.lastCheckedImageUrl = imageUrl;
if (!imageUrl) {
this.imageLoaded = false;
return;
}
const img = new Image();
img.onload = () => {
if (this.lastCheckedImageUrl === imageUrl) {
this.imageLoaded = true;
}
};
img.onerror = () => {
if (this.lastCheckedImageUrl === imageUrl) {
this.imageLoaded = false;
}
};
img.src = imageUrl;
}
resolveTemplateImageUrlIfNeeded() {
const override = findArtworkOverride(this.store, this.store.activePlayer.attributes.entity_picture);
const templateUrl = override?.imageUrl?.includes("{{") ? override.imageUrl : void 0;
if (templateUrl && this.lastTemplateUrl !== templateUrl) {
this.lastTemplateUrl = templateUrl;
this.store.hassService.renderTemplate(templateUrl, "").then((result) => {
this.resolvedImageUrl = result;
});
} else if (!templateUrl) {
this.lastTemplateUrl = void 0;
this.resolvedImageUrl = void 0;
}
}
}
__decorateClass$L([
n$4({ attribute: false })
], Player.prototype, "store");
__decorateClass$L([
r$3()
], Player.prototype, "resolvedImageUrl");
__decorateClass$L([
r$3()
], Player.prototype, "imageLoaded");
const r = (r2, o2, t2) => {
for (const t3 of o2) if (t3[0] === r2) return (0, t3[1])();
return t2?.();
};
const LIBRARY_URI_PREFIX = "library://";
class MusicAssistantService {
constructor(hass) {
this.hass = hass;
}
/**
* Discover the Music Assistant config entry ID
* Returns the first found Music Assistant integration ID, or null if not found
*/
async discoverConfigEntryId() {
const entries = await this.hass.callWS({
type: "config_entries/get"
});
const musicAssistant = entries.find((entry) => entry.domain === "music_assistant" && entry.state === "loaded");
if (!musicAssistant) {
throw new Error("Music Assistant integration not found or not loaded");
}
return musicAssistant.entry_id;
}
/**
* Discover the mass_queue config entry ID (needed for send_command)
* Returns the first found mass_queue integration ID, or null if not found
*/
async discoverMassQueueConfigEntryId() {
const entries = await this.hass.callWS({
type: "config_entries/get"
});
const massQueue = entries.find((entry) => entry.domain === "mass_queue" && entry.state === "loaded");
if (!massQueue?.entry_id) {
throw new Error(MASS_QUEUE_NOT_INSTALLED);
}
return massQueue.entry_id;
}
/**
* Get favorites from Music Assistant library
*/
async getFavorites(configEntryId, mediaTypes = ["track", "album", "artist", "playlist", "radio"]) {
const allFavorites = [];
for (const mediaType of mediaTypes) {
try {
const response = await this.hass.callWS({
type: "call_service",
domain: "music_assistant",
service: "get_library",
service_data: {
config_entry_id: configEntryId,
favorite: true,
media_type: mediaType,
limit: 0
},
return_response: true
});
const items = response.response?.items ?? [];
allFavorites.push(...items.map((item) => this.transformFavoriteItem(item, mediaType)));
} catch (e2) {
console.warn(`Failed to get ${mediaType} favorites from Music Assistant:`, e2);
}
}
return allFavorites;
}
transformFavoriteItem(item, mediaType) {
let title = item.name;
if (mediaType === "track" && item.artists?.length) {
const artistNames = item.artists.map((a2) => a2.name).join(", ");
title = `${artistNames} - ${item.name}`;
}
const thumbnail = typeof item.image === "string" ? item.image : item.image?.path;
return {
title,
media_content_id: item.uri,
media_content_type: mediaType,
thumbnail,
can_play: true,
favoriteType: this.getFavoriteTypeLabel(mediaType)
};
}
getFavoriteTypeLabel(mediaType) {
switch (mediaType) {
case "track":
return "Tracks";
case "album":
return "Albums";
case "artist":
return "Artists";
case "playlist":
return "Playlists";
case "radio":
return "Radio";
default:
return mediaType;
}
}
/**
* Search Music Assistant for media
*/
async search(configEntryId, name, mediaType, limit = 50) {
try {
const response = await this.hass.callWS({
type: "call_service",
domain: "music_assistant",
service: "search",
service_data: {
config_entry_id: configEntryId,
name,
media_type: [mediaType],
limit
},
return_response: true
});
return this.transformResults(response.response, mediaType);
} catch (e2) {
console.error("Music Assistant search failed:", e2);
throw e2;
}
}
/**
* Search multiple media types with library filter
* @param libraryFilter - 'all' (no filter), 'library' (only library), 'non-library' (exclude library)
*/
async searchMultipleTypes(configEntryId, name, mediaTypes, limit = 50, libraryFilter = "all") {
const searchPromises = mediaTypes.map((type) => this.search(configEntryId, name, type, limit));
const resultsArrays = await Promise.all(searchPromises);
let allResults = resultsArrays.flat();
if (libraryFilter === "library") {
allResults = allResults.filter((item) => item.uri.startsWith(LIBRARY_URI_PREFIX));
} else if (libraryFilter === "non-library") {
allResults = this.filterLibraryItems(allResults);
}
return allResults;
}
/**
* Filter out items with library:// URIs
*/
filterLibraryItems(results) {
return results.filter((item) => !item.uri.startsWith(LIBRARY_URI_PREFIX));
}
/**
* Transform Music Assistant response to our internal format
*/
transformResults(response, mediaType) {
const items = this.getResultsForType(response, mediaType);
return items.map((item) => this.transformResultItem(item, mediaType));
}
getResultsForType(response, mediaType) {
switch (mediaType) {
case "artist":
return response.artists ?? [];
case "album":
return response.albums ?? [];
case "track":
return response.tracks ?? [];
case "playlist":
return response.playlists ?? [];
case "radio":
return response.radio ?? [];
default:
return [];
}
}
transformResultItem(item, mediaType) {
let title = item.name;
let subtitle;
if (mediaType === "track") {
const artistNames = item.artists?.map((a2) => a2.name).join(", ");
if (artistNames) {
title = `${artistNames} - ${item.name}`;
}
if (item.album?.name) {
subtitle = `(${item.album.name})`;
}
} else if (mediaType === "album") {
subtitle = item.artists?.map((a2) => a2.name).join(", ");
}
const imageUrl = typeof item.image === "string" ? item.image : item.image?.path;
return {
title,
subtitle,
uri: item.uri,
mediaType,
imageUrl,
favorite: item.favorite,
inLibrary: item.in_library ?? item.uri.startsWith(LIBRARY_URI_PREFIX),
itemId: item.item_id,
provider: item.provider
};
}
// --- Player-level Music Assistant methods ---
isMusicAssistantPlayer(mediaPlayer) {
return mediaPlayer.attributes.platform === "music_assistant";
}
async getCurrentSongFavorite(mediaPlayer) {
try {
const ret = await this.hass.callWS({
type: "call_service",
domain: "music_assistant",
service: "get_queue",
target: { entity_id: mediaPlayer.id },
return_response: true
});
return ret.response[mediaPlayer.id]?.current_item?.media_item?.favorite ?? null;
} catch (e2) {
console.warn("Error getting favorite status", e2);
return null;
}
}
async favoriteCurrentSong(mediaPlayer) {
try {
const buttonEntity = await this.findRelatedEntityId(mediaPlayer, "button", "favorite_current_song");
if (buttonEntity) {
await this.hass.callService("button", "press", { entity_id: buttonEntity });
return true;
}
return false;
} catch (e2) {
console.warn("Error favoriting current song", e2);
return false;
}
}
async unfavoriteCurrentSong(mediaPlayer) {
try {
await this.hass.callService("mass_queue", "unfavorite_current_item", {
entity: mediaPlayer.id
});
return true;
} catch (e2) {
console.warn("Error unfavoriting current song", e2);
return false;
}
}
async getCurrentQueueItemId(mediaPlayer) {
if (!this.isMusicAssistantPlayer(mediaPlayer)) {
return null;
}
try {
const ret = await this.hass.callWS({
type: "call_service",
domain: "music_assistant",
service: "get_queue",
target: { entity_id: mediaPlayer.id },
return_response: true
});
return ret.response[mediaPlayer.id]?.current_item?.queue_item_id ?? null;
} catch (e2) {
console.warn("Error getting current queue item id", e2);
return null;
}
}
async getQueue(mediaPlayer) {
try {
const ret = await this.hass.callWS({
type: "call_service",
domain: "mass_queue",
service: "get_queue_items",
service_data: {
entity: mediaPlayer.id,
limit_before: 5
},
return_response: true
});
const queueItems = ret.response[mediaPlayer.id];
if (!Array.isArray(queueItems)) {
return [];
}
return queueItems.map((item) => this.mapQueueItem(item));
} catch (e2) {
const error = e2;
if (error.message?.includes("mass_queue") || error.message?.includes("Service not found")) {
throw new Error(MASS_QUEUE_NOT_INSTALLED);
}
throw e2;
}
}
mapQueueItem(item) {
const artist = item.media_artist || "";
const title = item.media_title || "";
return {
title: artist ? `${artist} - ${title}` : title,
media_content_id: item.media_content_id,
media_content_type: "track",
thumbnail: item.media_image || void 0,
queueItemId: item.queue_item_id
};
}
async playQueueItem(mediaPlayer, queueItemId) {
await this.hass.callService("mass_queue", "play_queue_item", {
entity: mediaPlayer.id,
queue_item_id: queueItemId
});
}
async removeQueueItem(mediaPlayer, queueItemId) {
await this.hass.callService("mass_queue", "remove_queue_item", {
entity: mediaPlayer.id,
queue_item_id: queueItemId
});
}
async moveQueueItemNext(mediaPlayer, queueItemId) {
await this.hass.callService("mass_queue", "move_queue_item_next", {
entity: mediaPlayer.id,
queue_item_id: queueItemId
});
}
async playMedia(mediaPlayer, mediaId, enqueue, radioMode) {
await this.hass.callService("music_assistant", "play_media", {
entity_id: mediaPlayer.id,
media_id: [mediaId],
...enqueue && { enqueue },
...radioMode && { radio_mode: true }
});
}
/**
* Get collection items based on media type
*/
async getCollectionItems(uri, mediaType, massConfigEntryId) {
return this.getCollectionTracks(`get_${mediaType}_tracks`, uri, massConfigEntryId);
}
async getCollectionTracks(service, uri, massQueueConfigEntryId) {
try {
const ret = await this.hass.callWS({
type: "call_service",
domain: "mass_queue",
service,
service_data: { uri, config_entry_id: massQueueConfigEntryId },
return_response: true
});
const items = ret.response?.tracks;
if (!Array.isArray(items)) {
return [];
}
return items.map((item) => this.mapCollectionTrack(item));
} catch (e2) {
console.error(`Failed to get collection tracks (${service}):`, e2);
throw e2;
}
}
mapCollectionTrack(item) {
const artist = item.media_artist || "";
const title = item.media_title || "";
return {
title: artist ? `${artist} - ${title}` : title,
subtitle: item.media_album_name || void 0,
uri: item.media_content_id,
mediaType: "track",
imageUrl: item.media_image || void 0,
favorite: item.favorite
};
}
async findRelatedEntityId(mediaPlayer, entityType, namePart) {
const template = `{{ device_entities(device_id('${mediaPlayer.id}')) }}`;
const entities = await this.renderTemplate(template, []);
const matching = entities.filter((id) => id.includes(entityType)).map((id) => this.hass.states[id]).filter(Boolean);
return matching.find((e2) => e2?.entity_id?.toLowerCase().includes(namePart.toLowerCase()))?.entity_id;
}
renderTemplate(template, defaultValue) {
return new Promise((resolve) => {
try {
this.hass.connection.subscribeMessage(
(response) => {
try {
resolve(response.result);
} catch {
resolve(defaultValue);
}
},
{ type: "render_template", template }
).then((unsub) => unsub());
} catch {
resolve(defaultValue);
}
});
}
/**
* Send a command to Music Assistant via mass_queue.send_command
*/
async sendMassCommand(massQueueConfigEntryId, command, data = {}, returnResponse = false) {
if (returnResponse) {
const ret = await this.hass.callWS({
type: "call_service",
domain: "mass_queue",
service: "send_command",
service_data: {
command,
data,
config_entry_id: massQueueConfigEntryId
},
return_response: true
});
return ret.response.response;
}
await this.hass.callService("mass_queue", "send_command", {
command,
data,
config_entry_id: massQueueConfigEntryId
});
return void 0;
}
/**
* Add an item to favorites via Music Assistant API
* Uses music/favorites/add_item which accepts a URI and handles
* adding to library + setting favorite automatically
*/
async addToFavorites(massQueueConfigEntryId, uri) {
try {
await this.sendMassCommand(massQueueConfigEntryId, "music/favorites/add_item", { item: uri });
return true;
} catch (e2) {
console.error("Failed to add to favorites:", e2);
return false;
}
}
/**
* Remove an item from favorites via Music Assistant API
* Uses music/favorites/remove_item which requires media_type and library_item_id
*/
async removeFromFavorites(massQueueConfigEntryId, uri, mediaType, itemId, provider) {
try {
const libraryItemId = await this.resolveLibraryItemId(massQueueConfigEntryId, uri, mediaType, itemId, provider);
if (!libraryItemId) {
console.error("Could not determine library item ID for unfavoriting");
return false;
}
await this.sendMassCommand(massQueueConfigEntryId, "music/favorites/remove_item", {
media_type: mediaType,
library_item_id: libraryItemId
});
return true;
} catch (e2) {
console.error("Failed to remove from favorites:", e2);
return false;
}
}
/**
* Add an item to the library via Music Assistant API
*/
async addToLibrary(massQueueConfigEntryId, uri) {
try {
await this.sendMassCommand(massQueueConfigEntryId, "music/library/add_item", { item: uri });
return true;
} catch (e2) {
console.error("Failed to add to library:", e2);
return false;
}
}
/**
* Remove an item from the library via Music Assistant API
*/
async removeFromLibrary(massQueueConfigEntryId, uri, mediaType, itemId, provider) {
try {
const libraryItemId = await this.resolveLibraryItemId(massQueueConfigEntryId, uri, mediaType, itemId, provider);
if (!libraryItemId) {
console.error("Could not determine library item ID for removal");
return false;
}
await this.sendMassCommand(massQueueConfigEntryId, "music/library/remove_item", {
media_type: mediaType,
library_item_id: libraryItemId
});
return true;
} catch (e2) {
console.error("Failed to remove from library:", e2);
return false;
}
}
/**
* Resolve the library item ID from various sources
*/
async resolveLibraryItemId(massQueueConfigEntryId, uri, mediaType, itemId, provider) {
if (provider === "library" && itemId) {
return itemId;
}
if (uri.startsWith(LIBRARY_URI_PREFIX)) {
return uri.split("/").pop();
}
if (itemId && provider) {
const libraryItem = await this.sendMassCommand(
massQueueConfigEntryId,
"music/get_library_item",
{
media_type: mediaType,
item_id: itemId,
provider_instance_id_or_domain: provider
},
true
);
return libraryItem?.item_id ? String(libraryItem.item_id) : void 0;
}
return void 0;
}
}
class HassService {
constructor(hass, section, card) {
this.hass = hass;
this.currentSection = section;
this.card = card;
this.musicAssistantService = new MusicAssistantService(hass);
}
async callWithLoader(action) {
this.card.dispatchEvent(customEvent(CALL_MEDIA_STARTED, { section: this.currentSection }));
try {
return await action();
} finally {
this.card.dispatchEvent(customEvent(CALL_MEDIA_DONE));
}
}
async callMediaService(service, inOptions) {
await this.callWithLoader(() => this.hass.callService("media_player", service, inOptions));
}
async callService(domain, service, serviceData) {
await this.hass.callService(domain, service, serviceData);
}
async renderTemplate(template, defaultValue) {
return new Promise((resolve) => {
const subscribeMessage = {
type: "render_template",
template
};
try {
this.hass.connection.subscribeMessage((response) => {
try {
resolve(response.result);
} catch {
resolve(defaultValue);
}
}, subscribeMessage).then((unsub) => unsub());
} catch {
resolve(defaultValue);
}
});
}
async getRelatedEntities(player, ...entityTypes) {
const template = `{{ device_entities(device_id('${player.id}')) }}`;
const result = await this.renderTemplate(template, []);
return result.filter((item) => entityTypes.some((type) => item.includes(type))).map((item) => this.hass.states[item]);
}
isMusicAssistant(mediaPlayer) {
return this.musicAssistantService.isMusicAssistantPlayer(mediaPlayer);
}
async getQueue(mediaPlayer) {
if (this.isMusicAssistant(mediaPlayer)) {
return await this.musicAssistantService.getQueue(mediaPlayer);
}
return await this.getSonosQueue(mediaPlayer);
}
async getSonosQueue(mediaPlayer) {
const ret = await this.hass.callWS({
type: "call_service",
domain: "sonos",
service: "get_queue",
target: {
entity_id: mediaPlayer.id
},
return_response: true
});
const queueItems = ret.response[mediaPlayer.id];
return queueItems.map((item) => {
return {
title: `${item.media_artist} - ${item.media_title}`,
media_content_id: item.media_content_id,
media_content_type: item.media_content_type
};
});
}
async removeFromQueue(mediaPlayer, queuePosition, queueItemId) {
if (this.isMusicAssistant(mediaPlayer) && queueItemId) {
await this.musicAssistantService.removeQueueItem(mediaPlayer, queueItemId);
} else {
await this.hass.callService("sonos", "remove_from_queue", {
entity_id: mediaPlayer.id,
queue_position: queuePosition
});
}
}
async clearQueue(mediaPlayer) {
await this.hass.callService("media_player", "clear_playlist", { entity_id: mediaPlayer.id });
}
async setSleepTimer(mediaPlayer, sleepTimer) {
await this.hass.callService("sonos", "set_sleep_timer", {
entity_id: mediaPlayer.id,
sleep_time: sleepTimer
});
}
async cancelSleepTimer(player) {
await this.hass.callService("sonos", "clear_sleep_timer", {
entity_id: player.id
});
}
async setSwitch(entityId, state) {
await this.hass.callService("switch", state ? "turn_on" : "turn_off", {
entity_id: entityId
});
}
async setNumber(entityId, value) {
await this.hass.callService("number", "set_value", {
entity_id: entityId,
value
});
}
async setRelatedEntityValue(player, name, value) {
if (value === void 0) {
return;
}
const type = typeof value === "number" ? "number" : "switch";
const entityId = await this.getRelatedEntityId(player, type, name);
if (!entityId) {
return;
}
if (typeof value === "number") {
await this.setNumber(entityId, value);
} else {
await this.setSwitch(entityId, value);
}
}
async getRelatedEntityId(player, entityType, namePart) {
const entities = await this.getRelatedEntities(player, entityType);
return entities.find((e2) => e2?.entity_id?.toLowerCase().includes(namePart.toLowerCase()))?.entity_id;
}
}
const DEFAULT_MEDIA_THUMBNAIL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAAAXNSR0IArs4c6QAAAIRlWElmTU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAAB4AAAAAQAAAHgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAHigAwAEAAAAAQAAAHgAAAAATk6PlwAAAAlwSFlzAAASdAAAEnQB3mYfeAAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KGV7hBwAAE/xJREFUeAHtXWlwVUd2PtKTkAAjsNCGBGhBCyD2TdhiUmYxHqdSE49ZHGOCcSbxLH8S44knsSvjpVw1XlMVXIWTYcoGe2Kn2Mrx1NgVsw7GGIPZJRACIXYQEkIIBNpvznfuO4+nh56kJ7373s3LbXHfXbr79OnzdZ/bffr0JcrgQE6IWAlER2zNnIqJBByAI7whOAA7AEe4BCK8ek4PdgCOcAlEePWcHuwAHOESiPDqOT3YATjCJRDh1XN6sANwhEsgwqvn9GAH4AiXQIRXz+nBDsARLoEIr57Tgx2AI1wCEV49pwc7AEe4BCK8ek4PdgCOcAlEePWcHuwAHOESiPDqOT3YATjCJRDh1YuJ8PqFtnq8C0j3AZkbgsy7qKgowhGOEOXsTQpc7NjO1dmWruho/288pA8HyA7AXvj6guZ735Oe2NzcRE1NTdTc3Ez19TfpzJkzNG7cOEpOTpZGEWqQ/9+r6Pb2doG4M/A6AwPpGxoaBMT6+nq6du0aXb9+nW7w9YUL5+nwoSNUVXWVrly5QocOHRDaR44ccQD26kghvfRVq82tLdTY2Cgg3rx5k+rq6qimuoYBq6La2lq6eOECHS8ro8rKSio/UdYlr1nZOXSm8jTV1l3vMp2VkWHpwVB9qv58BWxlZZU2ykbvBJCHDx+hixcv0tWqKrpaU02XLl0S8PZ+v5/qrtVoli7PQ5NTqWjGNMrOzqaM4cMpIz2dEocOpY/WrhWAQTNcIeQAq3A7U3+hEoLycOvWLZo5s6jbYocmpdCUKZMpJyeHRjCAacOGUWpqGqvdJLr//vspYfBgGjigP8X3j6dY112Rfrdnj9C+eOGinNGYtexuCw1SgrvcBIlgd2Siogw6d+4cq8BGio11idBC3Yu1cQ0aNIieXLKEPv3kE5o1axYV8mBoxPCRlJbGAKYkU1JyIg0ZwgAmJNB9991H/fsPYJ67FhkAxHva5XLRKG4QCJVnzlJraxvFxLi6E0/Q47vmNsjFoeIAc/e3u+nJv1pCkydPom++2c2C609tbW0ilCAX2SW5uLg4ys3NlTQPzZ5N//LrX1O/mK5FAgBxeAdtMHiGa70fMXKkJDtWWkoNtxtoMDeUUAf/EzcLORk/brxQP3jwEH311VdyjRYPkH2FZwUbAEDLGZ6RIUXgmYKLODRG7wPPcCAdGqn3gWd6ePOblpYmtzu2b6P6GzfkWsv1TmfldVgAHsbvsFGj8qRejz32GL351js8taiSHgxBqWCtrLgKWnsZpjVNLc2eIr0BxHVnAHoS+7lI4oHWkKShEovpVDhCSAGGkIiNeYmJQ+mJxYulvhMmTKR/+tU/8nsvjTZs2ETXamo9vQNAWx3SubEh7Nu3jxpuNQSlOLOeRAlDEujPH/mh0Lx69WpQaAdKJOQAK2iTeVSKcIuFOmZsIaWkDqNFixbQuPETaA1PL2prrwvQrBelR2uPC7SC3aVPTEyUJIcOHhRjBW6CVVb/uP40ZswYoY+pGAIrczmH6iekAJuVMiuYn2+qaBePLI8fK+V56GUqLp5Frpgoemb5cppZ/APaum0btTPAqiK1cQRDONrLMDqeMcOcKl2rCY4aBW3lNTMzU9g9e/asnKOiIxxgFeyIESOosHA8nSw/QcuefoZmz53PI+pdYikaM6ZQjAzz5s6l5557jg4cOCC9CkCr4PoKsvKBqdKkyaY2qaq60ley9+RPz0iXZydPnRLzJm6CpSHuKayTByHvwSJY7pUwECxY+LiwNHHCBNq0cT198cUX9NJLL9Hx46VUXV3F7+okWrlyJU2dOpVefuUVsTQBZIy2gxEg6BieFo0tNNXopUumGg0GbaWRxgYRBBg9YPoMeeBKhjwwQFLmho0bMKE0Hn30UePOnTvyjHuowQMS4/PPPzeKimZK/PTpM+SMtKXHSyWd0ugL80pj/cb1Qp8bl4HyEfTcW/qav7q62sjKzBH6J0+eFHJabm9pB5IP6iLkQSt4pOSoB7jy8nLho7W11cNPXd0NY9X7/y5pCkaP8aQ9ffq0pFEhejIEeKF87Ny1U2gvWrTI4JWioNBWVpqamo1ly5YL/d27d8tjLVfTWHkOuYqGitL3X0bGcHrgwQfxSAz8OCOOKyxqePDgBPr5z34qU5gTZccJNmGE3/zmTXkXY9aFtH0Nyckm3c1btvKo/paQCwZdZo769Yul/DzTWoZlxFCHsACslRzMRvqHHnpIbrFmiqAjZli2uIfKMW3aNCo9doznyFcpLz+fVq/+Dzp//jynRmPo+1w5IWGwlF13vVbWduUmCD+YASCMyh0l5wu81IigDVxuLP4JC8CoIMBz8Xnq1GlSxW08JdJBiPYegK2DqrE8n/xgzYc86i6X9N9/v88tmt5PO1TQAwYMoHkPzxd6NTU9WyJ0F96jU4bbHHqKR9Ksnj1aqkeZ+5goLACbPJutu8A9H/7yyy95LfayRCnAWrfoKJPNubPn6iMqLz8l16rSPRG9uEjgufAU91Tp8mWTh16Q8ZslKSlJ4kqw6HDnjpkuCK8WvwV6RYQRYLPnwS49adIUYencubNerHldujtpamoqLV26TCIwvUHoff+9+76HlsjKyhZ6VqhRtZZt3bKZ/bTqpZy+jxyETLc/YQPYVI/mfHjOnNnC6DF+zyKo6pQb3Mu71qC4uH6E9zHCkCFD5AxB+aaXiB7+qLZIS0uVHKdPV7IabQ+KGlW+sJ68YMECoX+d3X5CGcIKME855R2rduldu3fTbVZhEIwKXoTh1U1TUkx1l8iGEoQO6eRJYD+8CCgZ0tPNRYdjx47T7du3AyPSTWqsd48fby6RVlcHxxzaTZGe6LAB7OGAL0YXFMjthnXr6OrVKrn2BU7vMSBCGJmZJee+/mjb0ffk1q2sRt1rt32lDd2DwSSC2qQvXzb9s7gNhySEFWBVYbBLp7CbDII5/fHfM1taWiSd9ji56dOPKWmo/Pz80UKpxr12q40qEPLI097eJgdA1Dqmp5uOBXBXQohyDxwDod2btLYAeCgvjP/lj/5C+D9RdkLOKhitlN7DMQANAg5vCPpc0wV61vwDBw6kqdPMwV4V28EDDSaw5rs7OtrFrx4XkzDozp1GIZXt9s86ceIEO8WbjbQ3DShQvsyhaKC5gpgelcSIeMaMGbT6t6vFX2vp0qUUHx8v71cAgDQKREVFBT377LNsIerX4XlfWAJ9lKdOcrp221Oayh94xFy+tPQYHTlymMq4sZ5hh7vRowv4lZJJQ7ghf7V5s1jLEhPNMURPy+h1OmYurEHtsju//hqjHTlYjQlPGqc2Z9ipJ0yYYLBRpEN8oBUAXaUtec31BeN9t937lVdf7TFJ5Q0ZNm/ZYjzwwAOeemh99JyeMVziQrnoEPYerC0T/sYaYGyAGvYN8GvCiHSy2yihvdo3nb97xkCiMO/V0MburG08mu/XL8bjYVlRUUmNTc0Uz9My5PFXjsZhNvBv771HK/7h74Ts6NFjmWYbuwbfFhU9aNBAabrx8XESj3qoN6fyYdU57ACr8LA5a8HCRbRxw3pWb0dEZWucChKmvsXsy4UBEUan3kB1JyDv9PCugA/WgQP72Xp2hQDyxEkTPevM2//0J1Gj8XGJXQKsZX700VoBd9LkKVTFW1zKysz5PFvWeZAQTddrYf40KDMrW7LALFtUVOS34SjdoJxZeGENWH5VNffWW2+LClu+/BmjmZfZNGj80aNHjZKSEnncQcVqQj9nTVt/85axcuV7flUoUMjKMtduT1VUdFmO0iw7US70ckblGfH9B3F3jzWiXP2MqOiOR7Qrzug/MEHS5ufnG7xhTehr3fyw3ufHNujBxOoMCw8umsgelghr1nxKr7/+OmWwuwvXUFo6eiC2YSLgWU97L/IhLRYRfvK3f0ef//dnNIynLIN5BamZ3WR1RDuAVX+Dl4ED+4l00CWF+vyob9XGTRslxsW+Vo0w0sDnCjB2EqApKCqG7ejlsr0USbR+nSQPyqOwA4xaqKch9v6YoVG2YnoDDJAgDARV3Wbarn+RD3Pnl19+WcCdMmUq7w48SZdZNRPBCGHOg01UeLtJrsnDgf376Qe8vaWzshSUdkaywb1+jEZqBqXnvnWf0NDwLr5W08D29L8WlyVEdUa/Y86+3d0dbfSNTp9yayXThqXS/PmPCK3jnWzNRDpN25MCIVSEr9mZb9WqVfyenSzvXWzO5m7NvS2Wjxj3EQtp8x4ic46KqQ6P2qU8bVhaJnjAM6ZAifcnymPRKPz83u7rHqQZLTRyhDmQXLx4Ee9xipVxRCD10fIDOdsGYAgMLqzFxaaHx769+6iV1069e24gFUNaETqft2zZIlnNtV7TkUC1hkTIj6kdFEwArdd309y90rjp06bLQ+w7iuNRstHO+dpb3Ucznw0eFCbQFHYcPHjwAD3zNz+h+Y+YjdhqcMGYLQAGIyqwiRPN9/Afv/yCd8+bi+8ah3Q9DZqnkT+nUF5+UrLBqhTFniKdB+59/K8f9yyEwsJx0stApzMg9FlxcTG79j5P+/d/T9mZI9ncWSDbS9OGpVFuXj4VjM6j2mvVBJW/5Kmn6K2336Y4NtJAuyiNzvkJzlNbvIO9q5KdnS23Z09Xil06NcVcxvNOE8g1LF6pqabPFdQiWzgomkFm3Mzg1qrodXHx2B5qAvzDR80tJ10BjDhXTDS99tqrbDpNoRdf/JVf1tau/Yh3biyUebwO/PwmDmYEM2mLwJUWPmqu1RjTpk+H+I3f/+fv5ZlOSQJlVPNt+uwzoVc009vK5JJnKIe7rpGZmW2MnzBRnv32d6ulKOWpq3K1DKQ5dOiw8cYbbxhPP/208cQTTxhvvPmmsXXrVqO6usZDwju956GFF1CNtggqTLYAGc//8nkR9AsvvOCZI2t8IMxqntuNjcazP/2Z0MzLyzdYdRppw9KNTJ7zFhSMNkbl5kkcwF616n2jhU2Z7fhzN7ruyvRNx1/ZMZqaGjtkQxrfdB0SWHRjG4BRPxXAxx9/LAJ/sLjY4A+fSNU1LlA5aD52lTHeeecdD5Bmz5UZqzxb+tQy49tvv/OQ13yeB91cID16p3c+fRbqXuvNqq2+k8UCkZHv3r17xZTHIIjZEt4QGodngQbvvBUVp3nT+f/w9pjjYu8ezjbw8byjsYCdDvB5BhaOkA/FACjQevQmvS0BxnLdqLwx1HTnJtumN9LjCx7vE8AQDIDDoVMnXPuC6N0QeiNMO+YJ+zQJgoZgcWhISkqmJxcvlNuDhw7L2RcMTdvTM/IDXJSj4HqXjWsFv6c0/y+kCwvAKlgVNASrB4QG78lH3MaAHTt2yMfIABDS9zWgHG0sCrr3s77St1v+kM+DVQ2qkOvYwQ0Upsts3L/C7jg4YysJvpmBsGvXTrrIz7BEqA3CbkK0Mz8hBVjB5R13YvnZtn07rVu/no4eNtWwr6DGFhYSPkFUxn5MhWPH+kY79z2QQEgAVtUKVVh5+qwsBX7w4e887PGclOAOi3jYbnG+caOedwHckjTf7NpFj//4x2Z8J4MjDyHn4h4JWD+K5tcmD6EI+4vYZ4nmP/ywMDGOv5VVzw5q1fxVHXx+l6eRYlNy/8jKTiw746Wnp9LZM5W0h3fIwwtCtcA9NXEedCoBSwdZ6Ln4A7if/NenAm5yShpl5+RSSclROseuM02NTYIpVtrMw1wSxHVL823P1+/WrVvfYZrTaW2ch/dIwHKAMZjauGkTPfXkEhrLn0tqaWllNV3BBv84WY+VRoDRse8AWe5ddJsd1xCw40Gd3u+phfPArwQsewerKi0pOUYLeeNVzqhcOnv+onhAYKHd3LiNpZyuAvd+l9kGscqDd7MTApOAZRIDGK3cW//13XeFI7bHUsPNOu61KBLdsztwka1dnABwBXMlHOR1wIZnTuheApYAjN6LUMofOPtwzQf82YUCGSi5YuLdqrhrcPHehmqPieWv0LLbDMK8efPk7AAsYujxjyUAa+kl7v2+7bzfFs4jvGCmUX7PANfF+3ra25p412EuVVScoldffc2z/VINJH4JOBEdJBB0gM1Rs9lD9cNiLeiF7BZ7z0CqAyuIZnDZ26KttZG9G/NkpD0sLYN+8YufS0p2CfCYGX2yOrd+JBB0gOHMBiAQsjKz5DyAP3VP7FXYVe+TQRfna2NfZXzKsOKU6Ue1fcdWwt5dbMmMxtzJCQFJIOgAe5deNL1IbvkrdrxtI0fUrtEOT0N4HuKsRxul8Cf0c3KyZCcePmU4a9af8bezzsg6rTki9+cs512ic+0rAcssWbBMRbOX/x/+8Ef6kXvvLzZY4z0M70ZsxIIBBF+bbWE/ZbxrNbz4zy/Ril8+R0MThzqWKxVKb888KrUmmD50Qnvnzp3GnDlzoLf9Hvy9LGPFihXsNrPHww/7JnuunYveScCyHowGxyx53rv438KwO3DPnu/o5MlTNJL/wwqs++LTSFlZmXxkEb58p+9p77y9bbxOPh7bol1YLQi1avWkHKQFyAp0T/I4afxLICQAo3i0I7NXYlEBG8ngOiMxwp1+lMQBVsQRtJ+QARw0jh1CAUnA0mlSQJw4iS2RgAOwJWK1D1EHYPtgYQknDsCWiNU+RB2A7YOFJZw4AFsiVvsQdQC2DxaWcOIAbIlY7UPUAdg+WFjCiQOwJWK1D1EHYPtgYQknDsCWiNU+RB2A7YOFJZw4AFsiVvsQdQC2DxaWcOIAbIlY7UPUAdg+WFjCiQOwJWK1D1EHYPtgYQknDsCWiNU+RB2A7YOFJZw4AFsiVvsQdQC2DxaWcOIAbIlY7UPUAdg+WFjCiQOwJWK1D1EHYPtgYQknDsCWiNU+RP8X5GFBVoXc8LcAAAAASUVORK5CYII=";
function hasItemsWithImage(items) {
return items.some((item) => item.thumbnail);
}
function getValueFromKeyIgnoreSpecialChars(customFavoriteThumbnails, currentTitle) {
for (const title in customFavoriteThumbnails) {
if (removeSpecialChars(title) === removeSpecialChars(currentTitle)) {
return customFavoriteThumbnails[title];
}
}
return void 0;
}
function getThumbnail(mediaItem, config, itemsWithImage) {
const favoritesConfig = config.mediaBrowser?.favorites ?? {};
const overrides = config.player?.mediaArtworkOverrides;
const artworkOverride = overrides ? findMatchingOverride(overrides, { media_title: mediaItem.title, media_content_id: mediaItem.media_content_id }, mediaItem.thumbnail) : void 0;
let thumbnail = artworkOverride?.imageUrl ?? getValueFromKeyIgnoreSpecialChars(favoritesConfig.customThumbnails, mediaItem.title) ?? mediaItem.thumbnail;
if (!thumbnail) {
thumbnail = getValueFromKeyIgnoreSpecialChars(favoritesConfig.customThumbnailsIfMissing, mediaItem.title);
if (itemsWithImage && !thumbnail) {
thumbnail = favoritesConfig.customThumbnailsIfMissing?.["default"] || DEFAULT_MEDIA_THUMBNAIL;
}
} else if (thumbnail?.match(/https:\/\/brands\.home-assistant\.io\/.+\/logo.png/)) {
thumbnail = thumbnail?.replace("logo.png", "icon.png");
}
return thumbnail || "";
}
function removeSpecialChars(str) {
return str.replace(/[^a-zA-Z0-9 ]/g, "");
}
function indexOfWithoutSpecialChars(array, str) {
let result = -1;
array.forEach((value, index) => {
if (removeSpecialChars(value) === removeSpecialChars(str)) {
result = index;
}
});
return result;
}
function stringContainsAnyItemInArray(array, str) {
return !!array.find((value) => str.includes(value));
}
const IGNORED_MEDIA_SOURCES = ["media-source://tts", "media-source://camera", "media-source://image", "media-source://image_upload"];
function filterOutIgnoredMediaSources(items) {
return items.filter((item) => !IGNORED_MEDIA_SOURCES.some((src) => item.media_content_id?.startsWith(src)));
}
function getGridItemSize(_itemsPerRow, isPortrait) {
return { width: "100px", height: isPortrait ? "180px" : "150px" };
}
function itemsWithFallbacks(mediaPlayerItems, config) {
const itemsWithImage = hasItemsWithImage(mediaPlayerItems);
return mediaPlayerItems.map((item) => {
const thumbnail = getThumbnail(item, config, itemsWithImage);
return {
...item,
thumbnail
};
});
}
function renderFavoritesItem(item, showTitle = true, titleColor, titleBgColor) {
const titleStyle = o({
color: titleColor ?? "",
backgroundColor: titleBgColor ?? ""
});
return x`
${item.title}
`;
}
class MediaBrowseService {
constructor(hass, config) {
this.massConfigEntryId = null;
this.massConfigDiscoveryDone = false;
this.hass = hass;
this.config = config;
this.musicAssistantService = new MusicAssistantService(hass);
}
isMusicAssistant(player) {
return player.attributes.platform === "music_assistant";
}
async getMassConfigEntryId() {
if (!this.massConfigDiscoveryDone) {
this.massConfigEntryId = await this.musicAssistantService.discoverConfigEntryId();
this.massConfigDiscoveryDone = true;
}
return this.massConfigEntryId;
}
async getFavorites(player) {
if (!player) {
return [];
}
let favorites;
if (this.isMusicAssistant(player)) {
favorites = await this.getMusicAssistantFavorites();
} else {
favorites = await this.getFavoritesForPlayer(player);
favorites = favorites.flatMap((f2) => f2);
favorites = this.removeDuplicates(favorites);
favorites = favorites.length ? favorites : this.getFavoritesFromStates(player);
}
const exclude = this.config.mediaBrowser?.favorites?.exclude ?? [];
return favorites.filter((item) => {
const titleNotIgnored = !stringContainsAnyItemInArray(exclude, item.title);
const contentIdNotIgnored = !stringContainsAnyItemInArray(exclude, item.media_content_id ?? "");
return titleNotIgnored && contentIdNotIgnored;
});
}
async getMusicAssistantFavorites() {
const configEntryId = await this.getMassConfigEntryId();
if (!configEntryId) {
console.warn("Music Assistant config entry not found");
return [];
}
return this.musicAssistantService.getFavorites(configEntryId);
}
removeDuplicates(items) {
const seen2 = /* @__PURE__ */ new Set();
return items.filter((item) => {
const key = item.media_content_id || item.title;
if (!key || seen2.has(key)) {
return false;
}
seen2.add(key);
return true;
});
}
async getFavoritesForPlayer(player) {
const mediaRoot = await browseMediaPlayer(this.hass, player.id);
const favoritesStr = "favorites";
const favoritesDir = mediaRoot.children?.find(
(child) => child.media_content_type?.toLowerCase() === favoritesStr || child.media_content_id?.toLowerCase() === favoritesStr || child.title.toLowerCase() === favoritesStr
);
if (!favoritesDir) {
return [];
}
const favorites = [];
await this.browseDir(player, favoritesDir, favorites);
return favorites;
}
async browseDir(player, favoritesDir, favorites) {
const dir = await browseMediaPlayer(this.hass, player.id, favoritesDir.media_content_id, favoritesDir.media_content_type);
for (const child of dir.children ?? []) {
if (child.can_play) {
favorites.push({ ...child, favoriteType: dir.title });
} else if (child.can_expand) {
await this.browseDir(player, child, favorites);
}
}
}
getFavoritesFromStates(mediaPlayer) {
const titles = mediaPlayer.attributes.source_list ?? [];
return titles.map((title) => ({ title }));
}
showBrowseMedia(activePlayer, element) {
const detail = {
entityId: activePlayer.id,
view: "info"
};
element.dispatchEvent(customEvent(HASS_MORE_INFO, detail));
}
}
class MediaControlService {
constructor(hassService, config) {
this.hassService = hassService;
this.config = config;
}
isMusicAssistant(mediaPlayer) {
return mediaPlayer.attributes.platform === "music_assistant";
}
async join(main, memberIds) {
await this.hassService.callMediaService("join", {
entity_id: main,
group_members: memberIds
});
}
async unJoin(playerIds) {
await this.hassService.callMediaService("unjoin", {
entity_id: playerIds
});
}
async activatePredefinedGroup(pg) {
for (const pgp of pg.entities) {
const volume = pgp.volume ?? pg.volume;
if (volume) {
await this.volumeSetSinglePlayer(pgp.player, volume);
}
if (pg.unmuteWhenGrouped) {
await this.setVolumeMute(pgp.player, false, false);
}
await this.applyPredefinedGroupSettings(pgp.player, pg);
}
if (pg.media) {
await this.setSource(pg.entities[0].player, pg.media);
}
}
async applyPredefinedGroupSettings(player, pg) {
await this.hassService.setRelatedEntityValue(player, "bass", pg.bass);
await this.hassService.setRelatedEntityValue(player, "treble", pg.treble);
await this.hassService.setRelatedEntityValue(player, "loudness", pg.loudness);
await this.hassService.setRelatedEntityValue(player, "night_sound", pg.nightSound);
await this.hassService.setRelatedEntityValue(player, "speech_enhancement", pg.speechEnhancement);
await this.hassService.setRelatedEntityValue(player, "crossfade", pg.crossfade);
await this.hassService.setRelatedEntityValue(player, "touch_controls", pg.touchControls);
await this.hassService.setRelatedEntityValue(player, "status_light", pg.statusLight);
}
async stop(mediaPlayer) {
await this.hassService.callMediaService("media_stop", { entity_id: mediaPlayer.id });
}
async pause(mediaPlayer) {
await this.hassService.callMediaService("media_pause", { entity_id: mediaPlayer.id });
}
async prev(mediaPlayer) {
await this.hassService.callMediaService("media_previous_track", {
entity_id: mediaPlayer.id
});
}
async next(mediaPlayer) {
await this.hassService.callMediaService("media_next_track", { entity_id: mediaPlayer.id });
}
async play(mediaPlayer) {
await this.hassService.callMediaService("media_play", { entity_id: mediaPlayer.id });
}
async shuffle(mediaPlayer) {
await this.hassService.callMediaService("shuffle_set", {
entity_id: mediaPlayer.id,
shuffle: !mediaPlayer.attributes.shuffle
});
}
async repeat(mediaPlayer) {
const currentState = mediaPlayer.attributes.repeat;
const repeat = currentState === "all" ? "one" : currentState === "one" ? "off" : "all";
await this.hassService.callMediaService("repeat_set", { entity_id: mediaPlayer.id, repeat });
}
async volumeDown(mainPlayer, updateMembers = true) {
await this.volumeStep(mainPlayer, updateMembers, this.getStepDownVolume, "volume_down");
}
async volumeUp(mainPlayer, updateMembers = true) {
await this.volumeStep(mainPlayer, updateMembers, this.getStepUpVolume, "volume_up");
}
async volumeStep(mainPlayer, updateMembers, calculateVolume, stepDirection) {
if (this.config.volumeStepSize) {
await this.volumeWithStepSize(mainPlayer, updateMembers, this.config.volumeStepSize, calculateVolume);
} else {
await this.volumeDefaultStep(mainPlayer, updateMembers, stepDirection);
}
}
async volumeWithStepSize(mainPlayer, updateMembers, volumeStepSize, calculateVolume) {
for (const member of mainPlayer.members) {
if (mainPlayer.id === member.id || updateMembers) {
const newVolume = calculateVolume(member, volumeStepSize);
await this.volumeSetSinglePlayer(member, newVolume);
}
}
}
getStepDownVolume(member, volumeStepSize) {
return Math.max(0, member.getVolume() - volumeStepSize);
}
getStepUpVolume(member, stepSize) {
return Math.min(100, member.getVolume() + stepSize);
}
async volumeDefaultStep(mainPlayer, updateMembers, stepDirection) {
for (const member of mainPlayer.members) {
if (mainPlayer.id === member.id || updateMembers) {
if (!member.ignoreVolume) {
await this.hassService.callMediaService(stepDirection, { entity_id: member.id });
}
}
}
}
async volumeSet(player, volume, updateMembers) {
if (updateMembers) {
return await this.volumeSetGroup(player, volume);
} else {
return await this.volumeSetSinglePlayer(player, volume);
}
}
async volumeSetGroup(player, volumePercent) {
const allZero = player.members.every((member) => member.getVolume() === 0);
if (allZero) {
await Promise.all(
player.members.map((member) => {
return this.volumeSetSinglePlayer(member, volumePercent);
})
);
} else {
let relativeVolumeChange;
if (this.config.adjustVolumeRelativeToMainPlayer) {
relativeVolumeChange = player.getVolume() < 1 ? 1 : volumePercent / player.getVolume();
}
await Promise.all(
player.members.map((member) => {
let memberVolume = volumePercent;
if (relativeVolumeChange !== void 0) {
if (this.config.adjustVolumeRelativeToMainPlayer) {
memberVolume = member.getVolume() * relativeVolumeChange;
memberVolume = Math.min(100, Math.max(0, memberVolume));
}
}
return this.volumeSetSinglePlayer(member, memberVolume);
})
);
}
}
async volumeSetSinglePlayer(player, volumePercent) {
if (!player.ignoreVolume) {
const volume = volumePercent / 100;
await this.hassService.callMediaService("volume_set", { entity_id: player.id, volume_level: volume });
}
}
async toggleMute(mediaPlayer, updateMembers = true) {
const isMuted = updateMembers ? mediaPlayer.isGroupMuted() : mediaPlayer.isMemberMuted();
const muteVolume = !isMuted;
await this.setVolumeMute(mediaPlayer, muteVolume, updateMembers);
}
async setVolumeMute(mediaPlayer, muteVolume, updateMembers = true) {
for (const member of mediaPlayer.members) {
if (mediaPlayer.id === member.id || updateMembers) {
await this.hassService.callMediaService("volume_mute", { entity_id: member.id, is_volume_muted: muteVolume });
}
}
}
async setSource(mediaPlayer, source) {
await this.hassService.callMediaService("select_source", { source, entity_id: mediaPlayer.id });
}
async playMedia(mediaPlayer, item, enqueue) {
const mediaContentId = enqueue ? this.transformMediaContentId(item.media_content_id ?? "") : item.media_content_id ?? "";
if (this.config.entityPlatform === "music_assistant") {
await this.hassService.callWithLoader(() => this.hassService.musicAssistantService.playMedia(mediaPlayer, mediaContentId, enqueue));
} else {
await this.hassService.callMediaService("play_media", {
entity_id: mediaPlayer.id,
media_content_id: mediaContentId,
media_content_type: item.media_content_type ?? "music",
...enqueue && { enqueue }
});
}
}
async playQueue(mediaPlayer, queuePosition, queueItemId) {
if (this.isMusicAssistant(mediaPlayer) && queueItemId) {
await this.hassService.callWithLoader(() => this.hassService.musicAssistantService.playQueueItem(mediaPlayer, queueItemId));
} else {
await this.hassService.callWithLoader(
() => this.hassService.callService("sonos", "play_queue", {
entity_id: mediaPlayer.id,
queue_position: queuePosition
})
);
}
}
async moveQueueItemAfterCurrent(mediaPlayer, item, index, currentIndex) {
if (this.isMusicAssistant(mediaPlayer)) {
if (index === currentIndex || index === currentIndex + 1) {
return;
}
if (item.queueItemId) {
await this.hassService.musicAssistantService.moveQueueItemNext(mediaPlayer, item.queueItemId);
}
} else {
await this.playMedia(mediaPlayer, item, "next");
const removeIndex = index > currentIndex ? index + 1 : index;
await this.hassService.removeFromQueue(mediaPlayer, removeIndex, item.queueItemId);
}
}
async moveQueueItemsAfterCurrent(mediaPlayer, items, indices, currentIndex, onProgress, shouldCancel) {
if (this.isMusicAssistant(mediaPlayer)) {
const filteredIndices = indices.filter((i5) => i5 !== currentIndex && i5 !== currentIndex + 1);
const reversedForInsert2 = [...filteredIndices].reverse();
let completed2 = 0;
for (const index of reversedForInsert2) {
if (shouldCancel?.()) {
return;
}
const item = items[index];
if (item?.queueItemId) {
await this.hassService.musicAssistantService.moveQueueItemNext(mediaPlayer, item.queueItemId);
}
completed2++;
onProgress?.(completed2);
}
return;
}
const reversedForInsert = [...indices].reverse();
let completed = 0;
for (const index of reversedForInsert) {
if (shouldCancel?.()) {
return;
}
const item = items[index];
if (item?.media_content_id) {
await this.playMedia(mediaPlayer, item, "next");
}
completed++;
onProgress?.(completed);
}
const numInserted = indices.length;
const reversedIndices = [...indices].reverse();
for (const originalIndex of reversedIndices) {
if (shouldCancel?.()) {
return;
}
const item = items[originalIndex];
const removeIndex = originalIndex > currentIndex ? originalIndex + numInserted : originalIndex;
await this.hassService.removeFromQueue(mediaPlayer, removeIndex, item?.queueItemId);
}
}
async moveQueueItemsToEnd(mediaPlayer, items, indices, onProgress, shouldCancel) {
let completed = 0;
for (const index of indices) {
if (shouldCancel?.()) {
return;
}
const item = items[index];
if (item?.media_content_id) {
await this.playMedia(mediaPlayer, item, "add");
}
completed++;
onProgress?.(completed);
}
const reversedIndices = [...indices].reverse();
for (const originalIndex of reversedIndices) {
if (shouldCancel?.()) {
return;
}
const item = items[originalIndex];
await this.hassService.removeFromQueue(mediaPlayer, originalIndex, item?.queueItemId);
}
}
async queueAndPlay(mediaPlayer, items, enqueueMode, onProgress, shouldCancel) {
if (items.length === 0) {
return;
}
const [firstItem, ...restItems] = items;
await this.playMedia(mediaPlayer, firstItem, enqueueMode);
onProgress?.(1);
for (let i5 = restItems.length - 1; i5 >= 0; i5--) {
if (shouldCancel?.()) {
return;
}
await this.playMedia(mediaPlayer, restItems[i5], "next");
onProgress?.(restItems.length - i5 + 1);
}
}
// Needed for playing queue items, example:
// x-mxmp-spotify:spotify%3atrack%3a6KfyfEiMAQJrMhRrP2Epm4?sid=12&flags=8232&sn=2
// to
// spotify:track:6KfyfEiMAQJrMhRrP2Epm4
transformMediaContentId(id) {
if (!id) {
return "";
}
try {
const withoutQuery = id.split("?")[0];
const decoded = decodeURIComponent(withoutQuery);
const colonMatches = decoded.match(/:/g);
if (colonMatches && colonMatches.length >= 2) {
const firstColonIndex = decoded.indexOf(":");
return decoded.substring(firstColonIndex + 1);
}
return decoded;
} catch {
return id;
}
}
async seek(mediaPlayer, position) {
await this.hassService.callMediaService("media_seek", {
entity_id: mediaPlayer.id,
seek_position: position
});
}
async togglePower(mediaPlayer) {
const service = mediaPlayer.isOn() ? "turn_off" : "turn_on";
await this.hassService.callMediaService(service, { entity_id: mediaPlayer.id });
}
}
class MediaPlayer {
constructor(hassEntity, config, mediaPlayerHassEntities) {
this.id = hassEntity.entity_id;
this.config = config;
this.name = this.getEntityName(hassEntity);
this.state = hassEntity.state;
this.attributes = hassEntity.attributes;
this.members = mediaPlayerHassEntities ? this.createGroupMembers(hassEntity, mediaPlayerHassEntities) : [this];
this.volumePlayer = this.determineVolumePlayer();
this.ignoreVolume = !!this.config.entitiesToIgnoreVolumeLevelFor?.includes(this.volumePlayer.id);
}
getMember(playerId) {
return findPlayer(this.members, playerId);
}
hasMember(playerId) {
return this.getMember(playerId) !== void 0;
}
isPlaying() {
return this.state === "playing";
}
isMemberMuted() {
return this.attributes.is_volume_muted;
}
isGroupMuted() {
if (this.config.inverseGroupMuteState) {
return this.members.some((member) => member.isMemberMuted());
}
return this.members.every((member) => member.isMemberMuted());
}
getCurrentTrack() {
let track = `${this.attributes.media_artist || ""} - ${this.attributes.media_title || ""}`;
track = track.replace(/^ - | - $/g, "");
if (!track) {
track = this.attributes.media_content_id?.replace(/.*:\/\//g, "") ?? "";
}
if (this.config.mediaTitleRegexToReplace) {
track = track.replace(new RegExp(this.config.mediaTitleRegexToReplace, "g"), this.config.mediaTitleReplacement || "");
}
return track;
}
getEntityName(hassEntity) {
const name = hassEntity.attributes.friendly_name || "";
if (this.config.entityNameRegexToReplace) {
return name.replace(new RegExp(this.config.entityNameRegexToReplace, "g"), this.config.entityNameReplacement || "");
}
return name;
}
createGroupMembers(mainHassEntity, mediaPlayerHassEntities) {
const groupPlayerIds = getGroupPlayerIds(mainHassEntity);
return mediaPlayerHassEntities.reduce((players, hassEntity) => {
if (groupPlayerIds.includes(hassEntity.entity_id)) {
return [...players, new MediaPlayer(hassEntity, this.config)];
}
return players;
}, []);
}
determineVolumePlayer() {
let find;
if (this.members.length > 1 && this.config.entitiesToIgnoreVolumeLevelFor) {
find = this.members.find((p2) => {
return !this.config.entitiesToIgnoreVolumeLevelFor?.includes(p2.id);
});
}
return find ?? this;
}
getVolume() {
let volume;
if (this.members.length > 1 && this.config.adjustVolumeRelativeToMainPlayer) {
volume = this.getAverageVolume();
} else {
volume = 100 * (this.volumePlayer.attributes.volume_level || 0);
}
return Math.round(volume);
}
getAverageVolume() {
const volumes = this.members.filter((m2) => !this.config.entitiesToIgnoreVolumeLevelFor?.includes(m2.id)).map((m2) => m2.attributes.volume_level || 0);
return 100 * volumes.reduce((a2, b2) => a2 + b2, 0) / volumes.length;
}
isOn() {
return this.state !== "off" && this.state !== "unavailable";
}
}
class Store {
getJoinedPlayerIds() {
return this.allMediaPlayers.map((p2) => p2.id).filter((id) => id === this.activePlayer.id || this.activePlayer.hasMember(id));
}
getJoinedAndNotJoinedCounts() {
const joinedCount = this.getJoinedPlayerIds().length;
return { joinedCount, notJoinedCount: this.allMediaPlayers.length - joinedCount };
}
constructor(hass, config, currentSection, card, activePlayerId) {
this.hass = hass;
this.config = config;
const mediaPlayerHassEntities = this.getMediaPlayerHassEntities(this.hass);
this.allGroups = this.createPlayerGroups(mediaPlayerHassEntities);
this.allMediaPlayers = this.allGroups.reduce((previousValue, currentValue) => [...previousValue, ...currentValue.members], []);
this.activePlayer = this.determineActivePlayer(activePlayerId);
this.hassService = new HassService(this.hass, currentSection, card);
this.mediaControlService = new MediaControlService(this.hassService, config);
this.mediaBrowseService = new MediaBrowseService(this.hass, config);
this.predefinedGroups = this.createPredefinedGroups();
}
createPredefinedGroups() {
const result = [];
if (this.config.predefinedGroups) {
for (const cpg of this.config.predefinedGroups) {
const pg = this.createPredefinedGroup(cpg);
if (pg) {
result.push(pg);
}
}
}
return result;
}
createPredefinedGroup(configItem) {
let result = void 0;
const entities = [];
let configEntities = configItem.entities;
if (configItem.excludeItemsInEntitiesList) {
configEntities = this.convertExclusionsInPredefinedGroupsToInclusions(configEntities);
}
for (const item of configEntities) {
const predefinedGroupPlayer = this.createPredefinedGroupPlayer(item);
if (predefinedGroupPlayer) {
entities.push(predefinedGroupPlayer);
}
}
if (entities.length) {
result = {
...configItem,
entities
};
}
return result;
}
convertExclusionsInPredefinedGroupsToInclusions(configEntities) {
return this.allMediaPlayers.filter(
(mp) => !configEntities.find((player) => {
return (typeof player === "string" ? player : player.player) === mp.id;
})
).map((mp) => mp.id);
}
createPredefinedGroupPlayer(configItem) {
let pgEntityId;
let volume;
if (typeof configItem === "string") {
pgEntityId = configItem;
} else {
volume = configItem.volume;
pgEntityId = configItem.player;
}
let result = void 0;
if (this.hass.states[pgEntityId]?.state !== "unavailable") {
const player = this.allMediaPlayers.find((p2) => p2.id === pgEntityId);
if (player) {
result = { player, volume };
}
} else {
console.warn(`Player ${pgEntityId} is unavailable`);
}
return result;
}
getMediaPlayerHassEntities(hass) {
const hassWithEntities = hass;
const filtered = Object.values(hass.states).filter((hassEntity) => {
if (hassEntity.entity_id.includes("media_player")) {
if (this.config.allowPlayerVolumeEntityOutsideOfGroup && hassEntity.entity_id === this.config.player?.volumeEntityId) {
return true;
}
if (isSonosCard(this.config)) {
return entityMatchSonos(this.config, hassEntity, hassWithEntities);
} else {
return entityMatchMxmp(this.config, hassEntity, hassWithEntities);
}
}
return false;
});
return sortEntities(this.config, filtered);
}
createPlayerGroups(mediaPlayerHassEntities) {
return mediaPlayerHassEntities.filter((hassEntity) => this.isMainPlayer(hassEntity, mediaPlayerHassEntities)).map((hassEntity) => this.createPlayerGroup(hassEntity, mediaPlayerHassEntities)).filter((grp) => grp !== void 0);
}
isMainPlayer(hassEntity, mediaPlayerHassEntities) {
try {
const groupIds = getGroupPlayerIds(hassEntity).filter((playerId) => mediaPlayerHassEntities.some((value) => value.entity_id === playerId));
const isGrouped = groupIds?.length > 1;
const isMainInGroup = isGrouped && groupIds && groupIds[0] === hassEntity.entity_id;
const available = this.hass.states[hassEntity.entity_id]?.state !== "unavailable";
return (!isGrouped || isMainInGroup) && available;
} catch (e2) {
console.error("Failed to determine main player", JSON.stringify(hassEntity), e2);
return false;
}
}
createPlayerGroup(hassEntity, mediaPlayerHassEntities) {
try {
return new MediaPlayer(hassEntity, this.config, mediaPlayerHassEntities);
} catch (e2) {
console.error("Failed to create group", JSON.stringify(hassEntity), e2);
return void 0;
}
}
determineActivePlayer(activePlayerId) {
const playerId = activePlayerId || this.getActivePlayer() || this.config.entityId;
return this.allGroups.find((group) => group.id === playerId) || this.allGroups.find((group) => group.getMember(playerId) !== void 0) || this.allGroups.find((group) => group.isPlaying()) || this.allGroups[0];
}
getActivePlayer() {
if (this.config.doNotRememberSelectedPlayer) {
return "";
}
if (this.config.storePlayerInSessionStorage) {
return this.getActivePlayerFromStorage();
}
return this.getActivePlayerFromUrl();
}
getActivePlayerFromUrl() {
return window.location.href.includes("#") ? window.location.href.replace(/.*#/g, "") : "";
}
getActivePlayerFromStorage() {
return window.sessionStorage.getItem(SESSION_STORAGE_PLAYER_ID) || "";
}
hidePower(hideIfOn = false) {
if (this.config.player?.hideControlPowerButton) {
return true;
} else if (!supportsTurnOn(this.activePlayer)) {
return true;
} else if (hideIfOn && this.activePlayer.isOn()) {
return true;
} else {
return E;
}
}
}
var __defProp$K = Object.defineProperty;
var __decorateClass$K = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$K(target, key, result);
return result;
};
class SectionButton extends i$5 {
render() {
const size = this.config.sectionButtonIconSize;
const styles = size ? {
"--icon-button-size": `${size}rem`,
"--icon-size": `${size * 0.6}rem`
} : {};
return x` this.dispatchSection()}
selected=${this.selectedSection === this.section || E}
style=${o(styles)}
>
`;
}
dispatchSection() {
this.dispatchEvent(customEvent(SHOW_SECTION, this.section));
}
static get styles() {
return i$8`
:host > *[selected] {
color: var(--accent-color);
}
`;
}
}
__decorateClass$K([
n$4({ attribute: false })
], SectionButton.prototype, "config");
__decorateClass$K([
n$4()
], SectionButton.prototype, "icon");
__decorateClass$K([
n$4()
], SectionButton.prototype, "section");
__decorateClass$K([
n$4()
], SectionButton.prototype, "selectedSection");
customElements.define("mxmp-section-button", SectionButton);
var __defProp$J = Object.defineProperty;
var __decorateClass$J = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$J(target, key, result);
return result;
};
const { GROUPING: GROUPING$1, GROUPS: GROUPS$1, MEDIA_BROWSER: MEDIA_BROWSER$1, PLAYER: PLAYER$1, VOLUMES: VOLUMES$1, QUEUE: QUEUE$1, SEARCH: SEARCH$1 } = Section;
class Footer extends i$5 {
render() {
const icons = this.config.sectionButtonIcons;
let sections = [
[PLAYER$1, icons?.player ?? "mdi:home"],
[MEDIA_BROWSER$1, icons?.mediaBrowser ?? "mdi:star-outline"],
[GROUPS$1, icons?.groups ?? "mdi:speaker-multiple"],
[GROUPING$1, icons?.grouping ?? "mdi:checkbox-multiple-marked-circle-outline"],
[QUEUE$1, icons?.queue ?? "mdi:queue-first-in-last-out"],
[SEARCH$1, icons?.search ?? "mdi:magnify"],
[VOLUMES$1, icons?.volumes ?? "mdi:tune"]
];
if (!isQueueSupported(this.config)) {
sections = sections.filter(([section]) => section !== QUEUE$1);
}
sections = sections.filter(([section]) => !this.config.sections || this.config.sections?.includes(section));
return x`
${sections.map(
([section, icon]) => x`
`
)}
`;
}
static get styles() {
return i$8`
:host {
display: flex;
justify-content: space-between;
}
:host > * {
align-content: center;
}
`;
}
}
__decorateClass$J([
n$4({ attribute: false })
], Footer.prototype, "config");
__decorateClass$J([
n$4()
], Footer.prototype, "section");
customElements.define("mxmp-footer", Footer);
var NumberFormat;
(function(NumberFormat2) {
NumberFormat2["language"] = "language";
NumberFormat2["system"] = "system";
NumberFormat2["comma_decimal"] = "comma_decimal";
NumberFormat2["decimal_comma"] = "decimal_comma";
NumberFormat2["space_comma"] = "space_comma";
NumberFormat2["none"] = "none";
})(NumberFormat || (NumberFormat = {}));
var TimeFormat;
(function(TimeFormat2) {
TimeFormat2["language"] = "language";
TimeFormat2["system"] = "system";
TimeFormat2["am_pm"] = "12";
TimeFormat2["twenty_four"] = "24";
})(TimeFormat || (TimeFormat = {}));
const fireEvent = (node, type, detail, options2) => {
options2 = options2 || {};
detail = detail === null || detail === void 0 ? {} : detail;
const event = new Event(type, {
bubbles: options2.bubbles === void 0 ? true : options2.bubbles,
cancelable: Boolean(options2.cancelable),
composed: options2.composed === void 0 ? true : options2.composed
});
event.detail = detail;
node.dispatchEvent(event);
return event;
};
const debounce = (func, wait, immediate = false) => {
let timeout;
return function(...args) {
const context = this;
const later = () => {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
};
var __defProp$I = Object.defineProperty;
var __decorateClass$I = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$I(target, key, result);
return result;
};
class BaseEditor extends i$5 {
setConfig(config) {
this.config = JSON.parse(JSON.stringify(config));
}
static get styles() {
return i$8`
ha-svg-icon {
margin: 5px;
}
ha-control-button {
white-space: nowrap;
}
ha-control-button-group {
margin: 5px;
}
div {
margin-top: 20px;
}
`;
}
configChanged() {
fireEvent(this, "config-changed", { config: this.config });
this.requestUpdate();
}
dispatchClose() {
return this.dispatchEvent(new CustomEvent("closed"));
}
}
__decorateClass$I([
n$4({ attribute: false })
], BaseEditor.prototype, "config");
__decorateClass$I([
n$4({ attribute: false })
], BaseEditor.prototype, "hass");
const GROUPS_SCHEMA = [
{
name: "title",
type: "string"
},
{
name: "backgroundColor",
type: "string",
help: "Background color for group buttons"
},
{
name: "buttonWidth",
type: "integer",
help: "Width of group buttons (rem)",
valueMin: 1
},
{
name: "compact",
selector: { boolean: {} }
},
{
name: "hideCurrentTrack",
selector: { boolean: {} }
},
{
name: "itemMargin",
type: "string",
help: "Margin around groups list items (e.g., 5px, 0.5rem)"
},
{
name: "speakersFontSize",
type: "float",
help: "Font size for speakers name (rem)",
valueMin: 0.1
},
{
name: "titleFontSize",
type: "float",
help: "Font size for track title (rem)",
valueMin: 0.1
}
];
const GROUPING_SCHEMA = [
{
name: "title",
type: "string"
},
{
name: "buttonColor",
type: "string",
help: "Background color for grouping buttons"
},
{
name: "buttonFontSize",
type: "float",
help: "Font size for grouping buttons (rem)",
valueMin: 0.1
},
{
name: "compact",
selector: { boolean: {} }
},
{
name: "disableMainSpeakers",
selector: { boolean: {} }
},
{
name: "dontSortMembersOnTop",
selector: { boolean: {} }
},
{
name: "dontSwitchPlayer",
selector: { boolean: {} }
},
{
name: "hideUngroupAllButtons",
selector: { boolean: {} }
},
{
name: "hideVolumes",
selector: { boolean: {} }
},
{
name: "skipApplyButton",
selector: { boolean: {} }
}
];
const VOLUMES_SCHEMA = [
{
name: "title",
type: "string"
},
{
name: "additionalControlsFontSize",
selector: { number: {} }
},
{
name: "hideCogwheel",
selector: { boolean: {} }
},
{
name: "labelForAllSlider",
type: "string"
}
];
const QUEUE_SCHEMA = [
{
name: "title",
type: "string"
},
{
name: "itemBackgroundColor",
type: "string"
},
{
name: "itemTextColor",
type: "string"
},
{
name: "selectedItemBackgroundColor",
type: "string"
},
{
name: "selectedItemTextColor",
type: "string"
}
];
const mediaTypeOptions = {
none: "None",
track: "Track",
artist: "Artist",
album: "Album",
playlist: "Playlist",
radio: "Radio"
};
const viewModeOptions = {
list: "List",
grid: "Grid"
};
const SEARCH_SCHEMA = [
{
name: "title",
type: "string",
help: "Custom title for the search section"
},
{
name: "massConfigEntryId",
type: "string",
help: "Leave empty to auto-discover"
},
{
type: "select",
options: Object.entries(mediaTypeOptions).map((entry) => entry),
name: "defaultMediaType"
},
{
type: "select",
options: Object.entries(viewModeOptions).map((entry) => entry),
name: "defaultViewMode",
help: "Default view mode (default: list)"
},
{
name: "gridColumns",
type: "integer",
help: "Number of columns in grid view (default: 4)"
},
{
name: "searchLimit",
type: "integer",
help: "Max results per search (default: 50)"
},
{
name: "autoSearchMinChars",
type: "integer",
help: "Min characters to trigger auto-search (default: 2)"
},
{
name: "autoSearchDebounceMs",
type: "integer",
help: "Debounce delay in ms (default: 1000)"
}
];
const options = {
player: "Player",
"media browser": "Media Browser",
groups: "Groups",
grouping: "Grouping",
volumes: "Volumes",
queue: "Queue",
search: "Search"
};
const COMMON_SCHEMA = [
{
type: "multi_select",
options,
name: "sections"
},
{
type: "select",
options: Object.entries(options).map((entry) => entry),
name: "startSection"
},
{
type: "string",
name: "title"
},
{
name: "adjustVolumeRelativeToMainPlayer",
selector: { boolean: {} }
},
{
name: "allowPlayerVolumeEntityOutsideOfGroup",
selector: { boolean: {} }
},
{
name: "baseFontSize",
type: "float",
help: "Base font size for the entire card (rem)",
valueMin: 0.1
},
{
name: "changeVolumeOnSlide",
selector: { boolean: {} }
},
{
name: "doNotRememberSelectedPlayer",
selector: { boolean: {} }
},
{
name: "dynamicVolumeSlider",
selector: { boolean: {} }
},
{
name: "dynamicVolumeSliderMax",
type: "integer",
default: 30,
required: true,
valueMin: 1,
valueMax: 100
},
{
name: "dynamicVolumeSliderThreshold",
type: "integer",
default: 20,
required: true,
valueMin: 1,
valueMax: 100
},
{
name: "entitiesToIgnoreVolumeLevelFor",
help: "If you want to ignore volume level for certain players in the player section",
selector: { entity: { multiple: true, filter: { domain: "media_player" } } }
},
{
name: "fontFamily",
type: "string",
help: "Font family for the entire card (e.g., Arial, Roboto)"
},
{
name: "footerHeight",
type: "integer",
valueMin: 0
},
{
name: "heightPercentage",
type: "integer",
default: 100,
required: true
},
{
name: "inverseGroupMuteState",
selector: { boolean: {} }
},
{
name: "mediaTitleRegexToReplace",
type: "string"
},
{
name: "mediaTitleReplacement",
type: "string"
},
{
name: "minWidth",
type: "integer",
help: "Minimum width of the card (rem)",
valueMin: 1
},
{
name: "sectionButtonIconSize",
type: "float",
help: "Size of section button icons (rem)",
valueMin: 0.1
},
{
name: "storePlayerInSessionStorage",
selector: { boolean: {} }
},
{
name: "volumeStepSize",
type: "integer",
valueMin: 1
},
{
name: "widthPercentage",
type: "integer",
default: 100,
required: true
}
];
const ENTITIES_SCHEMA = [
{
name: "entityId",
help: "Not needed, but forces this player to be the selected one on loading the card (overrides url param etc)",
selector: { entity: { multiple: false, filter: { domain: "media_player" } } }
},
{
name: "entities",
help: "Required, unless you have specified entity platform",
cardType: "maxi",
selector: { entity: { multiple: true, filter: { domain: "media_player" } } }
},
{
name: "entities",
help: "Not needed, unless you don't want to include all of them",
cardType: "sonos",
selector: { entity: { multiple: true, filter: { domain: "media_player" } } }
},
{
name: "useMusicAssistant",
selector: { boolean: {} }
},
{
name: "showNonSonosPlayers",
help: "Show all media players, including those that are not on the Sonos platform",
cardType: "sonos",
selector: { boolean: {} }
},
{
name: "entityPlatform",
help: "Show all media players for the selected platform",
selector: { text: {} }
},
{
name: "entityNameRegexToReplace",
type: "string"
},
{
name: "entityNameReplacement",
type: "string"
},
{
name: "excludeItemsInEntitiesList",
selector: { boolean: {} }
}
];
const PREDEFINED_GROUP_SCHEMA = [
{ type: "string", name: "name", required: true },
{ type: "string", name: "media" },
{ type: "boolean", name: "excludeItemsInEntitiesList" },
{
name: "entities",
selector: { entity: { multiple: true, filter: { domain: "media_player" } } }
}
];
var __defProp$H = Object.defineProperty;
var __decorateClass$H = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$H(target, key, result);
return result;
};
class PredefinedGroupEditor extends BaseEditor {
constructor() {
super(...arguments);
this.groupChanged = (ev) => {
const changed = ev.detail.value;
const entities = changed.entities.map((id) => {
const existing = this.predefinedGroup?.entities.find(({ player }) => player === id);
return existing ?? { player: id };
});
this.predefinedGroup = { ...changed, entities };
};
this.volumeChanged = (ev, playerId) => {
if (!this.predefinedGroup) {
return;
}
const volume = ev.detail.value.volume;
const entities = this.predefinedGroup.entities.map((e2) => e2.player === playerId ? { ...e2, volume } : e2);
this.predefinedGroup = { ...this.predefinedGroup, entities };
};
this.save = () => {
let groups = this.config.predefinedGroups ?? [];
if (groups[this.index]) {
groups[this.index] = this.predefinedGroup;
} else {
groups = [...groups, this.predefinedGroup];
}
this.config = { ...this.config, predefinedGroups: groups };
this.configChanged();
this.dispatchClose();
};
this.delete = () => {
const groups = this.config.predefinedGroups?.filter((_2, i5) => i5 !== this.index);
this.config = { ...this.config, predefinedGroups: groups };
this.configChanged();
this.dispatchClose();
};
}
render() {
if (!this.predefinedGroup) {
this.initPredefinedGroup();
}
if (!this.predefinedGroup) {
return x``;
}
const pgWithoutVolumes = {
...this.predefinedGroup,
entities: this.predefinedGroup.entities.map((e2) => e2.player)
};
return x`
Add/Edit Predefined Group
Volumes - will be set when players are grouped
${this.predefinedGroup.entities.map(({ player, volume }) => this.renderVolumeField(player, volume))}
OK
Delete
`;
}
initPredefinedGroup() {
const configPg = this.config.predefinedGroups?.[this.index];
if (configPg) {
const entities = configPg.entities.map((e2) => typeof e2 === "string" ? { player: e2 } : e2);
this.predefinedGroup = { ...configPg, entities };
} else {
this.predefinedGroup = { name: "", media: "", entities: [] };
}
}
renderVolumeField(player, volume) {
const label = `${this.hass.states[player]?.attributes.friendly_name ?? player}${volume !== void 0 ? `: ${volume}` : ""}`;
const schema = [{ type: "integer", name: "volume", label, valueMin: 0, valueMax: 100 }];
return x`
this.volumeChanged(ev, player)}
>
`;
}
}
__decorateClass$H([
n$4({ type: Number })
], PredefinedGroupEditor.prototype, "index");
__decorateClass$H([
r$3()
], PredefinedGroupEditor.prototype, "predefinedGroup");
customElements.define("mxmp-predefined-group-editor", PredefinedGroupEditor);
var __defProp$G = Object.defineProperty;
var __decorateClass$G = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$G(target, key, result);
return result;
};
class CommonTab extends BaseEditor {
constructor() {
super(...arguments);
this.editPredefinedGroup = -1;
this.entitiesChanged = (ev) => {
const { useMusicAssistant, ...formData } = ev.detail.value;
const prevUseMusicAssistant = this.config.entityPlatform === "music_assistant";
const newUseMusicAssistant = !!useMusicAssistant;
if (newUseMusicAssistant !== prevUseMusicAssistant) {
if (newUseMusicAssistant) {
this.config = { ...this.config, entityPlatform: "music_assistant" };
} else {
this.config = { ...this.config, entityPlatform: void 0 };
}
} else {
this.config = { ...this.config, ...formData };
}
this.configChanged();
};
this.simpleChanged = (ev) => {
this.config = { ...this.config, ...ev.detail.value };
this.configChanged();
};
}
render() {
if (this.editPredefinedGroup > -1) {
return x`
this.editPredefinedGroup = -1}
>
`;
}
return x`
Entities
${this.renderEntitiesForm()}
Predefined Groups
${this.renderPredefinedGroupsList()}
Other
${this.renderForm(COMMON_SCHEMA)}
`;
}
renderEntitiesForm() {
const useMusicAssistant = this.config.entityPlatform === "music_assistant";
const data = { ...this.config, useMusicAssistant };
return x`
`;
}
renderForm(schema) {
return x`
`;
}
renderPredefinedGroupsList() {
const groups = this.config.predefinedGroups;
return x`
${groups?.map(
(pg, index) => x`
this.editPredefinedGroup = index}>
${pg.name}
`
)}
this.editPredefinedGroup = groups?.length ?? 0}>
Add
`;
}
}
__decorateClass$G([
r$3()
], CommonTab.prototype, "editPredefinedGroup");
customElements.define("mxmp-common-tab", CommonTab);
const PLAYER_SCHEMA = [
{
name: "artworkAsBackground",
selector: { boolean: {} }
},
{
name: "artworkAsBackgroundBlur",
selector: { number: { min: 0, max: 100, step: 1 } }
},
{
name: "artworkBorderRadius",
selector: { number: { min: 0, max: 100, step: 1 } },
help: "Border radius in pixels for player artwork"
},
{
name: "artworkHostname",
type: "string"
},
{
name: "artworkMinHeight",
type: "integer",
help: "Minimum height of the artwork in rem",
default: 5,
required: true,
valueMin: 0
},
{
name: "backgroundOverlayColor",
type: "string",
help: "Background overlay color when artworkAsBackground is true (e.g., rgba(0,0,0, 0.3))"
},
{
name: "controlsAndHeaderBackgroundOpacity",
selector: { number: { min: 0, max: 1, step: 0.1 } }
},
{
name: "controlsColor",
type: "string",
help: "Color for player control icons (e.g., pink, #ff69b4)"
},
{
name: "controlsLargeIcons",
selector: { boolean: {} }
},
{
name: "controlsMargin",
type: "string",
help: "Margin around player controls (e.g., 0 3rem)"
},
{
name: "fallbackArtwork",
type: "string",
help: "Override default fallback artwork image if artwork is missing for the currently selected media"
},
{
name: "fastForwardAndRewindStepSizeSeconds",
type: "integer",
default: 15,
valueMin: 1
},
{
name: "headerEntityFontSize",
type: "float",
help: "Font size for entity name in player header (rem)",
valueMin: 0.1
},
{
name: "headerSongFontSize",
type: "float",
help: "Font size for song title in player header (rem)",
valueMin: 0.1
},
{
name: "hideArtistAlbum",
selector: { boolean: {} }
},
{
name: "hideArtwork",
selector: { boolean: {} }
},
{
name: "hideControls",
selector: { boolean: {} }
},
{
name: "hideControlNextTrackButton",
selector: { boolean: {} }
},
{
name: "hideControlPowerButton",
selector: { boolean: {} }
},
{
name: "hideControlPrevTrackButton",
selector: { boolean: {} }
},
{
name: "hideControlRepeatButton",
selector: { boolean: {} }
},
{
name: "hideControlShuffleButton",
selector: { boolean: {} }
},
{
name: "hideEntityName",
selector: { boolean: {} }
},
{
name: "hideHeader",
selector: { boolean: {} }
},
{
name: "hidePlaylist",
selector: { boolean: {} }
},
{
name: "hideVolume",
selector: { boolean: {} }
},
{
name: "hideVolumeMuteButton",
selector: { boolean: {} }
},
{
name: "hideVolumePercentage",
selector: { boolean: {} }
},
{
name: "labelWhenNoMediaIsSelected",
type: "string"
},
{
name: "showAudioInputFormat",
selector: { boolean: {} }
},
{
name: "showBrowseMediaButton",
selector: { boolean: {} }
},
{
name: "showChannel",
selector: { boolean: {} }
},
{
name: "showFastForwardAndRewindButtons",
selector: { boolean: {} }
},
{
name: "showSource",
selector: { boolean: {} }
},
{
name: "showVolumeUpAndDownButtons",
selector: { boolean: {} }
},
{
name: "stopInsteadOfPause",
selector: { boolean: {} }
},
{
name: "volumeEntityId",
selector: { entity: { multiple: false, filter: { domain: "media_player" } } }
},
{
name: "volumeMuteButtonSize",
type: "float",
help: "Size of mute button in player (rem)",
valueMin: 0.1
},
{
name: "volumeSliderHeight",
type: "float",
help: "Height of volume slider in player (rem)",
valueMin: 0.1
}
];
var __defProp$F = Object.defineProperty;
var __decorateClass$F = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$F(target, key, result);
return result;
};
const ARTWORK_OVERRIDE_SCHEMA = [
{ name: "ifMissing", selector: { boolean: {} } },
{ name: "mediaTitleEquals", type: "string" },
{ name: "mediaArtistEquals", type: "string" },
{ name: "mediaAlbumNameEquals", type: "string" },
{ name: "mediaContentIdEquals", type: "string" },
{ name: "mediaChannelEquals", type: "string" },
{ name: "appNameEquals", type: "string" },
{ name: "sourceEquals", type: "string" },
{ name: "appIdEquals", type: "string" },
{ name: "mediaTitleRegexp", type: "string" },
{ name: "mediaArtistRegexp", type: "string" },
{ name: "mediaAlbumNameRegexp", type: "string" },
{ name: "mediaContentIdRegexp", type: "string" },
{ name: "mediaChannelRegexp", type: "string" },
{ name: "appNameRegexp", type: "string" },
{ name: "sourceRegexp", type: "string" },
{ name: "appIdRegexp", type: "string" },
{ name: "imageUrl", type: "string" },
{ type: "integer", name: "sizePercentage", default: 100, required: true, valueMin: 1, valueMax: 100 }
];
class ArtworkOverrideEditor extends BaseEditor {
constructor() {
super(...arguments);
this.changed = (ev) => {
const changed = ev.detail.value;
const player = this.config.player ?? {};
let overrides = player.mediaArtworkOverrides ?? [];
if (overrides[this.index]) {
overrides[this.index] = changed;
} else {
overrides = [...overrides, changed];
}
this.config = { ...this.config, player: { ...player, mediaArtworkOverrides: overrides } };
this.configChanged();
};
this.delete = () => {
const player = this.config.player ?? {};
const overrides = player.mediaArtworkOverrides?.filter((_2, i5) => i5 !== this.index);
this.config = { ...this.config, player: { ...player, mediaArtworkOverrides: overrides } };
this.configChanged();
this.dispatchClose();
};
}
render() {
const playerConfig = this.config.player ?? {};
const override = playerConfig.mediaArtworkOverrides?.[this.index] ?? { ifMissing: false };
const isExisting = !!playerConfig.mediaArtworkOverrides?.[this.index];
return x`
Add/Edit Artwork Override
OK
${isExisting ? x`Delete` : E}
`;
}
}
__decorateClass$F([
n$4({ type: Number })
], ArtworkOverrideEditor.prototype, "index");
customElements.define("mxmp-artwork-override-editor", ArtworkOverrideEditor);
var __defProp$E = Object.defineProperty;
var __decorateClass$E = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$E(target, key, result);
return result;
};
class PlayerTab extends BaseEditor {
constructor() {
super(...arguments);
this.editArtworkOverride = -1;
this.sectionChanged = (ev) => {
const changed = ev.detail.value;
this.config = { ...this.config, player: { ...this.config.player ?? {}, ...changed } };
this.configChanged();
};
}
render() {
if (this.editArtworkOverride > -1) {
return x`
this.editArtworkOverride = -1}
>
`;
}
return x`
Artwork Overrides
${this.renderArtworkOverridesList()}
`;
}
renderArtworkOverridesList() {
const items = this.config.player?.mediaArtworkOverrides;
return x`
${items?.map(
(item, index) => x`
this.editArtworkOverride = index}>
${this.getOverrideName(item, index)}
`
)}
this.editArtworkOverride = items?.length ?? 0}>
Add
`;
}
getOverrideName(item, index) {
return item.mediaTitleEquals || item.mediaArtistEquals || item.mediaAlbumNameEquals || item.mediaContentIdEquals || item.mediaChannelEquals || item.appNameEquals || item.sourceEquals || item.appIdEquals || item.ifMissing && "if missing" || index;
}
}
__decorateClass$E([
r$3()
], PlayerTab.prototype, "editArtworkOverride");
customElements.define("mxmp-player-tab", PlayerTab);
const MEDIA_BROWSER_SCHEMA = [
{
name: "hideHeader",
selector: { boolean: {} }
},
{
name: "itemsPerRow",
type: "integer",
valueMin: 1
},
{
name: "onlyFavorites",
selector: { boolean: {} }
}
];
const SHORTCUT_SUB_SCHEMA = [
{
name: "media_content_id",
type: "string",
help: "The content ID of the folder (use browser DevTools to find this)"
},
{
name: "media_content_type",
type: "string",
help: "The content type (e.g., spotify://library)"
},
{
name: "icon",
type: "string",
help: "Icon for the button (e.g., mdi:spotify). Default is bookmark icon."
},
{
name: "name",
type: "string",
help: "Tooltip/name for the shortcut button"
}
];
const FAVORITES_SUB_SCHEMA = [
{
name: "title",
type: "string"
},
{
name: "exclude",
type: "string"
},
{
name: "hideTitleForThumbnailIcons",
selector: { boolean: {} }
},
{
name: "iconBorder",
type: "string",
help: "Border for favorites icons (e.g., 1px solid white)"
},
{
name: "iconPadding",
type: "float",
help: "Padding around favorites icon artwork (rem)",
valueMin: 0
},
{
name: "iconTitleBackgroundColor",
type: "string",
help: "Background color for favorites icon titles"
},
{
name: "iconTitleColor",
type: "string",
help: "Color for favorites icon titles (e.g., red, #ff0000)"
},
{
name: "numberToShow",
type: "integer",
valueMin: 1
},
{
name: "sortByType",
selector: { boolean: {} }
},
{
name: "typeColor",
type: "string",
help: "Color for type headers when sortByType is enabled"
},
{
name: "typeFontSize",
type: "string",
help: "Font size for type headers (e.g., 18px)"
},
{
name: "typeFontWeight",
type: "string",
help: "Font weight for type headers (e.g., normal, bold)"
},
{
name: "typeMarginBottom",
type: "string",
help: "Bottom margin for type headers (e.g., 6px)"
},
{
name: "topItems",
type: "string"
}
];
class MediaBrowserTab extends BaseEditor {
constructor() {
super(...arguments);
this.mediaBrowserChanged = (ev) => {
const changed = ev.detail.value;
this.config = {
...this.config,
mediaBrowser: {
...this.config.mediaBrowser ?? {},
...changed
}
};
this.configChanged();
};
this.shortcutChanged = (ev) => {
const changed = ev.detail.value;
const mediaBrowser = this.config.mediaBrowser ?? {};
const shortcut = Object.fromEntries(
Object.entries({
...mediaBrowser.shortcut ?? {},
...changed
}).filter(([, v2]) => v2 !== "" && v2 !== void 0)
);
this.config = {
...this.config,
mediaBrowser: {
...mediaBrowser,
shortcut: Object.keys(shortcut).length > 0 ? shortcut : void 0
}
};
this.configChanged();
};
this.favoritesChanged = (ev) => {
const changed = ev.detail.value;
const mediaBrowser = this.config.mediaBrowser ?? {};
this.config = {
...this.config,
mediaBrowser: {
...mediaBrowser,
favorites: {
...mediaBrowser.favorites ?? {},
...changed,
exclude: changed.exclude?.split(/ *, */).filter(Boolean) ?? [],
topItems: changed.topItems?.split(/ *, */).filter(Boolean) ?? []
}
}
};
this.configChanged();
};
}
render() {
const mediaBrowserConfig = this.config.mediaBrowser ?? {};
const favoritesConfig = mediaBrowserConfig.favorites ?? {};
const shortcutConfig = mediaBrowserConfig.shortcut ?? {};
const exclude = favoritesConfig.exclude ?? [];
const topItems = favoritesConfig.topItems ?? [];
const mediaBrowserData = { ...mediaBrowserConfig };
const favoritesData = { ...favoritesConfig, exclude: exclude.join(", "), topItems: topItems.join(", ") };
const shortcutData = { ...shortcutConfig };
return x`
Shortcut
Favorites
The following needs to be configured using code (YAML):
- customFavorites
- customThumbnails
- customThumbnailsIfMissing
`;
}
static get styles() {
return i$8`
h3 {
margin: 20px 0 10px;
font-size: 1.1em;
border-bottom: 1px solid var(--divider-color);
padding-bottom: 5px;
}
h3:first-child {
margin-top: 0;
}
.yaml-note {
margin-top: 20px;
}
`;
}
}
customElements.define("mxmp-media-browser-tab", MediaBrowserTab);
var __defProp$D = Object.defineProperty;
var __decorateClass$D = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$D(target, key, result);
return result;
};
class SectionTab extends BaseEditor {
constructor() {
super(...arguments);
this.sectionChanged = (ev) => {
const changed = ev.detail.value;
this.config = { ...this.config, [this.section]: { ...this.config[this.section] ?? {}, ...changed } };
this.configChanged();
};
}
render() {
return x`
`;
}
}
__decorateClass$D([
n$4({ attribute: false })
], SectionTab.prototype, "schema");
__decorateClass$D([
n$4()
], SectionTab.prototype, "section");
customElements.define("mxmp-section-tab", SectionTab);
var __defProp$C = Object.defineProperty;
var __decorateClass$C = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$C(target, key, result);
return result;
};
class Form extends BaseEditor {
constructor() {
super(...arguments);
this.handleValueChanged = (ev) => {
const changed = ev.detail.value;
if (this.section) {
this.config = {
...this.config,
[this.section]: { ...this.config[this.section] ?? {}, ...changed }
};
} else {
this.config = { ...this.config, ...changed };
}
this.configChanged();
};
}
render() {
const schema = filterEditorSchemaOnCardType(this.schema, this.config.type);
const data = this.section ? this.config[this.section] ?? {} : this.data || this.config;
return x`
`;
}
}
__decorateClass$C([
n$4({ attribute: false })
], Form.prototype, "schema");
__decorateClass$C([
n$4({ attribute: false })
], Form.prototype, "data");
__decorateClass$C([
n$4()
], Form.prototype, "changed");
__decorateClass$C([
n$4()
], Form.prototype, "section");
function createComputeLabel() {
return ({ help, label, name }) => {
if (label) {
return label;
}
const unCamelCased = name.replace(/([A-Z])/g, " $1");
const capitalized = unCamelCased.charAt(0).toUpperCase() + unCamelCased.slice(1);
return capitalized + (help ? ` (${help})` : "");
};
}
function filterEditorSchemaOnCardType(schema, cardType) {
return schema.filter((schema2) => schema2.cardType === void 0 || cardType.indexOf(schema2.cardType) > -1);
}
customElements.define("mxmp-editor-form", Form);
var __defProp$B = Object.defineProperty;
var __decorateClass$B = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$B(target, key, result);
return result;
};
var Tab = /* @__PURE__ */ ((Tab2) => {
Tab2["COMMON"] = "Common";
Tab2["PLAYER"] = "Player";
Tab2["MEDIA_BROWSER"] = "Media Browser";
Tab2["GROUPS"] = "Groups";
Tab2["GROUPING"] = "Grouping";
Tab2["VOLUMES"] = "Volumes";
Tab2["QUEUE"] = "Queue";
Tab2["SEARCH"] = "Search";
return Tab2;
})(Tab || {});
class CardEditor extends BaseEditor {
constructor() {
super(...arguments);
this.activeTab = "Common";
this.navigatePrev = () => {
const idx = this.activeTabIndex;
if (idx > 0) {
this.activeTab = this.tabs[idx - 1];
this.scrollToActiveTab();
}
};
this.navigateNext = () => {
const idx = this.activeTabIndex;
if (idx < this.tabs.length - 1) {
this.activeTab = this.tabs[idx + 1];
this.scrollToActiveTab();
}
};
}
get tabs() {
return Object.values(Tab).filter((tab) => tab !== "Queue" || isQueueSupported(this.config));
}
get activeTabIndex() {
return this.tabs.indexOf(this.activeTab);
}
scrollToActiveTab() {
requestAnimationFrame(() => {
const container = this.shadowRoot?.querySelector(".tabs-list");
const activeButton = this.shadowRoot?.querySelector(".tab-button.active");
if (container && activeButton) {
activeButton.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
}
});
}
render() {
if (!this.config) {
return x``;
}
const tabs = this.tabs;
const activeIndex = this.activeTabIndex;
const showLeftArrow = activeIndex > 0;
const showRightArrow = activeIndex < tabs.length - 1;
return x`
${tabs.map(
(tab) => x` `
)}
${this.renderTabContent()}
`;
}
renderTabContent() {
const c3 = this.config, h2 = this.hass;
const t2 = (s2, sec) => x``;
return r(this.activeTab, [
["Common", () => x``],
["Player", () => x``],
["Media Browser", () => x``],
["Groups", () => t2(GROUPS_SCHEMA, "groups")],
["Grouping", () => t2(GROUPING_SCHEMA, "grouping")],
["Volumes", () => t2(VOLUMES_SCHEMA, "volumes")],
["Queue", () => t2(QUEUE_SCHEMA, "queue")],
["Search", () => t2(SEARCH_SCHEMA, "search")]
]);
}
static get styles() {
return i$8`
:host {
display: block;
}
.tabs-container {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 0 10px;
border-bottom: 1px solid var(--divider-color, #e0e0e0);
}
.tabs-list {
display: flex;
gap: 4px;
overflow-x: auto;
flex: 1;
scrollbar-width: none;
padding-bottom: 2px;
}
.tabs-list::-webkit-scrollbar {
display: none;
}
.tab-button {
height: 32px;
border: none;
background: transparent;
color: var(--primary-text-color);
font-size: 14px;
cursor: pointer;
border-radius: 4px;
position: relative;
padding: 0 8px;
white-space: nowrap;
}
.tab-button:hover {
background: var(--secondary-background-color);
}
.tab-button.active {
color: var(--primary-color);
}
.tab-button.active::after {
content: '';
position: absolute;
bottom: -3px;
left: 0;
right: 0;
height: 2px;
background: var(--primary-color);
}
.nav-arrow {
--icon-button-size: 32px;
--icon-size: 20px;
color: var(--primary-color);
flex-shrink: 0;
}
.nav-arrow.hidden {
visibility: hidden;
}
`;
}
}
__decorateClass$B([
r$3()
], CardEditor.prototype, "activeTab");
customElements.define("mxmp-editor", CardEditor);
var __defProp$A = Object.defineProperty;
var __decorateClass$A = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$A(target, key, result);
return result;
};
class Shuffle extends i$5 {
constructor() {
super(...arguments);
this.shuffle = async () => await this.mediaControlService.shuffle(this.activePlayer);
}
render() {
this.activePlayer = this.store.activePlayer;
this.mediaControlService = this.store.mediaControlService;
return x` `;
}
shuffleIcon() {
return this.activePlayer?.attributes.shuffle ? mdiShuffle : mdiShuffleDisabled;
}
}
__decorateClass$A([
n$4({ attribute: false })
], Shuffle.prototype, "store");
customElements.define("mxmp-shuffle", Shuffle);
var __defProp$z = Object.defineProperty;
var __decorateClass$z = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$z(target, key, result);
return result;
};
class Repeat extends i$5 {
constructor() {
super(...arguments);
this.repeat = async () => await this.mediaControlService.repeat(this.activePlayer);
}
render() {
this.activePlayer = this.store.activePlayer;
this.mediaControlService = this.store.mediaControlService;
return x` `;
}
repeatIcon() {
const repeatState = this.activePlayer?.attributes.repeat;
return repeatState === "all" ? mdiRepeat : repeatState === "one" ? mdiRepeatOnce : mdiRepeatOff;
}
}
__decorateClass$z([
n$4({ attribute: false })
], Repeat.prototype, "store");
customElements.define("mxmp-repeat", Repeat);
var __defProp$y = Object.defineProperty;
var __decorateClass$y = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$y(target, key, result);
return result;
};
class Source extends i$5 {
constructor() {
super(...arguments);
this.setSource = async (event) => await this.mediaControlService.setSource(this.activePlayer, this.activePlayer.attributes.source_list[event.detail.index]);
}
render() {
this.activePlayer = this.store.activePlayer;
this.mediaControlService = this.store.mediaControlService;
const sourceLabel = this.store.hass.localize("ui.card.media_player.source") || "Source";
return x`
${sourceLabel}
${this.activePlayer.attributes.source_list?.map((source) => {
return x` ${source} `;
})}
`;
}
static get styles() {
return i$8`
div {
display: flex;
color: var(--primary-text-color);
justify-content: center;
gap: 10px;
}
span {
align-content: center;
}
`;
}
}
__decorateClass$y([
n$4({ attribute: false })
], Source.prototype, "store");
customElements.define("mxmp-source", Source);
var __defProp$x = Object.defineProperty;
var __decorateClass$x = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$x(target, key, result);
return result;
};
const { GROUPING, GROUPS, MEDIA_BROWSER, PLAYER, VOLUMES, QUEUE, SEARCH } = Section;
const TITLE_HEIGHT = 2;
const FOOTER_HEIGHT = 5;
class Card extends i$5 {
constructor() {
super(...arguments);
this.configError = null;
this.hashChangeListener = () => {
this.activePlayerId = void 0;
this.createStore();
};
this.showSectionListener = (event) => {
const section = event.detail;
if (!this.config.sections || this.config.sections.indexOf(section) > -1) {
this.section = section;
}
};
this.callMediaStartedListener = (event) => {
if (!this.showLoader && (!this.config.sections || event.detail.section === this.section)) {
this.cancelLoader = false;
setTimeout(() => {
if (!this.cancelLoader) {
this.showLoader = true;
this.loaderTimestamp = Date.now();
}
}, 300);
}
};
this.callMediaDoneListener = () => {
this.cancelLoader = true;
const duration = Date.now() - this.loaderTimestamp;
if (this.showLoader) {
if (duration < 1e3) {
setTimeout(() => this.showLoader = false, 1e3 - duration);
} else {
this.showLoader = false;
}
}
};
this.activePlayerListener = (event) => {
const newEntityId = event.detail.entityId;
if (newEntityId !== this.activePlayerId) {
this.activePlayerId = newEntityId;
if (this.config.sections?.includes(PLAYER)) {
this.section = PLAYER;
}
this.requestUpdate();
}
};
this.onMediaItemSelected = () => {
if (this.config.sections?.includes(PLAYER)) {
setTimeout(() => this.section = PLAYER, 1e3);
}
};
}
render() {
this.createStore();
let height = getHeight(this.config);
const sections = this.config.sections;
const showFooter = !sections || sections.length > 1;
const footerHeight = this.config.footerHeight || FOOTER_HEIGHT;
const contentHeight = showFooter ? height - footerHeight : height;
const title = this.config.title;
height = title ? height + TITLE_HEIGHT : height;
const noPlayersText = isSonosCard(this.config) ? "No supported players found" : "No players found. Make sure you have configured entities in the card's configuration, or configured `entityPlatform`.";
return x`
${title ? x`${title}
` : x``}
${this.configError ? x`${this.configError}
` : x``}
${this.activePlayerId ? r(this.section, [
[PLAYER, () => x`
`],
[GROUPS, () => x`
`],
[GROUPING, () => x`
`],
[VOLUMES, () => x`
`],
[MEDIA_BROWSER, () => x`
`],
[QUEUE, () => x`
`],
[SEARCH, () => x`
`]
]) : x`
${noPlayersText}
`}
${n$1(
showFooter,
() => x`
`
)}
`;
}
createStore() {
if (this.activePlayerId) {
this.store = new Store(this.hass, this.config, this.section, this, this.activePlayerId);
} else {
this.store = new Store(this.hass, this.config, this.section, this);
this.activePlayerId = this.store.activePlayer?.id;
}
}
getCardSize() {
return 3;
}
static getConfigElement() {
return document.createElement("mxmp-editor");
}
connectedCallback() {
super.connectedCallback();
if (cardDoesNotContainAllSections(this.config)) {
window.addEventListener(ACTIVE_PLAYER_EVENT, this.activePlayerListener);
}
window.addEventListener(CALL_MEDIA_STARTED, this.callMediaStartedListener);
window.addEventListener(CALL_MEDIA_DONE, this.callMediaDoneListener);
if (!this.config.storePlayerInSessionStorage && !this.config.doNotRememberSelectedPlayer) {
window.addEventListener("hashchange", this.hashChangeListener);
}
}
disconnectedCallback() {
window.removeEventListener(ACTIVE_PLAYER_EVENT, this.activePlayerListener);
window.removeEventListener(CALL_MEDIA_STARTED, this.callMediaStartedListener);
window.removeEventListener(CALL_MEDIA_DONE, this.callMediaDoneListener);
window.removeEventListener("hashchange", this.hashChangeListener);
super.disconnectedCallback();
}
haCardStyle(height) {
const width = getWidth(this.config);
const minWidth = this.config.minWidth ?? 20;
return o({
color: "var(--secondary-text-color)",
height: `${height}rem`,
minWidth: `${minWidth}rem`,
maxWidth: `${width}rem`,
overflow: "hidden",
// only set borderRadius if this.config.style.borderRadius is set, otherwise the card looks weird with box-shadow
...this.config.style?.borderRadius ? { borderRadius: this.config.style.borderRadius } : {},
...this.config.baseFontSize ? {
fontSize: `${this.config.baseFontSize}rem`,
"--mxmp-font-size": `${this.config.baseFontSize}rem`,
"--ha-font-size-s": "0.75em",
"--ha-font-size-m": "0.875em",
"--ha-font-size-l": "1em",
"--ha-font-size-xl": "1.125em",
"--ha-font-size-2xl": "1.25em",
"--ha-font-size-4xl": "1.5em"
} : {},
...this.config.fontFamily ? {
fontFamily: this.config.fontFamily,
"--mdc-typography-font-family": this.config.fontFamily,
"--ha-font-family-body": this.config.fontFamily
} : {}
});
}
footerStyle(height) {
return o({
height: `${height}rem`,
padding: "0 1rem"
});
}
contentStyle(height) {
return o({
overflowY: "auto",
height: `${height}rem`
});
}
setConfig(config) {
const newConfig = JSON.parse(JSON.stringify(config));
for (const [key, value] of Object.entries(newConfig)) {
if (Array.isArray(value) && value.length === 0) {
delete newConfig[key];
}
}
const showQueue = isQueueSupported(newConfig);
const sections = newConfig.sections || Object.values(Section).filter((section) => showQueue || section !== QUEUE);
if (newConfig.startSection && sections.includes(newConfig.startSection)) {
this.section = newConfig.startSection;
} else if (sections) {
this.section = sections.includes(PLAYER) ? PLAYER : sections.includes(MEDIA_BROWSER) ? MEDIA_BROWSER : sections.includes(GROUPS) ? GROUPS : sections.includes(GROUPING) ? GROUPING : sections.includes(SEARCH) ? SEARCH : sections.includes(QUEUE) && showQueue ? QUEUE : VOLUMES;
} else {
this.section = PLAYER;
}
newConfig.mediaBrowser = newConfig.mediaBrowser ?? {};
newConfig.mediaBrowser.favorites = newConfig.mediaBrowser.favorites ?? {};
newConfig.mediaBrowser.itemsPerRow = newConfig.mediaBrowser.itemsPerRow || 4;
if (newConfig.entities?.length && newConfig.entities[0].entity) {
newConfig.entities = newConfig.entities.map((entity) => entity.entity);
}
if (isSonosCard(newConfig) && newConfig.entityPlatform === void 0) {
newConfig.entityPlatform = "sonos";
if (newConfig.showNonSonosPlayers) {
newConfig.entityPlatform = void 0;
}
}
this.configError = this.getConfigError(newConfig);
this.config = newConfig;
}
getConfigError(config) {
const isMusicAssistant = config.entityPlatform === "music_assistant";
const hasShowNonSonos = !!config.showNonSonosPlayers;
const hasOtherPlatform = !!config.entityPlatform && config.entityPlatform !== "music_assistant" && config.entityPlatform !== "sonos";
const activeCount = [isMusicAssistant, hasShowNonSonos, hasOtherPlatform].filter(Boolean).length;
if (activeCount > 1) {
return "Conflicting configuration: only one of useMusicAssistant, showNonSonosPlayers, or entityPlatform can be set at a time. Please fix your configuration.";
}
return null;
}
static get styles() {
return i$8`
:host {
--mdc-icon-button-size: 3rem;
--mdc-icon-size: 2rem;
}
ha-circular-progress {
--md-sys-color-primary: var(--accent-color);
}
.loader {
position: absolute;
z-index: 1000;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
--mdc-theme-primary: var(--accent-color);
}
.title {
margin: 0.4rem 0;
text-align: center;
font-weight: bold;
font-size: calc(var(--mxmp-font-size, 1rem) * 1.2);
color: var(--secondary-text-color);
}
.no-players {
text-align: center;
margin-top: 50%;
}
`;
}
}
__decorateClass$x([
n$4({ attribute: false })
], Card.prototype, "hass");
__decorateClass$x([
n$4({ attribute: false })
], Card.prototype, "config");
__decorateClass$x([
r$3()
], Card.prototype, "section");
__decorateClass$x([
r$3()
], Card.prototype, "store");
__decorateClass$x([
r$3()
], Card.prototype, "showLoader");
__decorateClass$x([
r$3()
], Card.prototype, "loaderTimestamp");
__decorateClass$x([
r$3()
], Card.prototype, "cancelLoader");
__decorateClass$x([
r$3()
], Card.prototype, "activePlayerId");
__decorateClass$x([
r$3()
], Card.prototype, "configError");
class GroupingItem {
constructor(player, activePlayer, isModified) {
this.isDisabled = false;
this.isMain = player.id === activePlayer.id;
this.isModified = isModified;
this.currentlyJoined = this.isMain || activePlayer.hasMember(player.id);
this.isSelected = isModified ? !this.currentlyJoined : this.currentlyJoined;
this.player = player;
this.name = player.name;
this.icon = this.isSelected ? "check-circle" : "checkbox-blank-circle-outline";
}
}
const groupingSectionStyles = [
listStyle,
i$8`
:host {
--mdc-icon-size: 24px;
}
.wrapper {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
}
.list {
flex: 1;
overflow: auto;
}
.buttons {
flex-shrink: 0;
margin: 0 1rem;
padding-top: 0.5rem;
}
.apply {
--control-button-background-color: var(--accent-color);
}
[hidden] {
display: none !important;
}
.applying {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.3);
z-index: 10;
pointer-events: none;
}
`
];
const SYNC_POLL_INTERVAL = 500;
const SYNC_TIMEOUT = 3e4;
function buildGroupingItems(store, modifiedItems) {
const items = store.allMediaPlayers.map((player) => new GroupingItem(player, store.activePlayer, modifiedItems.includes(player.id)));
const selected = items.filter((item) => item.isSelected);
if (selected.length === 1) {
selected[0].isDisabled = true;
}
if (store.config.grouping?.disableMainSpeakers) {
const mainIds = store.allGroups.filter((p2) => p2.members.length > 1).map((p2) => p2.id);
items.forEach((item) => {
if (mainIds.includes(item.player.id)) {
item.isDisabled = true;
}
});
}
if (!store.config.grouping?.dontSortMembersOnTop) {
items.sort((a2, b2) => {
if (a2.isMain) {
return -1;
}
if (b2.isMain) {
return 1;
}
if (a2.currentlyJoined !== b2.currentlyJoined) {
return a2.currentlyJoined ? -1 : 1;
}
return 0;
});
}
return items;
}
function waitForGroupSync(store, mainPlayerId, expectedIds) {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
clearInterval(poll);
resolve();
}, SYNC_TIMEOUT);
const poll = setInterval(() => {
const mainEntity = store.hass.states[mainPlayerId];
if (mainEntity) {
const actualIds = getGroupPlayerIds(mainEntity).sort();
if (actualIds.length === expectedIds.length && actualIds.every((id, i5) => id === expectedIds[i5])) {
clearInterval(poll);
clearTimeout(timeout);
resolve();
}
}
}, SYNC_POLL_INTERVAL);
});
}
var __defProp$w = Object.defineProperty;
var __decorateClass$w = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$w(target, key, result);
return result;
};
class GroupingButton extends i$5 {
render() {
const iconAndName = !!this.icon && !!this.name || E;
const buttonStyle = o({
...this.buttonColor ? { "--control-button-background-color": this.buttonColor } : {},
...this.fontSize ? { fontSize: `${this.fontSize}rem` } : {}
});
return x`
${this.icon ? x` ` : ""} ${this.name ? x`${this.name}` : ""}
`;
}
static get styles() {
return i$8`
ha-control-button {
width: fit-content;
--control-button-background-color: var(--secondary-text-color);
--control-button-icon-color: var(--secondary-text-color);
}
ha-control-button[selected] {
--control-button-icon-color: var(--accent-color);
}
span {
font-weight: bold;
}
`;
}
}
__decorateClass$w([
n$4()
], GroupingButton.prototype, "icon");
__decorateClass$w([
n$4()
], GroupingButton.prototype, "name");
__decorateClass$w([
n$4()
], GroupingButton.prototype, "selected");
__decorateClass$w([
n$4()
], GroupingButton.prototype, "buttonColor");
__decorateClass$w([
n$4()
], GroupingButton.prototype, "fontSize");
customElements.define("mxmp-grouping-button", GroupingButton);
var __defProp$v = Object.defineProperty;
var __decorateClass$v = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$v(target, key, result);
return result;
};
class GroupingActions extends i$5 {
render() {
const { store, selectedPredefinedGroup } = this;
const { joinedCount, notJoinedCount } = store.getJoinedAndNotJoinedCounts();
const joinAllIcon = store.config.grouping?.buttonIcons?.joinAll ?? "mdi:checkbox-multiple-marked-outline";
const unJoinAllIcon = store.config.grouping?.buttonIcons?.unJoinAll ?? "mdi:minus-box-multiple-outline";
const pgIcon = store.config.grouping?.buttonIcons?.predefinedGroup ?? "mdi:speaker-multiple";
const fontSize = store.config.grouping?.buttonFontSize;
const isCompact = store.config.grouping?.compact || E;
const hideUngroupButtons = !!store.config.grouping?.hideUngroupAllButtons;
return x`
this.dispatch({ type: "select-all" })}
.icon=${joinAllIcon}
.buttonColor=${store.config.grouping?.buttonColor}
.fontSize=${fontSize}
>
this.dispatch({ type: "deselect-all" })}
.icon=${unJoinAllIcon}
.buttonColor=${store.config.grouping?.buttonColor}
.fontSize=${fontSize}
>
${store.predefinedGroups.map((pg) => {
const isSelected = selectedPredefinedGroup?.name === pg.name;
return x` this.dispatch({ type: "select-predefined-group", predefinedGroup: pg })}
.icon=${pgIcon}
.name=${pg.name}
.selected=${isSelected}
.buttonColor=${store.config.grouping?.buttonColor}
.fontSize=${fontSize}
>`;
})}
`;
}
dispatch(detail) {
this.dispatchEvent(new CustomEvent("grouping-action", { detail, bubbles: true, composed: true }));
}
static get styles() {
return i$8`
.predefined-groups {
margin: 1rem;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
flex-shrink: 0;
}
.predefined-groups[compact] {
margin: 0.3rem !important;
}
`;
}
}
__decorateClass$v([
n$4({ attribute: false })
], GroupingActions.prototype, "store");
__decorateClass$v([
n$4({ attribute: false })
], GroupingActions.prototype, "selectedPredefinedGroup");
customElements.define("mxmp-grouping-actions", GroupingActions);
var __defProp$u = Object.defineProperty;
var __decorateClass$u = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$u(target, key, result);
return result;
};
class GroupingItemRow extends i$5 {
constructor() {
super(...arguments);
this.applying = false;
}
render() {
const { item, applying, store } = this;
const isModified = item.isModified || E;
const isDisabled = item.isDisabled || applying || E;
const isCompact = store.config.grouping?.compact || E;
const isSelected = item.isSelected || E;
return x`
`;
}
handleToggle() {
this.dispatchEvent(new CustomEvent("toggle-item", { detail: this.item, bubbles: true, composed: true }));
}
static get styles() {
return i$8`
.item {
color: var(--secondary-text-color);
padding: 0.5rem;
display: flex;
align-items: center;
}
.item[compact] {
padding-top: 0;
padding-bottom: 0;
border-bottom: 1px solid #333;
}
.icon {
padding-right: 0.5rem;
flex-shrink: 0;
}
.icon[selected] {
color: var(--accent-color);
}
.item[modified] .name {
font-weight: bold;
font-style: italic;
}
.item[disabled] .icon {
color: var(--disabled-text-color);
}
.name-and-volume {
display: flex;
flex-direction: column;
flex: 1;
}
.volume {
--accent-color: var(--secondary-text-color);
}
`;
}
}
__decorateClass$u([
n$4({ attribute: false })
], GroupingItemRow.prototype, "store");
__decorateClass$u([
n$4({ attribute: false })
], GroupingItemRow.prototype, "item");
__decorateClass$u([
n$4({ type: Boolean })
], GroupingItemRow.prototype, "applying");
customElements.define("mxmp-grouping-item-row", GroupingItemRow);
var __defProp$t = Object.defineProperty;
var __decorateClass$t = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$t(target, key, result);
return result;
};
const POST_SYNC_DELAY = 1e3;
class Grouping extends i$5 {
constructor() {
super(...arguments);
this.modifiedItems = [];
this.applying = false;
}
render() {
if (!this.applying) {
this.refreshFromStore();
}
const items = this.frozenGroupingItems ?? this.groupingItems;
const { applying, selectedPredefinedGroup, modifiedItems, store } = this;
const hasChanges = modifiedItems.length > 0 || !!selectedPredefinedGroup;
const hideButtons = applying || !hasChanges || !!store.config.grouping?.skipApplyButton;
return x`
${items.map(
(item) => x`
`
)}
${store.hass.localize("ui.common.apply") || "Apply"}
${store.hass.localize("ui.common.cancel") || "Cancel"}
`;
}
static get styles() {
return groupingSectionStyles;
}
handleGroupingAction(e2) {
const { type, predefinedGroup } = e2.detail;
if (type === "select-all") {
this.selectAll();
} else if (type === "deselect-all") {
this.deSelectAll();
} else if (type === "select-predefined-group") {
this.selectPredefinedGroup(predefinedGroup);
}
}
handleToggleItem(e2) {
this.toggleItem(e2.detail);
}
toggleItem(item) {
if (item.isDisabled || this.applying) {
return;
}
this.toggleModifiedItem(item);
}
toggleModifiedItem(item) {
this.modifiedItems = this.modifiedItems.includes(item.player.id) ? this.modifiedItems.filter((id) => id !== item.player.id) : [...this.modifiedItems, item.player.id];
this.selectedPredefinedGroup = void 0;
}
async applyGrouping() {
if (this.applying) {
return;
}
const activePlayer = this.store.activePlayer;
const joinedPlayers = this.store.getJoinedPlayerIds();
const { unJoin, join, newMainPlayer } = getGroupingChanges(this.groupingItems, joinedPlayers, activePlayer.id);
const selectedPG = this.selectedPredefinedGroup;
const expectedIds = this.groupingItems.filter((i5) => i5.isSelected).map((i5) => i5.player.id).sort();
this.frozenGroupingItems = this.groupingItems.map(
(item) => Object.assign(new GroupingItem(item.player, activePlayer, item.isModified), {
isSelected: item.isSelected
})
);
this.applying = true;
this.modifiedItems = [];
this.selectedPredefinedGroup = void 0;
try {
await this.executeChanges(join, unJoin, newMainPlayer, selectedPG);
this.switchActivePlayerIfNeeded(activePlayer, newMainPlayer, unJoin);
await waitForGroupSync(this.store, newMainPlayer, expectedIds);
await new Promise((resolve) => setTimeout(resolve, POST_SYNC_DELAY));
} finally {
this.applying = false;
this.frozenGroupingItems = void 0;
}
}
refreshFromStore() {
this.groupingItems = buildGroupingItems(this.store, this.modifiedItems);
const hasChanges = this.modifiedItems.length > 0 || !!this.selectedPredefinedGroup;
if (this.store.config.grouping?.skipApplyButton && hasChanges) {
this.applyGrouping();
}
}
async executeChanges(join, unJoin, mainPlayer, pg) {
if (join.length) {
await this.store.mediaControlService.join(mainPlayer, join);
}
if (unJoin.length) {
await this.store.mediaControlService.unJoin(unJoin);
}
if (pg) {
await this.store.mediaControlService.activatePredefinedGroup(pg);
}
}
switchActivePlayerIfNeeded(activePlayer, newMainPlayer, unJoin) {
if (newMainPlayer !== activePlayer.id && !this.store.config.grouping?.dontSwitchPlayer) {
dispatchActivePlayerId(newMainPlayer, this.store.config, this);
}
if (this.store.config.entityId && unJoin.includes(this.store.config.entityId) && this.store.config.grouping?.dontSwitchPlayer) {
dispatchActivePlayerId(this.store.config.entityId, this.store.config, this);
}
}
cancelGrouping() {
if (this.applying) {
return;
}
this.modifiedItems = [];
this.selectedPredefinedGroup = void 0;
}
async selectPredefinedGroup(pg) {
let hasChanges = false;
for (const item of this.groupingItems) {
const shouldBeSelected = pg.entities.some((e2) => e2.player.id === item.player.id);
if (shouldBeSelected !== item.isSelected) {
this.toggleModifiedItem(item);
hasChanges = true;
}
}
this.selectedPredefinedGroup = pg;
if (!hasChanges && this.store.config.grouping?.skipApplyButton) {
await this.store.mediaControlService.activatePredefinedGroup(pg);
this.selectedPredefinedGroup = void 0;
}
}
selectAll() {
this.groupingItems.filter((item) => !item.isSelected).forEach((item) => this.toggleItem(item));
}
deSelectAll() {
this.groupingItems.filter((item) => !item.isMain && item.isSelected || item.isMain && !item.isSelected).forEach((item) => this.toggleItem(item));
}
}
__decorateClass$t([
n$4({ attribute: false })
], Grouping.prototype, "store");
__decorateClass$t([
r$3()
], Grouping.prototype, "modifiedItems");
__decorateClass$t([
r$3()
], Grouping.prototype, "selectedPredefinedGroup");
__decorateClass$t([
r$3()
], Grouping.prototype, "applying");
var __defProp$s = Object.defineProperty;
var __decorateClass$s = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$s(target, key, result);
return result;
};
class PlayingBars extends i$5 {
constructor() {
super(...arguments);
this.show = false;
}
render() {
if (!this.show) {
return E;
}
return x`
`;
}
static get styles() {
return i$8`
@keyframes sound {
0% {
opacity: 0.35;
height: 0.15rem;
}
100% {
opacity: 1;
height: 1rem;
}
}
:host {
display: flex;
align-items: center;
}
.bars {
width: 0.55rem;
height: 1rem;
position: relative;
}
.bars > div {
background: var(--secondary-text-color);
bottom: 0;
height: 0.15rem;
position: absolute;
width: 0.15rem;
animation: sound 0ms -800ms linear infinite alternate;
display: block;
}
.bars > div:first-child {
left: 0.05rem;
animation-duration: 474ms;
}
.bars > div:nth-child(2) {
left: 0.25rem;
animation-duration: 433ms;
}
.bars > div:last-child {
left: 0.45rem;
animation-duration: 407ms;
}
`;
}
}
__decorateClass$s([
n$4({ type: Boolean })
], PlayingBars.prototype, "show");
customElements.define("mxmp-playing-bars", PlayingBars);
var __defProp$r = Object.defineProperty;
var __decorateClass$r = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$r(target, key, result);
return result;
};
class GroupIcons extends i$5 {
constructor() {
super(...arguments);
this.icons = [];
}
render() {
const length = this.icons.length;
const iconsToShow = this.icons.slice(0, 4);
const iconClass = length > 1 ? "small" : "";
const iconsHtml = iconsToShow.map((icon) => x` `);
if (length > 4) {
iconsHtml.splice(3, 1, x`+${length - 3}`);
}
if (length > 2) {
iconsHtml.splice(2, 0, x`
`);
}
return x` ${iconsHtml}
`;
}
static get styles() {
return i$8`
.icons {
text-align: center;
margin: 0;
min-width: 5em;
max-width: 5em;
}
.icons[empty] {
min-width: 1em;
max-width: 1em;
}
ha-icon {
--mdc-icon-size: 3em;
margin: 1em;
}
ha-icon.small {
--mdc-icon-size: 2em;
margin: 0;
}
`;
}
}
__decorateClass$r([
n$4({ attribute: false })
], GroupIcons.prototype, "icons");
customElements.define("mxmp-group-icons", GroupIcons);
var __defProp$q = Object.defineProperty;
var __decorateClass$q = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$q(target, key, result);
return result;
};
class Group extends i$5 {
constructor() {
super(...arguments);
this.selected = false;
this.dispatchEntityIdEvent = () => {
if (this.selected) {
dispatchActivePlayerId(this.player.id, this.store.config, this);
}
};
}
render() {
const { hideCurrentTrack, itemMargin, backgroundColor, speakersFontSize, titleFontSize, compact } = this.store.config.groups ?? {};
const currentTrack = hideCurrentTrack ? "" : this.player.getCurrentTrack();
const speakerList = getSpeakerList(this.player, this.store.predefinedGroups);
const icons = this.player.members.map((member) => member.attributes.icon).filter((icon) => icon);
const listItemStyle = o({
...itemMargin ? { margin: itemMargin } : {},
...backgroundColor ? { background: backgroundColor } : {}
});
const speakersStyle = o(speakersFontSize ? { fontSize: `${speakersFontSize}rem` } : {});
const titleStyle = o(titleFontSize ? { fontSize: `${titleFontSize}rem` } : {});
return x`
this.handleGroupClicked()}
style=${listItemStyle}
>
${speakerList}
${currentTrack}
`;
}
connectedCallback() {
super.connectedCallback();
this.dispatchEntityIdEvent();
}
handleGroupClicked() {
if (!this.selected) {
this.selected = true;
if (!this.store.config.doNotRememberSelectedPlayer) {
if (this.store.config.storePlayerInSessionStorage) {
window.sessionStorage.setItem(SESSION_STORAGE_PLAYER_ID, this.player.id);
} else {
const newUrl = window.location.href.replace(/#.*/g, "");
window.location.replace(`${newUrl}#${this.player.id}`);
}
}
this.dispatchEntityIdEvent();
}
}
static get styles() {
return i$8`
mwc-list-item {
height: fit-content;
margin: 1rem;
border-radius: 1rem;
background: var(--secondary-background-color);
padding-left: 0;
}
mwc-list-item.compact {
margin: 0.3rem;
}
.row {
display: flex;
margin: 1em 0;
align-items: center;
}
.text {
display: flex;
flex-direction: column;
justify-content: center;
}
.speakers {
white-space: initial;
font-size: calc(var(--mxmp-font-size, 1rem) * 1.1);
font-weight: bold;
color: var(--secondary-text-color);
}
.song-title {
font-size: calc(var(--mxmp-font-size, 1rem) * 0.9);
font-weight: bold;
}
.compact ha-icon {
--mdc-icon-size: 2em;
}
.compact div {
margin: 0.1em;
}
mxmp-playing-bars {
margin-left: 0.5rem;
}
`;
}
}
__decorateClass$q([
n$4({ attribute: false })
], Group.prototype, "store");
__decorateClass$q([
n$4({ attribute: false })
], Group.prototype, "player");
__decorateClass$q([
n$4({ type: Boolean })
], Group.prototype, "selected");
customElements.define("mxmp-group", Group);
var __defProp$p = Object.defineProperty;
var __decorateClass$p = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$p(target, key, result);
return result;
};
class Groups extends i$5 {
render() {
const { buttonWidth } = this.store.config.groups ?? {};
const listStyleMap = buttonWidth ? o({ width: `${buttonWidth}rem` }) : "";
return x`
${this.store.allGroups.map((group) => {
const selected = this.store.activePlayer.id === group.id;
return x` `;
})}
`;
}
static get styles() {
return listStyle;
}
}
__decorateClass$p([
n$4({ attribute: false })
], Groups.prototype, "store");
var __defProp$o = Object.defineProperty;
var __decorateClass$o = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$o(target, key, result);
return result;
};
class FavoritesList extends i$5 {
render() {
this.config = this.store.config;
return x`
${itemsWithFallbacks(this.items, this.config).map((item) => {
return x` this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED, item))} .item=${item}> `;
})}
`;
}
static get styles() {
return listStyle;
}
}
__decorateClass$o([
n$4({ attribute: false })
], FavoritesList.prototype, "store");
__decorateClass$o([
n$4({ type: Array })
], FavoritesList.prototype, "items");
customElements.define("mxmp-favorites-list", FavoritesList);
var __defProp$n = Object.defineProperty;
var __decorateClass$n = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$n(target, key, result);
return result;
};
class FavoritesIcons extends i$5 {
render() {
const mediaBrowserConfig = this.store.config.mediaBrowser ?? {};
const favoritesConfig = this.store.config.mediaBrowser?.favorites ?? {};
const items = itemsWithFallbacks(this.items, this.store.config);
let prevType = "";
this.sortItemsByFavoriteTypeIfConfigured(items, favoritesConfig);
const iconTitleColor = favoritesConfig.iconTitleColor;
const iconTitleBgColor = favoritesConfig.iconTitleBackgroundColor;
const border = favoritesConfig.iconBorder;
const padding = favoritesConfig.iconPadding;
const typeColor = favoritesConfig.typeColor;
const typeFontSize = favoritesConfig.typeFontSize;
const typeFontWeight = favoritesConfig.typeFontWeight;
const typeMarginBottom = favoritesConfig.typeMarginBottom;
return x`
${items.map((item) => {
const showFavoriteType = favoritesConfig.sortByType && item.favoriteType !== prevType || E;
const toRender = x`
${item.favoriteType}
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED, item))}
>
${renderFavoritesItem(item, !item.thumbnail || !favoritesConfig.hideTitleForThumbnailIcons, iconTitleColor, iconTitleBgColor)}
`;
prevType = item.favoriteType;
return toRender;
})}
`;
}
sortItemsByFavoriteTypeIfConfigured(items, config) {
if (config.sortByType) {
items.sort((a2, b2) => {
return a2.favoriteType?.localeCompare(b2.favoriteType ?? "") || a2.title.localeCompare(b2.title);
});
}
}
buttonStyle(favoritesItemsPerRow) {
const margin = "1%";
const size = `calc(100% / ${favoritesItemsPerRow} - ${margin} * 2)`;
return o({
width: size,
height: size,
margin
});
}
static get styles() {
return [
mediaItemTitleStyle,
i$8`
.icons {
display: flex;
flex-wrap: wrap;
}
.thumbnail {
width: 100%;
padding-bottom: 100%;
margin: 0 6%;
background-size: 100%;
background-repeat: no-repeat;
background-position: center;
}
.title {
font-size: calc(var(--mxmp-font-size, 1rem) * 0.8);
position: absolute;
width: 100%;
line-height: 160%;
bottom: 0;
background-color: rgba(var(--rgb-card-background-color), 0.733);
}
.favorite-type {
width: 100%;
display: none;
margin-top: 0.2rem;
margin-left: 15px;
font-weight: bold;
}
.favorite-type[show] {
display: block;
}
`
];
}
}
__decorateClass$n([
n$4({ attribute: false })
], FavoritesIcons.prototype, "store");
__decorateClass$n([
n$4({ attribute: false })
], FavoritesIcons.prototype, "items");
customElements.define("mxmp-favorites-icons", FavoritesIcons);
var __defProp$m = Object.defineProperty;
var __decorateClass$m = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$m(target, key, result);
return result;
};
const _Favorites = class _Favorites2 extends i$5 {
constructor() {
super(...arguments);
this.layout = "auto";
this.cachedFavorites = null;
this.cachedFavoritesPlayerId = null;
this.onFavoriteSelected = async (event) => {
const mediaItem = event.detail;
await this.playFavorite(mediaItem);
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED, mediaItem));
};
}
connectedCallback() {
super.connectedCallback();
void this.loadFavorites();
}
willUpdate(changedProperties) {
if (changedProperties.has("store")) {
const playerId = this.store?.activePlayer?.id;
if (playerId && playerId !== this.cachedFavoritesPlayerId) {
void this.loadFavorites();
}
}
}
async loadFavorites() {
const playerId = this.store?.activePlayer?.id;
if (!playerId) {
return;
}
this.cachedFavoritesPlayerId = playerId;
this.cachedFavorites = await this.getFavorites();
}
async getFavorites() {
const favoritesConfig = this.store.config.mediaBrowser?.favorites ?? {};
const player = this.store.activePlayer;
let favorites = await this.store.mediaBrowseService.getFavorites(player);
const topItems = favoritesConfig.topItems ?? [];
favorites.sort((a2, b2) => this.sortFavorites(a2.title, b2.title, topItems));
favorites = [
...favoritesConfig.customFavorites?.[player.id]?.map(_Favorites2.createFavorite) || [],
...favoritesConfig.customFavorites?.all?.map(_Favorites2.createFavorite) || [],
...favorites
];
return favoritesConfig.numberToShow ? favorites.slice(0, favoritesConfig.numberToShow) : favorites;
}
sortFavorites(a2, b2, topItems) {
const aIndex = indexOfWithoutSpecialChars(topItems, a2);
const bIndex = indexOfWithoutSpecialChars(topItems, b2);
if (aIndex > -1 && bIndex > -1) {
return aIndex - bIndex;
}
let result = bIndex - aIndex;
if (result === 0) {
result = a2.localeCompare(b2, "en", { sensitivity: "base" });
}
return result;
}
static createFavorite(source) {
return { ...source, can_play: true };
}
async playFavorite(mediaItem) {
const player = this.store.activePlayer;
if (mediaItem.media_content_type || mediaItem.media_content_id) {
await this.store.mediaControlService.playMedia(player, mediaItem);
} else {
await this.store.mediaControlService.setSource(player, mediaItem.title);
}
}
render() {
if (!this.cachedFavorites) {
return E;
}
if (!this.cachedFavorites.length) {
return x`No favorites found
`;
}
const useGrid = this.layout !== "list";
if (useGrid) {
return x`
`;
} else {
return x`
`;
}
}
static get styles() {
return i$8`
:host {
display: block;
flex: 1;
min-height: 0;
overflow: auto;
}
.no-items {
text-align: center;
margin-top: 50%;
}
mxmp-favorites-icons,
mxmp-favorites-list {
--mdc-icon-size: 24px;
--media-browse-item-size: 100px;
}
`;
}
};
__decorateClass$m([
n$4({ attribute: false })
], _Favorites.prototype, "store");
__decorateClass$m([
n$4({ type: String })
], _Favorites.prototype, "layout");
__decorateClass$m([
r$3()
], _Favorites.prototype, "cachedFavorites");
let Favorites = _Favorites;
function dim1(direction) {
return direction === "horizontal" ? "width" : "height";
}
function dim2(direction) {
return direction === "horizontal" ? "height" : "width";
}
class BaseLayout {
_getDefaultConfig() {
return {
direction: "vertical"
};
}
constructor(hostSink, config) {
this._latestCoords = { left: 0, top: 0 };
this._direction = null;
this._viewportSize = { width: 0, height: 0 };
this.totalScrollSize = { width: 0, height: 0 };
this.offsetWithinScroller = { left: 0, top: 0 };
this._pendingReflow = false;
this._pendingLayoutUpdate = false;
this._pin = null;
this._firstVisible = 0;
this._lastVisible = 0;
this._physicalMin = 0;
this._physicalMax = 0;
this._first = -1;
this._last = -1;
this._sizeDim = "height";
this._secondarySizeDim = "width";
this._positionDim = "top";
this._secondaryPositionDim = "left";
this._scrollPosition = 0;
this._scrollError = 0;
this._items = [];
this._scrollSize = 1;
this._overhang = 1e3;
this._hostSink = hostSink;
Promise.resolve().then(() => this.config = config || this._getDefaultConfig());
}
set config(config) {
Object.assign(this, Object.assign({}, this._getDefaultConfig(), config));
}
get config() {
return {
direction: this.direction
};
}
/**
* Maximum index of children + 1, to help estimate total height of the scroll
* space.
*/
get items() {
return this._items;
}
set items(items) {
this._setItems(items);
}
_setItems(items) {
if (items !== this._items) {
this._items = items;
this._scheduleReflow();
}
}
/**
* Primary scrolling direction.
*/
get direction() {
return this._direction;
}
set direction(dir) {
dir = dir === "horizontal" ? dir : "vertical";
if (dir !== this._direction) {
this._direction = dir;
this._sizeDim = dir === "horizontal" ? "width" : "height";
this._secondarySizeDim = dir === "horizontal" ? "height" : "width";
this._positionDim = dir === "horizontal" ? "left" : "top";
this._secondaryPositionDim = dir === "horizontal" ? "top" : "left";
this._triggerReflow();
}
}
/**
* Height and width of the viewport.
*/
get viewportSize() {
return this._viewportSize;
}
set viewportSize(dims) {
const { _viewDim1, _viewDim2 } = this;
Object.assign(this._viewportSize, dims);
if (_viewDim2 !== this._viewDim2) {
this._scheduleLayoutUpdate();
} else if (_viewDim1 !== this._viewDim1) {
this._checkThresholds();
}
}
/**
* Scroll offset of the viewport.
*/
get viewportScroll() {
return this._latestCoords;
}
set viewportScroll(coords) {
Object.assign(this._latestCoords, coords);
const oldPos = this._scrollPosition;
this._scrollPosition = this._latestCoords[this._positionDim];
const change = Math.abs(oldPos - this._scrollPosition);
if (change >= 1) {
this._checkThresholds();
}
}
/**
* Perform a reflow if one has been scheduled.
*/
reflowIfNeeded(force = false) {
if (force || this._pendingReflow) {
this._pendingReflow = false;
this._reflow();
}
}
set pin(options2) {
this._pin = options2;
this._triggerReflow();
}
get pin() {
if (this._pin !== null) {
const { index, block } = this._pin;
return {
index: Math.max(0, Math.min(index, this.items.length - 1)),
block
};
}
return null;
}
_clampScrollPosition(val) {
return Math.max(-this.offsetWithinScroller[this._positionDim], Math.min(val, this.totalScrollSize[dim1(this.direction)] - this._viewDim1));
}
unpin() {
if (this._pin !== null) {
this._sendUnpinnedMessage();
this._pin = null;
}
}
_updateLayout() {
}
// protected _viewDim2Changed(): void {
// this._scheduleLayoutUpdate();
// }
/**
* The height or width of the viewport, whichever corresponds to the scrolling direction.
*/
get _viewDim1() {
return this._viewportSize[this._sizeDim];
}
/**
* The height or width of the viewport, whichever does NOT correspond to the scrolling direction.
*/
get _viewDim2() {
return this._viewportSize[this._secondarySizeDim];
}
_scheduleReflow() {
this._pendingReflow = true;
}
_scheduleLayoutUpdate() {
this._pendingLayoutUpdate = true;
this._scheduleReflow();
}
// For triggering a reflow based on incoming changes to
// the layout config.
_triggerReflow() {
this._scheduleLayoutUpdate();
Promise.resolve().then(() => this.reflowIfNeeded());
}
_reflow() {
if (this._pendingLayoutUpdate) {
this._updateLayout();
this._pendingLayoutUpdate = false;
}
this._updateScrollSize();
this._setPositionFromPin();
this._getActiveItems();
this._updateVisibleIndices();
this._sendStateChangedMessage();
}
/**
* If we are supposed to be pinned to a particular
* item or set of coordinates, we set `_scrollPosition`
* accordingly and adjust `_scrollError` as needed
* so that the virtualizer can keep the scroll
* position in the DOM in sync
*/
_setPositionFromPin() {
if (this.pin !== null) {
const lastScrollPosition = this._scrollPosition;
const { index, block } = this.pin;
this._scrollPosition = this._calculateScrollIntoViewPosition({
index,
block: block || "start"
}) - this.offsetWithinScroller[this._positionDim];
this._scrollError = lastScrollPosition - this._scrollPosition;
}
}
/**
* Calculate the coordinates to scroll to, given
* a request to scroll to the element at a specific
* index.
*
* Supports the same positioning options (`start`,
* `center`, `end`, `nearest`) as the standard
* `Element.scrollIntoView()` method, but currently
* only considers the provided value in the `block`
* dimension, since we don't yet have any layouts
* that support virtualization in two dimensions.
*/
_calculateScrollIntoViewPosition(options2) {
const { block } = options2;
const index = Math.min(this.items.length, Math.max(0, options2.index));
const itemStartPosition = this._getItemPosition(index)[this._positionDim];
let scrollPosition = itemStartPosition;
if (block !== "start") {
const itemSize = this._getItemSize(index)[this._sizeDim];
if (block === "center") {
scrollPosition = itemStartPosition - 0.5 * this._viewDim1 + 0.5 * itemSize;
} else {
const itemEndPosition = itemStartPosition - this._viewDim1 + itemSize;
if (block === "end") {
scrollPosition = itemEndPosition;
} else {
const currentScrollPosition = this._scrollPosition;
scrollPosition = Math.abs(currentScrollPosition - itemStartPosition) < Math.abs(currentScrollPosition - itemEndPosition) ? itemStartPosition : itemEndPosition;
}
}
}
scrollPosition += this.offsetWithinScroller[this._positionDim];
return this._clampScrollPosition(scrollPosition);
}
getScrollIntoViewCoordinates(options2) {
return {
[this._positionDim]: this._calculateScrollIntoViewPosition(options2)
};
}
_sendUnpinnedMessage() {
this._hostSink({
type: "unpinned"
});
}
_sendVisibilityChangedMessage() {
this._hostSink({
type: "visibilityChanged",
firstVisible: this._firstVisible,
lastVisible: this._lastVisible
});
}
_sendStateChangedMessage() {
const childPositions = /* @__PURE__ */ new Map();
if (this._first !== -1 && this._last !== -1) {
for (let idx = this._first; idx <= this._last; idx++) {
childPositions.set(idx, this._getItemPosition(idx));
}
}
const message = {
type: "stateChanged",
scrollSize: {
[this._sizeDim]: this._scrollSize,
[this._secondarySizeDim]: null
},
range: {
first: this._first,
last: this._last,
firstVisible: this._firstVisible,
lastVisible: this._lastVisible
},
childPositions
};
if (this._scrollError) {
message.scrollError = {
[this._positionDim]: this._scrollError,
[this._secondaryPositionDim]: 0
};
this._scrollError = 0;
}
this._hostSink(message);
}
/**
* Number of items to display.
*/
get _num() {
if (this._first === -1 || this._last === -1) {
return 0;
}
return this._last - this._first + 1;
}
_checkThresholds() {
if (this._viewDim1 === 0 && this._num > 0 || this._pin !== null) {
this._scheduleReflow();
} else {
const min = Math.max(0, this._scrollPosition - this._overhang);
const max = Math.min(this._scrollSize, this._scrollPosition + this._viewDim1 + this._overhang);
if (this._physicalMin > min || this._physicalMax < max) {
this._scheduleReflow();
} else {
this._updateVisibleIndices({ emit: true });
}
}
}
/**
* Find the indices of the first and last items to intersect the viewport.
* Emit a visibleindiceschange event when either index changes.
*/
_updateVisibleIndices(options2) {
if (this._first === -1 || this._last === -1)
return;
let firstVisible = this._first;
while (firstVisible < this._last && Math.round(this._getItemPosition(firstVisible)[this._positionDim] + this._getItemSize(firstVisible)[this._sizeDim]) <= Math.round(this._scrollPosition)) {
firstVisible++;
}
let lastVisible = this._last;
while (lastVisible > this._first && Math.round(this._getItemPosition(lastVisible)[this._positionDim]) >= Math.round(this._scrollPosition + this._viewDim1)) {
lastVisible--;
}
if (firstVisible !== this._firstVisible || lastVisible !== this._lastVisible) {
this._firstVisible = firstVisible;
this._lastVisible = lastVisible;
if (options2 && options2.emit) {
this._sendVisibilityChangedMessage();
}
}
}
}
function paddingValueToNumber(v2) {
if (v2 === "match-gap") {
return Infinity;
}
return parseInt(v2);
}
function gapValueToNumber(v2) {
if (v2 === "auto") {
return Infinity;
}
return parseInt(v2);
}
function gap1(direction) {
return direction === "horizontal" ? "column" : "row";
}
function gap2(direction) {
return direction === "horizontal" ? "row" : "column";
}
function padding1(direction) {
return direction === "horizontal" ? ["left", "right"] : ["top", "bottom"];
}
function padding2(direction) {
return direction === "horizontal" ? ["top", "bottom"] : ["left", "right"];
}
class SizeGapPaddingBaseLayout extends BaseLayout {
constructor() {
super(...arguments);
this._itemSize = {};
this._gaps = {};
this._padding = {};
}
_getDefaultConfig() {
return Object.assign({}, super._getDefaultConfig(), {
itemSize: { width: "300px", height: "300px" },
gap: "8px",
padding: "match-gap"
});
}
// Temp, to support current flexWrap implementation
get _gap() {
return this._gaps.row;
}
// Temp, to support current flexWrap implementation
get _idealSize() {
return this._itemSize[dim1(this.direction)];
}
get _idealSize1() {
return this._itemSize[dim1(this.direction)];
}
get _idealSize2() {
return this._itemSize[dim2(this.direction)];
}
get _gap1() {
return this._gaps[gap1(this.direction)];
}
get _gap2() {
return this._gaps[gap2(this.direction)];
}
get _padding1() {
const padding = this._padding;
const [start, end] = padding1(this.direction);
return [padding[start], padding[end]];
}
get _padding2() {
const padding = this._padding;
const [start, end] = padding2(this.direction);
return [padding[start], padding[end]];
}
set itemSize(dims) {
const size = this._itemSize;
if (typeof dims === "string") {
dims = {
width: dims,
height: dims
};
}
const width = parseInt(dims.width);
const height = parseInt(dims.height);
if (width !== size.width) {
size.width = width;
this._triggerReflow();
}
if (height !== size.height) {
size.height = height;
this._triggerReflow();
}
}
set gap(spec) {
this._setGap(spec);
}
// This setter is overridden in specific layouts to narrow the accepted types
_setGap(spec) {
const values = spec.split(" ").map((v2) => gapValueToNumber(v2));
const gaps = this._gaps;
if (values[0] !== gaps.row) {
gaps.row = values[0];
this._triggerReflow();
}
if (values[1] === void 0) {
if (values[0] !== gaps.column) {
gaps.column = values[0];
this._triggerReflow();
}
} else {
if (values[1] !== gaps.column) {
gaps.column = values[1];
this._triggerReflow();
}
}
}
set padding(spec) {
const padding = this._padding;
const values = spec.split(" ").map((v2) => paddingValueToNumber(v2));
if (values.length === 1) {
padding.top = padding.right = padding.bottom = padding.left = values[0];
this._triggerReflow();
} else if (values.length === 2) {
padding.top = padding.bottom = values[0];
padding.right = padding.left = values[1];
this._triggerReflow();
} else if (values.length === 3) {
padding.top = values[0];
padding.right = padding.left = values[1];
padding.bottom = values[2];
this._triggerReflow();
} else if (values.length === 4) {
["top", "right", "bottom", "left"].forEach((side, idx) => padding[side] = values[idx]);
this._triggerReflow();
}
}
}
class GridBaseLayout extends SizeGapPaddingBaseLayout {
constructor() {
super(...arguments);
this._metrics = null;
this.flex = null;
this.justify = null;
}
_getDefaultConfig() {
return Object.assign({}, super._getDefaultConfig(), {
flex: false,
justify: "start"
});
}
set gap(spec) {
super._setGap(spec);
}
_updateLayout() {
const justify = this.justify;
const [padding1Start, padding1End] = this._padding1;
const [padding2Start, padding2End] = this._padding2;
["_gap1", "_gap2"].forEach((gap) => {
const gapValue = this[gap];
if (gapValue === Infinity && !["space-between", "space-around", "space-evenly"].includes(justify)) {
throw new Error(`grid layout: gap can only be set to 'auto' when justify is set to 'space-between', 'space-around' or 'space-evenly'`);
}
if (gapValue === Infinity && gap === "_gap2") {
throw new Error(`grid layout: ${gap2(this.direction)}-gap cannot be set to 'auto' when direction is set to ${this.direction}`);
}
});
const usePaddingAndGap2 = this.flex || ["start", "center", "end"].includes(justify);
const metrics = {
rolumns: -1,
itemSize1: -1,
itemSize2: -1,
// Infinity represents 'auto', so we set an invalid placeholder until we can calculate
gap1: this._gap1 === Infinity ? -1 : this._gap1,
gap2: usePaddingAndGap2 ? this._gap2 : 0,
// Infinity represents 'match-gap', so we set padding to match gap
padding1: {
start: padding1Start === Infinity ? this._gap1 : padding1Start,
end: padding1End === Infinity ? this._gap1 : padding1End
},
padding2: usePaddingAndGap2 ? {
start: padding2Start === Infinity ? this._gap2 : padding2Start,
end: padding2End === Infinity ? this._gap2 : padding2End
} : {
start: 0,
end: 0
},
positions: []
};
const availableSpace = this._viewDim2 - metrics.padding2.start - metrics.padding2.end;
if (availableSpace <= 0) {
metrics.rolumns = 0;
} else {
const gapSize = usePaddingAndGap2 ? metrics.gap2 : 0;
let rolumns = 0;
let spaceTaken = 0;
if (availableSpace >= this._idealSize2) {
rolumns = Math.floor((availableSpace - this._idealSize2) / (this._idealSize2 + gapSize)) + 1;
spaceTaken = rolumns * this._idealSize2 + (rolumns - 1) * gapSize;
}
if (this.flex) {
if ((availableSpace - spaceTaken) / (this._idealSize2 + gapSize) >= 0.5) {
rolumns = rolumns + 1;
}
metrics.rolumns = rolumns;
metrics.itemSize2 = Math.round((availableSpace - gapSize * (rolumns - 1)) / rolumns);
const preserve = this.flex === true ? "area" : this.flex.preserve;
switch (preserve) {
case "aspect-ratio":
metrics.itemSize1 = Math.round(this._idealSize1 / this._idealSize2 * metrics.itemSize2);
break;
case dim1(this.direction):
metrics.itemSize1 = Math.round(this._idealSize1);
break;
case "area":
default:
metrics.itemSize1 = Math.round(this._idealSize1 * this._idealSize2 / metrics.itemSize2);
}
} else {
metrics.itemSize1 = this._idealSize1;
metrics.itemSize2 = this._idealSize2;
metrics.rolumns = rolumns;
}
let pos;
if (usePaddingAndGap2) {
const spaceTaken2 = metrics.rolumns * metrics.itemSize2 + (metrics.rolumns - 1) * metrics.gap2;
pos = this.flex || justify === "start" ? metrics.padding2.start : justify === "end" ? this._viewDim2 - metrics.padding2.end - spaceTaken2 : Math.round(this._viewDim2 / 2 - spaceTaken2 / 2);
} else {
const spaceToDivide = availableSpace - metrics.rolumns * metrics.itemSize2;
if (justify === "space-between") {
metrics.gap2 = Math.round(spaceToDivide / (metrics.rolumns - 1));
pos = 0;
} else if (justify === "space-around") {
metrics.gap2 = Math.round(spaceToDivide / metrics.rolumns);
pos = Math.round(metrics.gap2 / 2);
} else {
metrics.gap2 = Math.round(spaceToDivide / (metrics.rolumns + 1));
pos = metrics.gap2;
}
if (this._gap1 === Infinity) {
metrics.gap1 = metrics.gap2;
if (padding1Start === Infinity) {
metrics.padding1.start = pos;
}
if (padding1End === Infinity) {
metrics.padding1.end = pos;
}
}
}
for (let i5 = 0; i5 < metrics.rolumns; i5++) {
metrics.positions.push(pos);
pos += metrics.itemSize2 + metrics.gap2;
}
}
this._metrics = metrics;
}
}
const grid = (config) => Object.assign({
type: GridLayout
}, config);
class GridLayout extends GridBaseLayout {
/**
* Returns the average size (precise or estimated) of an item in the scrolling direction,
* including any surrounding space.
*/
get _delta() {
return this._metrics.itemSize1 + this._metrics.gap1;
}
_getItemSize(_idx) {
return {
[this._sizeDim]: this._metrics.itemSize1,
[this._secondarySizeDim]: this._metrics.itemSize2
};
}
_getActiveItems() {
const metrics = this._metrics;
const { rolumns } = metrics;
if (rolumns === 0) {
this._first = -1;
this._last = -1;
this._physicalMin = 0;
this._physicalMax = 0;
} else {
const { padding1: padding12 } = metrics;
const min = Math.max(0, this._scrollPosition - this._overhang);
const max = Math.min(this._scrollSize, this._scrollPosition + this._viewDim1 + this._overhang);
const firstCow = Math.max(0, Math.floor((min - padding12.start) / this._delta));
const lastCow = Math.max(0, Math.ceil((max - padding12.start) / this._delta));
this._first = firstCow * rolumns;
this._last = Math.min(lastCow * rolumns - 1, this.items.length - 1);
this._physicalMin = padding12.start + this._delta * firstCow;
this._physicalMax = padding12.start + this._delta * lastCow;
}
}
_getItemPosition(idx) {
const { rolumns, padding1: padding12, positions, itemSize1, itemSize2 } = this._metrics;
return {
[this._positionDim]: padding12.start + Math.floor(idx / rolumns) * this._delta,
[this._secondaryPositionDim]: positions[idx % rolumns],
[dim1(this.direction)]: itemSize1,
[dim2(this.direction)]: itemSize2
};
}
_updateScrollSize() {
const { rolumns, gap1: gap12, padding1: padding12, itemSize1 } = this._metrics;
let size = 1;
if (rolumns > 0) {
const cows = Math.ceil(this.items.length / rolumns);
size = padding12.start + cows * itemSize1 + (cows - 1) * gap12 + padding12.end;
}
this._scrollSize = size;
}
}
const e = e$1(class extends i$3 {
constructor(t$12) {
if (super(t$12), t$12.type !== t.ATTRIBUTE || "class" !== t$12.name || t$12.strings?.length > 2) throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.");
}
render(t2) {
return " " + Object.keys(t2).filter(((s2) => t2[s2])).join(" ") + " ";
}
update(s2, [i5]) {
if (void 0 === this.st) {
this.st = /* @__PURE__ */ new Set(), void 0 !== s2.strings && (this.nt = new Set(s2.strings.join(" ").split(/\s/).filter(((t2) => "" !== t2))));
for (const t2 in i5) i5[t2] && !this.nt?.has(t2) && this.st.add(t2);
return this.render(i5);
}
const r2 = s2.element.classList;
for (const t2 of this.st) t2 in i5 || (r2.remove(t2), this.st.delete(t2));
for (const t2 in i5) {
const s3 = !!i5[t2];
s3 === this.st.has(t2) || this.nt?.has(t2) || (s3 ? (r2.add(t2), this.st.add(t2)) : (r2.remove(t2), this.st.delete(t2)));
}
return T;
}
});
const slugify = (value, delimiter = "_") => {
const a2 = "àáâäæãåāăąабçćčđďдèéêëēėęěеёэфğǵгḧхîïíīįìıİийкłлḿмñńǹňнôöòóœøōõőоṕпŕřрßśšşșсťțтûüùúūǘůűųувẃẍÿýыžźżз·";
const b2 = `aaaaaaaaaaabcccdddeeeeeeeeeeefggghhiiiiiiiiijkllmmnnnnnoooooooooopprrrsssssstttuuuuuuuuuuvwxyyyzzzz${delimiter}`;
const p2 = new RegExp(a2.split("").join("|"), "g");
const complex_cyrillic = {
ж: "zh",
х: "kh",
ц: "ts",
ч: "ch",
ш: "sh",
щ: "shch",
ю: "iu",
я: "ia"
};
let slugified;
if (value === "") {
slugified = "";
} else {
slugified = value.toString().toLowerCase().replace(p2, (c3) => b2.charAt(a2.indexOf(c3))).replace(/[а-я]/g, (c3) => complex_cyrillic[c3] || "").replace(/(\d),(?=\d)/g, "$1").replace(/[^a-z0-9]+/g, delimiter).replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1").replace(new RegExp(`^${delimiter}+`), "").replace(new RegExp(`${delimiter}+$`), "");
if (slugified === "") {
slugified = "unknown";
}
}
return slugified;
};
const browseLocalMediaPlayer = (hass, mediaContentId) => hass.callWS({
type: "media_source/browse_media",
media_content_id: mediaContentId
});
const MANUAL_MEDIA_SOURCE_PREFIX = "__MANUAL_ENTRY__";
const isManualMediaSourceContentId = (mediaContentId) => mediaContentId.startsWith(MANUAL_MEDIA_SOURCE_PREFIX);
const showAlertDialog = (_element, _params) => {
};
const haStyle = i$8`
.mdc-deprecated-list-item__graphic {
margin-inline-end: 16px;
margin-inline-start: 0;
}
`;
function __decorate(decorators, target, key, desc) {
var c3 = arguments.length, r2 = c3 < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d2;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r2 = Reflect.decorate(decorators, target, key, desc);
else for (var i5 = decorators.length - 1; i5 >= 0; i5--) if (d2 = decorators[i5]) r2 = (c3 < 3 ? d2(r2) : c3 > 3 ? d2(target, key, r2) : d2(target, key)) || r2;
return c3 > 3 && r2 && Object.defineProperty(target, key, r2), r2;
}
typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
var e2 = new Error(message);
return e2.name = "SuppressedError", e2.error = error, e2.suppressed = suppressed, e2;
};
const u = (e2, s2, t2) => {
const r2 = /* @__PURE__ */ new Map();
for (let l2 = s2; l2 <= t2; l2++) r2.set(e2[l2], l2);
return r2;
}, c2 = e$1(class extends i$3 {
constructor(e2) {
if (super(e2), e2.type !== t.CHILD) throw Error("repeat() can only be used in text expressions");
}
dt(e2, s2, t2) {
let r2;
void 0 === t2 ? t2 = s2 : void 0 !== s2 && (r2 = s2);
const l2 = [], o2 = [];
let i5 = 0;
for (const s3 of e2) l2[i5] = r2 ? r2(s3, i5) : i5, o2[i5] = t2(s3, i5), i5++;
return { values: o2, keys: l2 };
}
render(e2, s2, t2) {
return this.dt(e2, s2, t2).values;
}
update(s2, [t2, r2, c3]) {
const d2 = p(s2), { values: p$12, keys: a2 } = this.dt(t2, r2, c3);
if (!Array.isArray(d2)) return this.ut = a2, p$12;
const h2 = this.ut ??= [], v$12 = [];
let m2, y3, x2 = 0, j2 = d2.length - 1, k2 = 0, w = p$12.length - 1;
for (; x2 <= j2 && k2 <= w; ) if (null === d2[x2]) x2++;
else if (null === d2[j2]) j2--;
else if (h2[x2] === a2[k2]) v$12[k2] = v(d2[x2], p$12[k2]), x2++, k2++;
else if (h2[j2] === a2[w]) v$12[w] = v(d2[j2], p$12[w]), j2--, w--;
else if (h2[x2] === a2[w]) v$12[w] = v(d2[x2], p$12[w]), r$2(s2, v$12[w + 1], d2[x2]), x2++, w--;
else if (h2[j2] === a2[k2]) v$12[k2] = v(d2[j2], p$12[k2]), r$2(s2, d2[x2], d2[j2]), j2--, k2++;
else if (void 0 === m2 && (m2 = u(a2, k2, w), y3 = u(h2, x2, j2)), m2.has(h2[x2])) if (m2.has(h2[j2])) {
const e2 = y3.get(a2[k2]), t3 = void 0 !== e2 ? d2[e2] : null;
if (null === t3) {
const e3 = r$2(s2, d2[x2]);
v(e3, p$12[k2]), v$12[k2] = e3;
} else v$12[k2] = v(t3, p$12[k2]), r$2(s2, d2[x2], t3), d2[e2] = null;
k2++;
} else M2(d2[j2]), j2--;
else M2(d2[x2]), x2++;
for (; k2 <= w; ) {
const e2 = r$2(s2, v$12[w + 1]);
v(e2, p$12[k2]), v$12[k2++] = e2;
}
for (; x2 <= j2; ) {
const e2 = d2[x2++];
null !== e2 && M2(e2);
}
return this.ut = a2, m$1(s2, v$12), T;
}
});
const scriptRel = "modulepreload";
const assetsURL = function(dep) {
return "/" + dep;
};
const seen = {};
const __vitePreload = function preload(baseModule, deps, importerUrl) {
let promise = Promise.resolve();
if (deps && deps.length > 0) {
let allSettled2 = function(promises$2) {
return Promise.all(promises$2.map((p2) => Promise.resolve(p2).then((value$1) => ({
status: "fulfilled",
value: value$1
}), (reason) => ({
status: "rejected",
reason
}))));
};
var allSettled = allSettled2;
document.getElementsByTagName("link");
const cspNonceMeta = document.querySelector("meta[property=csp-nonce]");
const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
promise = allSettled2(deps.map((dep) => {
dep = assetsURL(dep);
if (dep in seen) return;
seen[dep] = true;
const isCss = dep.endsWith(".css");
const cssSelector = isCss ? '[rel="stylesheet"]' : "";
if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) return;
const link = document.createElement("link");
link.rel = isCss ? "stylesheet" : scriptRel;
if (!isCss) link.as = "script";
link.crossOrigin = "";
link.href = dep;
if (cspNonce) link.setAttribute("nonce", cspNonce);
document.head.appendChild(link);
if (isCss) return new Promise((res, rej) => {
link.addEventListener("load", res);
link.addEventListener("error", () => rej(/* @__PURE__ */ new Error(`Unable to preload CSS for ${dep}`)));
});
}));
}
function handlePreloadError(err$2) {
const e$12 = new Event("vite:preloadError", { cancelable: true });
e$12.payload = err$2;
window.dispatchEvent(e$12);
if (!e$12.defaultPrevented) throw err$2;
}
return promise.then((res) => {
for (const item of res || []) {
if (item.status !== "rejected") continue;
handlePreloadError(item.reason);
}
return baseModule().catch(handlePreloadError);
});
};
class RangeChangedEvent extends Event {
constructor(range) {
super(RangeChangedEvent.eventName, { bubbles: false });
this.first = range.first;
this.last = range.last;
}
}
RangeChangedEvent.eventName = "rangeChanged";
class VisibilityChangedEvent extends Event {
constructor(range) {
super(VisibilityChangedEvent.eventName, { bubbles: false });
this.first = range.first;
this.last = range.last;
}
}
VisibilityChangedEvent.eventName = "visibilityChanged";
class UnpinnedEvent extends Event {
constructor() {
super(UnpinnedEvent.eventName, { bubbles: false });
}
}
UnpinnedEvent.eventName = "unpinned";
class ScrollerShim {
constructor(element) {
this._element = null;
const node = element ?? window;
this._node = node;
if (element) {
this._element = element;
}
}
get element() {
return this._element || document.scrollingElement || document.documentElement;
}
get scrollTop() {
return this.element.scrollTop || window.scrollY;
}
get scrollLeft() {
return this.element.scrollLeft || window.scrollX;
}
get scrollHeight() {
return this.element.scrollHeight;
}
get scrollWidth() {
return this.element.scrollWidth;
}
get viewportHeight() {
return this._element ? this._element.getBoundingClientRect().height : window.innerHeight;
}
get viewportWidth() {
return this._element ? this._element.getBoundingClientRect().width : window.innerWidth;
}
get maxScrollTop() {
return this.scrollHeight - this.viewportHeight;
}
get maxScrollLeft() {
return this.scrollWidth - this.viewportWidth;
}
}
class ScrollerController extends ScrollerShim {
constructor(client, element) {
super(element);
this._clients = /* @__PURE__ */ new Set();
this._retarget = null;
this._end = null;
this.__destination = null;
this.correctingScrollError = false;
this._checkForArrival = this._checkForArrival.bind(this);
this._updateManagedScrollTo = this._updateManagedScrollTo.bind(this);
this.scrollTo = this.scrollTo.bind(this);
this.scrollBy = this.scrollBy.bind(this);
const node = this._node;
this._originalScrollTo = node.scrollTo;
this._originalScrollBy = node.scrollBy;
this._originalScroll = node.scroll;
this._attach(client);
}
get _destination() {
return this.__destination;
}
get scrolling() {
return this._destination !== null;
}
scrollTo(p1, p2) {
const options2 = typeof p1 === "number" && typeof p2 === "number" ? { left: p1, top: p2 } : p1;
this._scrollTo(options2);
}
scrollBy(p1, p2) {
const options2 = typeof p1 === "number" && typeof p2 === "number" ? { left: p1, top: p2 } : p1;
if (options2.top !== void 0) {
options2.top += this.scrollTop;
}
if (options2.left !== void 0) {
options2.left += this.scrollLeft;
}
this._scrollTo(options2);
}
_nativeScrollTo(options2) {
this._originalScrollTo.bind(this._element || window)(options2);
}
_scrollTo(options2, retarget = null, end = null) {
if (this._end !== null) {
this._end();
}
if (options2.behavior === "smooth") {
this._setDestination(options2);
this._retarget = retarget;
this._end = end;
} else {
this._resetScrollState();
}
this._nativeScrollTo(options2);
}
_setDestination(options2) {
let { top, left } = options2;
top = top === void 0 ? void 0 : Math.max(0, Math.min(top, this.maxScrollTop));
left = left === void 0 ? void 0 : Math.max(0, Math.min(left, this.maxScrollLeft));
if (this._destination !== null && left === this._destination.left && top === this._destination.top) {
return false;
}
this.__destination = { top, left, behavior: "smooth" };
return true;
}
_resetScrollState() {
this.__destination = null;
this._retarget = null;
this._end = null;
}
_updateManagedScrollTo(coordinates) {
if (this._destination) {
if (this._setDestination(coordinates)) {
this._nativeScrollTo(this._destination);
}
}
}
managedScrollTo(options2, retarget, end) {
this._scrollTo(options2, retarget, end);
return this._updateManagedScrollTo;
}
correctScrollError(coordinates) {
this.correctingScrollError = true;
requestAnimationFrame(() => requestAnimationFrame(() => this.correctingScrollError = false));
this._nativeScrollTo(coordinates);
if (this._retarget) {
this._setDestination(this._retarget());
}
if (this._destination) {
this._nativeScrollTo(this._destination);
}
}
_checkForArrival() {
if (this._destination !== null) {
const { scrollTop, scrollLeft } = this;
let { top, left } = this._destination;
top = Math.min(top || 0, this.maxScrollTop);
left = Math.min(left || 0, this.maxScrollLeft);
const topDiff = Math.abs(top - scrollTop);
const leftDiff = Math.abs(left - scrollLeft);
if (topDiff < 1 && leftDiff < 1) {
if (this._end) {
this._end();
}
this._resetScrollState();
}
}
}
detach(client) {
this._clients.delete(client);
if (this._clients.size === 0) {
this._node.scrollTo = this._originalScrollTo;
this._node.scrollBy = this._originalScrollBy;
this._node.scroll = this._originalScroll;
this._node.removeEventListener("scroll", this._checkForArrival);
}
return null;
}
_attach(client) {
this._clients.add(client);
if (this._clients.size === 1) {
this._node.scrollTo = this.scrollTo;
this._node.scrollBy = this.scrollBy;
this._node.scroll = this.scrollTo;
this._node.addEventListener("scroll", this._checkForArrival);
}
}
}
let _ResizeObserver = typeof window !== "undefined" ? window.ResizeObserver : void 0;
const virtualizerRef = /* @__PURE__ */ Symbol("virtualizerRef");
const SIZER_ATTRIBUTE = "virtualizer-sizer";
let DefaultLayoutConstructor;
class Virtualizer {
constructor(config) {
this._benchmarkStart = null;
this._layout = null;
this._clippingAncestors = [];
this._scrollSize = null;
this._scrollError = null;
this._childrenPos = null;
this._childMeasurements = null;
this._toBeMeasured = /* @__PURE__ */ new Map();
this._rangeChanged = true;
this._itemsChanged = true;
this._visibilityChanged = true;
this._scrollerController = null;
this._isScroller = false;
this._sizer = null;
this._hostElementRO = null;
this._childrenRO = null;
this._mutationObserver = null;
this._scrollEventListeners = [];
this._scrollEventListenerOptions = {
passive: true
};
this._loadListener = this._childLoaded.bind(this);
this._scrollIntoViewTarget = null;
this._updateScrollIntoViewCoordinates = null;
this._items = [];
this._first = -1;
this._last = -1;
this._firstVisible = -1;
this._lastVisible = -1;
this._scheduled = /* @__PURE__ */ new WeakSet();
this._measureCallback = null;
this._measureChildOverride = null;
this._layoutCompletePromise = null;
this._layoutCompleteResolver = null;
this._layoutCompleteRejecter = null;
this._pendingLayoutComplete = null;
this._layoutInitialized = null;
this._connected = false;
if (!config) {
throw new Error("Virtualizer constructor requires a configuration object");
}
if (config.hostElement) {
this._init(config);
} else {
throw new Error('Virtualizer configuration requires the "hostElement" property');
}
}
set items(items) {
if (Array.isArray(items) && items !== this._items) {
this._itemsChanged = true;
this._items = items;
this._schedule(this._updateLayout);
}
}
_init(config) {
this._isScroller = !!config.scroller;
this._initHostElement(config);
const layoutConfig = config.layout || {};
this._layoutInitialized = this._initLayout(layoutConfig);
}
_initObservers() {
this._mutationObserver = new MutationObserver(this._finishDOMUpdate.bind(this));
this._hostElementRO = new _ResizeObserver(() => this._hostElementSizeChanged());
this._childrenRO = new _ResizeObserver(this._childrenSizeChanged.bind(this));
}
_initHostElement(config) {
const hostElement = this._hostElement = config.hostElement;
this._applyVirtualizerStyles();
hostElement[virtualizerRef] = this;
}
connected() {
this._initObservers();
const includeSelf = this._isScroller;
this._clippingAncestors = getClippingAncestors(this._hostElement, includeSelf);
this._scrollerController = new ScrollerController(this, this._clippingAncestors[0]);
this._schedule(this._updateLayout);
this._observeAndListen();
this._connected = true;
}
_observeAndListen() {
this._mutationObserver.observe(this._hostElement, { childList: true });
this._hostElementRO.observe(this._hostElement);
this._scrollEventListeners.push(window);
window.addEventListener("scroll", this, this._scrollEventListenerOptions);
this._clippingAncestors.forEach((ancestor) => {
ancestor.addEventListener("scroll", this, this._scrollEventListenerOptions);
this._scrollEventListeners.push(ancestor);
this._hostElementRO.observe(ancestor);
});
this._hostElementRO.observe(this._scrollerController.element);
this._children.forEach((child) => this._childrenRO.observe(child));
this._scrollEventListeners.forEach((target) => target.addEventListener("scroll", this, this._scrollEventListenerOptions));
}
disconnected() {
this._scrollEventListeners.forEach((target) => target.removeEventListener("scroll", this, this._scrollEventListenerOptions));
this._scrollEventListeners = [];
this._clippingAncestors = [];
this._scrollerController?.detach(this);
this._scrollerController = null;
this._mutationObserver?.disconnect();
this._mutationObserver = null;
this._hostElementRO?.disconnect();
this._hostElementRO = null;
this._childrenRO?.disconnect();
this._childrenRO = null;
this._rejectLayoutCompletePromise("disconnected");
this._connected = false;
}
_applyVirtualizerStyles() {
const hostElement = this._hostElement;
const style = hostElement.style;
style.display = style.display || "block";
style.position = style.position || "relative";
style.contain = style.contain || "size layout";
if (this._isScroller) {
style.overflow = style.overflow || "auto";
style.minHeight = style.minHeight || "150px";
}
}
_getSizer() {
const hostElement = this._hostElement;
if (!this._sizer) {
let sizer = hostElement.querySelector(`[${SIZER_ATTRIBUTE}]`);
if (!sizer) {
sizer = document.createElement("div");
sizer.setAttribute(SIZER_ATTRIBUTE, "");
hostElement.appendChild(sizer);
}
Object.assign(sizer.style, {
position: "absolute",
margin: "-2px 0 0 0",
padding: 0,
visibility: "hidden",
fontSize: "2px"
});
sizer.textContent = " ";
sizer.setAttribute(SIZER_ATTRIBUTE, "");
this._sizer = sizer;
}
return this._sizer;
}
async updateLayoutConfig(layoutConfig) {
await this._layoutInitialized;
const Ctor = layoutConfig.type || // The new config is compatible with the current layout,
// so we update the config and return true to indicate
// a successful update
DefaultLayoutConstructor;
if (typeof Ctor === "function" && this._layout instanceof Ctor) {
const config = { ...layoutConfig };
delete config.type;
this._layout.config = config;
return true;
}
return false;
}
async _initLayout(layoutConfig) {
let config;
let Ctor;
if (typeof layoutConfig.type === "function") {
Ctor = layoutConfig.type;
const copy = { ...layoutConfig };
delete copy.type;
config = copy;
} else {
config = layoutConfig;
}
if (Ctor === void 0) {
DefaultLayoutConstructor = Ctor = (await __vitePreload(() => Promise.resolve().then(() => flow$1), true ? void 0 : void 0)).FlowLayout;
}
this._layout = new Ctor((message) => this._handleLayoutMessage(message), config);
if (this._layout.measureChildren && typeof this._layout.updateItemSizes === "function") {
if (typeof this._layout.measureChildren === "function") {
this._measureChildOverride = this._layout.measureChildren;
}
this._measureCallback = this._layout.updateItemSizes.bind(this._layout);
}
if (this._layout.listenForChildLoadEvents) {
this._hostElement.addEventListener("load", this._loadListener, true);
}
this._schedule(this._updateLayout);
}
// TODO (graynorton): Rework benchmarking so that it has no API and
// instead is always on except in production builds
startBenchmarking() {
if (this._benchmarkStart === null) {
this._benchmarkStart = window.performance.now();
}
}
stopBenchmarking() {
if (this._benchmarkStart !== null) {
const now = window.performance.now();
const timeElapsed = now - this._benchmarkStart;
const entries = performance.getEntriesByName("uv-virtualizing", "measure");
const virtualizationTime = entries.filter((e2) => e2.startTime >= this._benchmarkStart && e2.startTime < now).reduce((t2, m2) => t2 + m2.duration, 0);
this._benchmarkStart = null;
return { timeElapsed, virtualizationTime };
}
return null;
}
_measureChildren() {
const mm = {};
const children = this._children;
const fn = this._measureChildOverride || this._measureChild;
for (let i5 = 0; i5 < children.length; i5++) {
const child = children[i5];
const idx = this._first + i5;
if (this._itemsChanged || this._toBeMeasured.has(child)) {
mm[idx] = fn.call(this, child, this._items[idx]);
}
}
this._childMeasurements = mm;
this._schedule(this._updateLayout);
this._toBeMeasured.clear();
}
/**
* Returns the width, height, and margins of the given child.
*/
_measureChild(element) {
const { width, height } = element.getBoundingClientRect();
return Object.assign({ width, height }, getMargins(element));
}
async _schedule(method) {
if (!this._scheduled.has(method)) {
this._scheduled.add(method);
await Promise.resolve();
this._scheduled.delete(method);
method.call(this);
}
}
async _updateDOM(state) {
this._scrollSize = state.scrollSize;
this._adjustRange(state.range);
this._childrenPos = state.childPositions;
this._scrollError = state.scrollError || null;
const { _rangeChanged, _itemsChanged } = this;
if (this._visibilityChanged) {
this._notifyVisibility();
this._visibilityChanged = false;
}
if (_rangeChanged || _itemsChanged) {
this._notifyRange();
this._rangeChanged = false;
}
this._finishDOMUpdate();
}
_finishDOMUpdate() {
if (this._connected) {
this._children.forEach((child) => this._childrenRO.observe(child));
this._checkScrollIntoViewTarget(this._childrenPos);
this._positionChildren(this._childrenPos);
this._sizeHostElement(this._scrollSize);
this._correctScrollError();
if (this._benchmarkStart && "mark" in window.performance) {
window.performance.mark("uv-end");
}
}
}
_updateLayout() {
if (this._layout && this._connected) {
this._layout.items = this._items;
this._updateView();
if (this._childMeasurements !== null) {
if (this._measureCallback) {
this._measureCallback(this._childMeasurements);
}
this._childMeasurements = null;
}
this._layout.reflowIfNeeded();
if (this._benchmarkStart && "mark" in window.performance) {
window.performance.mark("uv-end");
}
}
}
_handleScrollEvent() {
if (this._benchmarkStart && "mark" in window.performance) {
try {
window.performance.measure("uv-virtualizing", "uv-start", "uv-end");
} catch (e2) {
console.warn("Error measuring performance data: ", e2);
}
window.performance.mark("uv-start");
}
if (this._scrollerController.correctingScrollError === false) {
this._layout?.unpin();
}
this._schedule(this._updateLayout);
}
handleEvent(event) {
switch (event.type) {
case "scroll":
if (event.currentTarget === window || this._clippingAncestors.includes(event.currentTarget)) {
this._handleScrollEvent();
}
break;
default:
console.warn("event not handled", event);
}
}
_handleLayoutMessage(message) {
if (message.type === "stateChanged") {
this._updateDOM(message);
} else if (message.type === "visibilityChanged") {
this._firstVisible = message.firstVisible;
this._lastVisible = message.lastVisible;
this._notifyVisibility();
} else if (message.type === "unpinned") {
this._hostElement.dispatchEvent(new UnpinnedEvent());
}
}
get _children() {
const arr = [];
let next = this._hostElement.firstElementChild;
while (next) {
if (!next.hasAttribute(SIZER_ATTRIBUTE)) {
arr.push(next);
}
next = next.nextElementSibling;
}
return arr;
}
_updateView() {
const hostElement = this._hostElement;
const scrollingElement = this._scrollerController?.element;
const layout = this._layout;
if (hostElement && scrollingElement && layout) {
let top, left, bottom, right;
const hostElementBounds = hostElement.getBoundingClientRect();
top = 0;
left = 0;
bottom = window.innerHeight;
right = window.innerWidth;
const ancestorBounds = this._clippingAncestors.map((ancestor) => ancestor.getBoundingClientRect());
ancestorBounds.unshift(hostElementBounds);
for (const bounds of ancestorBounds) {
top = Math.max(top, bounds.top);
left = Math.max(left, bounds.left);
bottom = Math.min(bottom, bounds.bottom);
right = Math.min(right, bounds.right);
}
const scrollingElementBounds = scrollingElement.getBoundingClientRect();
const offsetWithinScroller = {
left: hostElementBounds.left - scrollingElementBounds.left,
top: hostElementBounds.top - scrollingElementBounds.top
};
const totalScrollSize = {
width: scrollingElement.scrollWidth,
height: scrollingElement.scrollHeight
};
const scrollTop = top - hostElementBounds.top + hostElement.scrollTop;
const scrollLeft = left - hostElementBounds.left + hostElement.scrollLeft;
const height = Math.max(0, bottom - top);
const width = Math.max(0, right - left);
layout.viewportSize = { width, height };
layout.viewportScroll = { top: scrollTop, left: scrollLeft };
layout.totalScrollSize = totalScrollSize;
layout.offsetWithinScroller = offsetWithinScroller;
}
}
/**
* Styles the host element so that its size reflects the
* total size of all items.
*/
_sizeHostElement(size) {
const max = 82e5;
const h2 = size && size.width !== null ? Math.min(max, size.width) : 0;
const v2 = size && size.height !== null ? Math.min(max, size.height) : 0;
if (this._isScroller) {
this._getSizer().style.transform = `translate(${h2}px, ${v2}px)`;
} else {
const style = this._hostElement.style;
style.minWidth = h2 ? `${h2}px` : "100%";
style.minHeight = v2 ? `${v2}px` : "100%";
}
}
/**
* Sets the top and left transform style of the children from the values in
* pos.
*/
_positionChildren(pos) {
if (pos) {
pos.forEach(({ top, left, width, height, xOffset, yOffset }, index) => {
const child = this._children[index - this._first];
if (child) {
child.style.position = "absolute";
child.style.boxSizing = "border-box";
child.style.transform = `translate(${left}px, ${top}px)`;
if (width !== void 0) {
child.style.width = width + "px";
}
if (height !== void 0) {
child.style.height = height + "px";
}
child.style.left = xOffset === void 0 ? null : xOffset + "px";
child.style.top = yOffset === void 0 ? null : yOffset + "px";
}
});
}
}
async _adjustRange(range) {
const { _first, _last, _firstVisible, _lastVisible } = this;
this._first = range.first;
this._last = range.last;
this._firstVisible = range.firstVisible;
this._lastVisible = range.lastVisible;
this._rangeChanged = this._rangeChanged || this._first !== _first || this._last !== _last;
this._visibilityChanged = this._visibilityChanged || this._firstVisible !== _firstVisible || this._lastVisible !== _lastVisible;
}
_correctScrollError() {
if (this._scrollError) {
const { scrollTop, scrollLeft } = this._scrollerController;
const { top, left } = this._scrollError;
this._scrollError = null;
this._scrollerController.correctScrollError({
top: scrollTop - top,
left: scrollLeft - left
});
}
}
element(index) {
if (index === Infinity) {
index = this._items.length - 1;
}
return this._items?.[index] === void 0 ? void 0 : {
scrollIntoView: (options2 = {}) => this._scrollElementIntoView({ ...options2, index })
};
}
_scrollElementIntoView(options2) {
if (options2.index >= this._first && options2.index <= this._last) {
this._children[options2.index - this._first].scrollIntoView(options2);
} else {
options2.index = Math.min(options2.index, this._items.length - 1);
if (options2.behavior === "smooth") {
const coordinates = this._layout.getScrollIntoViewCoordinates(options2);
const { behavior } = options2;
this._updateScrollIntoViewCoordinates = this._scrollerController.managedScrollTo(Object.assign(coordinates, { behavior }), () => this._layout.getScrollIntoViewCoordinates(options2), () => this._scrollIntoViewTarget = null);
this._scrollIntoViewTarget = options2;
} else {
this._layout.pin = options2;
}
}
}
/**
* If we are smoothly scrolling to an element and the target element
* is in the DOM, we update our target coordinates as needed
*/
_checkScrollIntoViewTarget(pos) {
const { index } = this._scrollIntoViewTarget || {};
if (index && pos?.has(index)) {
this._updateScrollIntoViewCoordinates(this._layout.getScrollIntoViewCoordinates(this._scrollIntoViewTarget));
}
}
/**
* Emits a rangechange event with the current first, last, firstVisible, and
* lastVisible.
*/
_notifyRange() {
this._hostElement.dispatchEvent(new RangeChangedEvent({ first: this._first, last: this._last }));
}
_notifyVisibility() {
this._hostElement.dispatchEvent(new VisibilityChangedEvent({
first: this._firstVisible,
last: this._lastVisible
}));
}
get layoutComplete() {
if (!this._layoutCompletePromise) {
this._layoutCompletePromise = new Promise((resolve, reject) => {
this._layoutCompleteResolver = resolve;
this._layoutCompleteRejecter = reject;
});
}
return this._layoutCompletePromise;
}
_rejectLayoutCompletePromise(reason) {
if (this._layoutCompleteRejecter !== null) {
this._layoutCompleteRejecter(reason);
}
this._resetLayoutCompleteState();
}
_scheduleLayoutComplete() {
if (this._layoutCompletePromise && this._pendingLayoutComplete === null) {
this._pendingLayoutComplete = requestAnimationFrame(() => requestAnimationFrame(() => this._resolveLayoutCompletePromise()));
}
}
_resolveLayoutCompletePromise() {
if (this._layoutCompleteResolver !== null) {
this._layoutCompleteResolver();
}
this._resetLayoutCompleteState();
}
_resetLayoutCompleteState() {
this._layoutCompletePromise = null;
this._layoutCompleteResolver = null;
this._layoutCompleteRejecter = null;
this._pendingLayoutComplete = null;
}
/**
* Render and update the view at the next opportunity with the given
* hostElement size.
*/
_hostElementSizeChanged() {
this._schedule(this._updateLayout);
}
// TODO (graynorton): Rethink how this works. Probably child loading is too specific
// to have dedicated support for; might want some more generic lifecycle hooks for
// layouts to use. Possibly handle measurement this way, too, or maybe that remains
// a first-class feature?
_childLoaded() {
}
// This is the callback for the ResizeObserver that watches the
// virtualizer's children. We land here at the end of every virtualizer
// update cycle that results in changes to physical items, and we also
// end up here if one or more children change size independently of
// the virtualizer update cycle.
_childrenSizeChanged(changes) {
if (this._layout?.measureChildren) {
for (const change of changes) {
this._toBeMeasured.set(change.target, change.contentRect);
}
this._measureChildren();
}
this._scheduleLayoutComplete();
this._itemsChanged = false;
this._rangeChanged = false;
}
}
function getMargins(el) {
const style = window.getComputedStyle(el);
return {
marginTop: getMarginValue(style.marginTop),
marginRight: getMarginValue(style.marginRight),
marginBottom: getMarginValue(style.marginBottom),
marginLeft: getMarginValue(style.marginLeft)
};
}
function getMarginValue(value) {
const float = value ? parseFloat(value) : NaN;
return Number.isNaN(float) ? 0 : float;
}
function getParentElement(el) {
if (el.assignedSlot !== null) {
return el.assignedSlot;
}
if (el.parentElement !== null) {
return el.parentElement;
}
const parentNode = el.parentNode;
if (parentNode && parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return parentNode.host || null;
}
return null;
}
function getElementAncestors(el, includeSelf = false) {
const ancestors = [];
let parent = includeSelf ? el : getParentElement(el);
while (parent !== null) {
ancestors.push(parent);
parent = getParentElement(parent);
}
return ancestors;
}
function getClippingAncestors(el, includeSelf = false) {
let foundFixed = false;
return getElementAncestors(el, includeSelf).filter((a2) => {
if (foundFixed) {
return false;
}
const style = getComputedStyle(a2);
foundFixed = style.position === "fixed";
return style.overflow !== "visible";
});
}
const defaultKeyFunction = (item) => item;
const defaultRenderItem = (item, idx) => x`${idx}: ${JSON.stringify(item, null, 2)}`;
class VirtualizeDirective extends f {
constructor(part) {
super(part);
this._virtualizer = null;
this._first = 0;
this._last = -1;
this._renderItem = (item, idx) => defaultRenderItem(item, idx + this._first);
this._keyFunction = (item, idx) => defaultKeyFunction(item, idx + this._first);
this._items = [];
if (part.type !== t.CHILD) {
throw new Error("The virtualize directive can only be used in child expressions");
}
}
render(config) {
if (config) {
this._setFunctions(config);
}
const itemsToRender = [];
if (this._first >= 0 && this._last >= this._first) {
for (let i5 = this._first; i5 <= this._last; i5++) {
itemsToRender.push(this._items[i5]);
}
}
return c2(itemsToRender, this._keyFunction, this._renderItem);
}
update(part, [config]) {
this._setFunctions(config);
const itemsChanged = this._items !== config.items;
this._items = config.items || [];
if (this._virtualizer) {
this._updateVirtualizerConfig(part, config);
} else {
this._initialize(part, config);
}
return itemsChanged ? T : this.render();
}
async _updateVirtualizerConfig(part, config) {
const compatible = await this._virtualizer.updateLayoutConfig(config.layout || {});
if (!compatible) {
const hostElement = part.parentNode;
this._makeVirtualizer(hostElement, config);
}
this._virtualizer.items = this._items;
}
_setFunctions(config) {
const { renderItem, keyFunction } = config;
if (renderItem) {
this._renderItem = (item, idx) => renderItem(item, idx + this._first);
}
if (keyFunction) {
this._keyFunction = (item, idx) => keyFunction(item, idx + this._first);
}
}
_makeVirtualizer(hostElement, config) {
if (this._virtualizer) {
this._virtualizer.disconnected();
}
const { layout, scroller, items } = config;
this._virtualizer = new Virtualizer({ hostElement, layout, scroller });
this._virtualizer.items = items;
this._virtualizer.connected();
}
_initialize(part, config) {
const hostElement = part.parentNode;
if (hostElement && hostElement.nodeType === 1) {
hostElement.addEventListener("rangeChanged", (e2) => {
this._first = e2.first;
this._last = e2.last;
this.setValue(this.render());
});
this._makeVirtualizer(hostElement, config);
}
}
disconnected() {
this._virtualizer?.disconnected();
}
reconnected() {
this._virtualizer?.connected();
}
}
const virtualize = e$1(VirtualizeDirective);
class LitVirtualizer extends i$5 {
constructor() {
super(...arguments);
this.items = [];
this.renderItem = defaultRenderItem;
this.keyFunction = defaultKeyFunction;
this.layout = {};
this.scroller = false;
}
createRenderRoot() {
return this;
}
render() {
const { items, renderItem, keyFunction, layout, scroller } = this;
return x`${virtualize({
items,
renderItem,
keyFunction,
layout,
scroller
})}`;
}
element(index) {
return this[virtualizerRef]?.element(index);
}
get layoutComplete() {
return this[virtualizerRef]?.layoutComplete;
}
/**
* This scrollToIndex() shim is here to provide backwards compatibility with other 0.x versions of
* lit-virtualizer. It is deprecated and will likely be removed in the 1.0.0 release.
*/
scrollToIndex(index, position = "start") {
this.element(index)?.scrollIntoView({ block: position });
}
}
__decorate([
n$4({ attribute: false })
], LitVirtualizer.prototype, "items", void 0);
__decorate([
n$4()
], LitVirtualizer.prototype, "renderItem", void 0);
__decorate([
n$4()
], LitVirtualizer.prototype, "keyFunction", void 0);
__decorate([
n$4({ attribute: false })
], LitVirtualizer.prototype, "layout", void 0);
__decorate([
n$4({ reflect: true, type: Boolean })
], LitVirtualizer.prototype, "scroller", void 0);
if (!customElements.get("lit-virtualizer")) {
customElements.define("lit-virtualizer", LitVirtualizer);
}
const loadVirtualizer = async () => {
};
const brandsUrl = (options2) => `https://brands.home-assistant.io/_/${options2.domain}/${options2.darkOptimized ? "dark_" : ""}${options2.type}.png`;
const isBrandUrl = (url) => {
return url?.startsWith("https://brands.home-assistant.io/") ?? false;
};
const extractDomainFromBrandUrl = (url) => {
const match = url.match(/brands\.home-assistant\.io\/[^/]+\/([^/]+)\//);
return match?.[1] ?? "";
};
const documentationUrl = (_hass, path) => {
return `https://www.home-assistant.io${path}`;
};
var __defProp$l = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass$l = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp$l(target, key, result);
return result;
};
const MANUAL_ITEM = {
can_expand: true,
can_play: false,
can_search: false,
children_media_class: "",
media_class: "app",
media_content_id: MANUAL_MEDIA_SOURCE_PREFIX,
media_content_type: "",
iconPath: mdiKeyboard,
title: "Manual entry"
};
let HaMediaPlayerBrowse = class extends i$5 {
constructor() {
super(...arguments);
this.action = "play";
this.preferredLayout = "auto";
this.dialog = false;
this.navigateIds = [];
this.hideContentType = false;
this.narrow = false;
this.scrolled = false;
this._observed = false;
this._headerOffsetHeight = 0;
this._renderGridItem = (child) => {
const backgroundImage = child.thumbnail ? this._getThumbnailURLorBase64(child.thumbnail).then((value) => `url(${value})`) : "none";
return x`
${child.thumbnail ? x`
` : x`
`}
${child.can_play ? x`
` : ""}
${child.title}
${child.title}
`;
};
this._renderListItem = (child) => {
const currentItem = this._currentItem;
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
const backgroundImage = mediaClass.show_list_images && child.thumbnail ? this._getThumbnailURLorBase64(child.thumbnail).then((value) => `url(${value})`) : "none";
return x`
${backgroundImage === "none" && !child.can_play ? x`` : x`
${child.can_play ? x`` : E}
`}
${child.title}
`;
};
this._actionClicked = (ev) => {
ev.stopPropagation();
const item = ev.currentTarget.item;
this._runAction(item);
};
this._childClicked = async (ev) => {
const target = ev.currentTarget;
const item = target.item;
if (!item) {
return;
}
if (!item.can_expand) {
this._runAction(item);
return;
}
fireEvent(this, "media-browsed", {
ids: [...this.navigateIds, item]
});
};
}
connectedCallback() {
super.connectedCallback();
this.updateComplete.then(() => this._attachResizeObserver());
}
getPlayableChildren() {
if (!this._currentItem?.children) {
return [];
}
return this._currentItem.children.filter((child) => child.can_play);
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
}
async refresh() {
const currentId = this.navigateIds[this.navigateIds.length - 1];
try {
this._currentItem = await this._fetchData(
this.entityId,
currentId.media_content_id,
currentId.media_content_type
);
fireEvent(this, "media-browsed", {
ids: this.navigateIds,
current: this._currentItem
});
} catch (err) {
this._setError(err);
}
}
play() {
if (this._currentItem?.can_play) {
this._runAction(this._currentItem);
}
}
willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
loadVirtualizer();
}
if (changedProps.has("entityId")) {
this._setError(void 0);
} else if (!changedProps.has("navigateIds")) {
return;
}
this._setError(void 0);
const oldNavigateIds = changedProps.get("navigateIds");
const navigateIds = this.navigateIds;
this._content?.scrollTo(0, 0);
this.scrolled = false;
const oldCurrentItem = this._currentItem;
const oldParentItem = this._parentItem;
this._currentItem = void 0;
this._parentItem = void 0;
const currentId = navigateIds[navigateIds.length - 1];
const parentId = navigateIds.length > 1 ? navigateIds[navigateIds.length - 2] : void 0;
let currentProm;
let parentProm;
if (!changedProps.has("entityId")) {
if (
// Check if we navigated to a child
oldNavigateIds && navigateIds.length === oldNavigateIds.length + 1 && oldNavigateIds.every((oldVal, idx) => {
const curVal = navigateIds[idx];
return curVal.media_content_id === oldVal.media_content_id && curVal.media_content_type === oldVal.media_content_type;
})
) {
parentProm = Promise.resolve(oldCurrentItem);
} else if (
// Check if we navigated to a parent
oldNavigateIds && navigateIds.length === oldNavigateIds.length - 1 && navigateIds.every((curVal, idx) => {
const oldVal = oldNavigateIds[idx];
return curVal.media_content_id === oldVal.media_content_id && curVal.media_content_type === oldVal.media_content_type;
})
) {
currentProm = Promise.resolve(oldParentItem);
}
}
if (currentId.media_content_id && isManualMediaSourceContentId(currentId.media_content_id)) {
this._currentItem = MANUAL_ITEM;
fireEvent(this, "media-browsed", {
ids: navigateIds,
current: this._currentItem
});
} else {
if (!currentProm) {
currentProm = this._fetchData(this.entityId, currentId.media_content_id, currentId.media_content_type);
}
currentProm.then(
(item) => {
this._currentItem = item;
fireEvent(this, "media-browsed", {
ids: navigateIds,
current: item
});
},
(err) => {
const isNewEntityWithSamePath = oldNavigateIds && changedProps.has("entityId") && navigateIds.length === oldNavigateIds.length && oldNavigateIds.every(
(oldItem, idx) => navigateIds[idx].media_content_id === oldItem.media_content_id && navigateIds[idx].media_content_type === oldItem.media_content_type
);
if (isNewEntityWithSamePath) {
fireEvent(this, "media-browsed", {
ids: [{ media_content_id: void 0, media_content_type: void 0 }],
replace: true
});
} else if (err.code === "entity_not_found" && this.entityId && isUnavailableState(this.hass.states[this.entityId]?.state)) {
this._setError({
message: this.hass.localize(`ui.components.media-browser.media_player_unavailable`),
code: "entity_not_found"
});
} else {
this._setError(err);
}
}
);
}
if (!parentProm && parentId !== void 0) {
parentProm = this._fetchData(this.entityId, parentId.media_content_id, parentId.media_content_type);
}
if (parentProm) {
parentProm.then((parent) => {
this._parentItem = parent;
});
}
}
shouldUpdate(changedProps) {
if (changedProps.size > 1 || !changedProps.has("hass")) {
return true;
}
const oldHass = changedProps.get("hass");
return oldHass === void 0 || oldHass.localize !== this.hass.localize;
}
firstUpdated() {
this._measureCard();
this._attachResizeObserver();
}
updated(changedProps) {
super.updated(changedProps);
if (changedProps.has("_scrolled")) {
this._animateHeaderHeight();
} else if (changedProps.has("_currentItem")) {
this._setHeaderHeight();
if (this._observed) {
return;
}
const virtualizer = this._virtualizer?._virtualizer;
if (virtualizer) {
this._observed = true;
setTimeout(() => virtualizer._observeMutations(), 0);
}
}
}
render() {
if (this._error) {
return x`
${this._renderError(this._error)}
`;
}
if (!this._currentItem) {
return x``;
}
const currentItem = this._currentItem;
const subtitle = this.hass.localize(`ui.components.media-browser.class.${currentItem.media_class}`);
let children = filterOutIgnoredMediaSources(currentItem.children || []);
const canPlayChildren = /* @__PURE__ */ new Set();
if (this.accept && children.length > 0) {
let checks = [];
for (const type of this.accept) {
if (type.endsWith("/*")) {
const baseType = type.slice(0, -1);
checks.push((t2) => t2.startsWith(baseType));
} else if (type === "*") {
checks = [() => true];
break;
} else {
checks.push((t2) => t2 === type);
}
}
children = children.filter((child) => {
const contentType = child.media_content_type.toLowerCase();
const canPlay = child.media_content_type && checks.some((check) => check(contentType));
if (canPlay) {
canPlayChildren.add(child.media_content_id);
}
return !child.media_content_type || child.can_expand || canPlay;
});
}
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
const childrenMediaClass = currentItem.children_media_class ? MediaClassBrowserSettings[currentItem.children_media_class] : MediaClassBrowserSettings.directory;
const backgroundImage = currentItem.thumbnail ? this._getThumbnailURLorBase64(currentItem.thumbnail).then((value) => `url(${value})`) : "none";
return x`
${currentItem.can_play ? x`
` : ""}
${this._error ? x`
${this._renderError(this._error)}
` : isManualMediaSourceContentId(currentItem.media_content_id) ? x`
` : isTTSMediaSource(currentItem.media_content_id) ? x`
` : !children.length && !currentItem.not_shown ? x`
${currentItem.media_content_id === "media-source://media_source/local/." ? x`
${this.hass.localize(
"ui.components.media-browser.file_management.highlight_button"
)}
` : this.hass.localize("ui.components.media-browser.no_items")}
` : this.preferredLayout === "list" ? x`
${currentItem.not_shown ? x`
${this.hass.localize("ui.components.media-browser.not_shown", {
count: currentItem.not_shown
})}
` : ""}
` : this.itemsPerRow ? x`
${children.map((child) => this._renderGridItem(child))}
${currentItem.not_shown ? x`
${this.hass.localize("ui.components.media-browser.not_shown", {
count: currentItem.not_shown
})}
` : ""}
` : this.preferredLayout === "grid" || this.preferredLayout === "auto" && childrenMediaClass.layout === "grid" ? x`
${currentItem.not_shown ? x`
${this.hass.localize("ui.components.media-browser.not_shown", {
count: currentItem.not_shown
})}
` : ""}
` : x`
${currentItem.not_shown ? x`
${this.hass.localize("ui.components.media-browser.not_shown", {
count: currentItem.not_shown
})}
` : ""}
`}
`;
}
async _getThumbnailURLorBase64(thumbnailUrl) {
if (!thumbnailUrl) {
return "";
}
if (thumbnailUrl.startsWith("/")) {
return new Promise((resolve, reject) => {
this.hass.fetchWithAuth(thumbnailUrl).then((response) => response.blob()).then((blob) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result;
resolve(typeof result === "string" ? result : "");
};
reader.onerror = (e2) => reject(e2);
reader.readAsDataURL(blob);
});
});
}
if (isBrandUrl(thumbnailUrl)) {
thumbnailUrl = brandsUrl({
domain: extractDomainFromBrandUrl(thumbnailUrl),
type: "icon",
darkOptimized: this.hass.themes?.darkMode
});
}
return thumbnailUrl;
}
_runAction(item) {
fireEvent(this, "media-picked", { item, navigateIds: this.navigateIds });
}
_ttsPicked(ev) {
ev.stopPropagation();
const navigateIds = this.navigateIds.slice(0, -1);
navigateIds.push(ev.detail.item);
fireEvent(this, "media-picked", {
...ev.detail,
navigateIds
});
}
_manualPicked(ev) {
ev.stopPropagation();
fireEvent(this, "media-picked", {
item: ev.detail.item,
navigateIds: this.navigateIds
});
}
async _fetchData(entityId, mediaContentId, mediaContentType) {
const prom = entityId && entityId !== BROWSER_PLAYER ? browseMediaPlayer(this.hass, entityId, mediaContentId, mediaContentType) : browseLocalMediaPlayer(this.hass, mediaContentId);
return prom.then((item) => {
if (!mediaContentId && this.action === "pick") {
item.children = item.children || [];
item.children.push(MANUAL_ITEM);
}
return item;
});
}
_measureCard() {
this.narrow = (this.dialog ? window.innerWidth : this.offsetWidth) < 450;
}
async _attachResizeObserver() {
if (!this._resizeObserver) {
this._resizeObserver = new ResizeObserver(debounce(() => this._measureCard(), 250, false));
}
this._resizeObserver.observe(this);
}
_closeDialogAction() {
fireEvent(this, "close-dialog");
}
_setError(error) {
if (!this.dialog) {
this._error = error;
return;
}
if (!error) {
return;
}
this._closeDialogAction();
showAlertDialog(this, {
title: this.hass.localize("ui.components.media-browser.media_browsing_error"),
text: this._renderError(error)
});
}
_renderError(err) {
if (err.message === "Media directory does not exist.") {
return x`
${this.hass.localize("ui.components.media-browser.no_local_media_found")}
${this.hass.localize("ui.components.media-browser.no_media_folder")}
${this.hass.localize("ui.components.media-browser.setup_local_help", {
documentation: x`${this.hass.localize("ui.components.media-browser.documentation")}`
})}
${this.hass.localize("ui.components.media-browser.local_media_files")}
`;
}
return x`${err.message}`;
}
async _setHeaderHeight() {
await this.updateComplete;
const header = this._header;
const content = this._content;
if (!header || !content) {
return;
}
this._headerOffsetHeight = header.offsetHeight;
content.style.marginTop = `${this._headerOffsetHeight}px`;
content.style.maxHeight = `calc(var(--media-browser-max-height, 100%) - ${this._headerOffsetHeight}px)`;
}
_animateHeaderHeight() {
let start;
const animate = (time) => {
if (start === void 0) {
start = time;
}
const elapsed = time - start;
this._setHeaderHeight();
if (elapsed < 400) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}
_scroll(ev) {
const content = ev.currentTarget;
if (!this.scrolled && content.scrollTop > this._headerOffsetHeight) {
this.scrolled = true;
} else if (this.scrolled && content.scrollTop < this._headerOffsetHeight) {
this.scrolled = false;
}
}
static get styles() {
return [
haStyle,
i$8`
:host {
display: flex;
flex-direction: column;
position: relative;
direction: ltr;
height: 100%;
}
ha-spinner {
margin: 40px auto;
}
.container {
padding: 16px;
}
.no-items {
padding-left: 32px;
}
.highlight-add-button {
display: flex;
flex-direction: row-reverse;
margin-right: 48px;
margin-inline-end: 48px;
margin-inline-start: initial;
direction: var(--direction);
}
.highlight-add-button ha-svg-icon {
position: relative;
top: -0.5em;
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
transform: scaleX(var(--scale-direction));
}
.content {
overflow-y: auto;
box-sizing: border-box;
height: 100%;
flex: 1;
}
/* HEADER */
.header {
display: flex;
justify-content: space-between;
border-bottom: 1px solid var(--divider-color);
background-color: var(--card-background-color);
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 3;
padding: 16px;
}
.header_button {
position: relative;
right: -8px;
}
.header-content {
display: flex;
flex-wrap: wrap;
flex-grow: 1;
align-items: flex-start;
}
.header-content .img {
height: 175px;
width: 175px;
margin-right: 16px;
background-size: cover;
border-radius: 2px;
transition:
width 0.4s,
height 0.4s;
}
.header-info {
display: flex;
flex-direction: column;
justify-content: space-between;
align-self: stretch;
min-width: 0;
flex: 1;
}
.header-info ha-button {
display: block;
padding-bottom: 16px;
}
.breadcrumb {
display: flex;
flex-direction: column;
overflow: hidden;
flex-grow: 1;
padding-top: 16px;
}
.breadcrumb .title {
font-size: var(--ha-font-size-4xl);
line-height: var(--ha-line-height-condensed);
font-weight: var(--ha-font-weight-bold);
margin: 0;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
padding-right: 8px;
}
.breadcrumb .previous-title {
font-size: var(--ha-font-size-m);
padding-bottom: 8px;
color: var(--secondary-text-color);
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
--mdc-icon-size: 14px;
}
.breadcrumb .subtitle {
font-size: var(--ha-font-size-l);
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 0;
transition:
height 0.5s,
margin 0.5s;
}
.not-shown {
font-style: italic;
color: var(--secondary-text-color);
padding: 8px 16px 8px;
}
.grid.not-shown {
display: flex;
align-items: center;
text-align: center;
}
/* ============= CHILDREN ============= */
ha-list {
--mdc-list-vertical-padding: 0;
--mdc-list-item-graphic-margin: 0;
--mdc-theme-text-icon-on-background: var(--secondary-text-color);
margin-top: 10px;
}
ha-list li:last-child {
display: none;
}
ha-list li[divider] {
border-bottom-color: var(--divider-color);
}
ha-list-item {
width: 100%;
}
div.children {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--media-browse-item-size, 175px), 0.1fr));
grid-gap: var(--ha-space-4);
padding: 16px;
}
div.children.flex-grid {
display: flex;
flex-wrap: wrap;
padding: 4px;
gap: 8px;
}
.flex-grid .child {
/* 8px gap between items, so subtract gap*(n-1)/n ≈ gap for simplicity */
width: calc(100% / var(--items-per-row) - 8px);
}
:host([dialog]) .children {
grid-template-columns: repeat(auto-fit, minmax(var(--media-browse-item-size, 175px), 0.33fr));
}
.child {
display: flex;
flex-direction: column;
cursor: pointer;
}
ha-card {
position: relative;
width: 100%;
box-sizing: border-box;
}
.children ha-card .thumbnail {
width: 100%;
position: relative;
box-sizing: border-box;
transition: padding-bottom 0.1s ease-out;
padding-bottom: 100%;
}
.portrait ha-card .thumbnail {
padding-bottom: 150%;
}
ha-card .image {
border-radius: var(--ha-border-radius-sm) var(--ha-border-radius-sm) var(--ha-border-radius-square)
var(--ha-border-radius-square);
}
.image {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.centered-image {
margin: 0 8px;
background-size: contain;
}
.brand-image {
background-size: 40%;
}
.children ha-card .icon-holder {
display: flex;
justify-content: center;
align-items: center;
}
.child .folder {
color: var(--secondary-text-color);
--mdc-icon-size: calc(var(--media-browse-item-size, 175px) * 0.4);
}
.child .icon {
color: #00a9f7; /* Match the png color from brands repo */
--mdc-icon-size: calc(var(--media-browse-item-size, 175px) * 0.4);
}
.child .play {
position: absolute;
transition: color 0.5s;
border-radius: var(--ha-border-radius-circle);
top: calc(50% - 20px);
right: calc(50% - 20px);
opacity: 0;
transition: opacity 0.1s ease-out;
}
.child .play:not(.can_expand) {
--mdc-icon-button-size: 40px;
--mdc-icon-size: 24px;
background-color: var(--primary-color);
color: var(--text-primary-color);
}
ha-card:hover .image {
filter: brightness(70%);
transition: filter 0.5s;
}
ha-card:hover .play {
opacity: 1;
}
ha-card:hover .play.can_expand {
bottom: 8px;
}
.child .play.can_expand {
background-color: rgba(var(--rgb-card-background-color), 0.5);
top: auto;
bottom: 0px;
right: 8px;
transition:
bottom 0.1s ease-out,
opacity 0.1s ease-out;
}
.child .title {
font-size: var(--ha-font-size-l);
padding-top: 16px;
padding-left: 2px;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
}
.child ha-card .title {
margin-bottom: 16px;
padding-left: 16px;
}
ha-list-item .graphic {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
border-radius: var(--ha-border-radius-sm);
display: flex;
align-content: center;
align-items: center;
line-height: initial;
}
ha-list-item .graphic .play {
opacity: 0;
transition: all 0.5s;
background-color: rgba(var(--rgb-card-background-color), 0.5);
border-radius: var(--ha-border-radius-circle);
--mdc-icon-button-size: 40px;
}
ha-list-item:hover .graphic .play {
opacity: 1;
color: var(--primary-text-color);
}
ha-list-item .graphic .play.show {
opacity: 1;
background-color: transparent;
}
ha-list-item .title {
margin-left: 16px;
margin-inline-start: 16px;
margin-inline-end: initial;
}
/* ============= Narrow ============= */
:host([narrow]) {
padding: 0;
}
:host([narrow]) .media-source {
padding: 0 24px;
}
:host([narrow]) div.children {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
}
:host([narrow]) .breadcrumb .title {
font-size: var(--ha-font-size-2xl);
}
:host([narrow]) .header {
padding: 0;
}
:host([narrow]) .header.no-dialog {
display: block;
}
:host([narrow]) .header_button {
position: absolute;
top: 14px;
right: 8px;
}
:host([narrow]) .header-content {
flex-direction: column;
flex-wrap: nowrap;
}
:host([narrow]) .header-content .img {
height: auto;
width: 100%;
margin-right: 0;
padding-bottom: 50%;
margin-bottom: 8px;
position: relative;
background-position: center;
border-radius: var(--ha-border-radius-square);
transition:
width 0.4s,
height 0.4s,
padding-bottom 0.4s;
}
ha-fab {
position: absolute;
--mdc-theme-secondary: var(--primary-color);
bottom: -20px;
right: 20px;
}
:host([narrow]) .header-info ha-button {
margin-top: 16px;
margin-bottom: 8px;
}
:host([narrow]) .header-info {
padding: 0 16px 8px;
}
/* ============= Scroll ============= */
:host([scrolled]) .breadcrumb .subtitle {
height: 0;
margin: 0;
}
:host([scrolled]) .breadcrumb .title {
-webkit-line-clamp: 1;
}
:host(:not([narrow])[scrolled]) .header:not(.no-img) ha-icon-button {
align-self: center;
}
:host([scrolled]) .header-info ha-button,
.no-img .header-info ha-button {
padding-right: 4px;
}
:host([scrolled][narrow]) .no-img .header-info ha-button {
padding-right: 16px;
}
:host([scrolled]) .header-info {
flex-direction: row;
}
:host([scrolled]) .header-info ha-button {
align-self: center;
margin-top: 0;
margin-bottom: 0;
padding-bottom: 0;
}
:host([scrolled][narrow]) .no-img .header-info {
flex-direction: row-reverse;
}
:host([scrolled][narrow]) .header-info {
padding: 20px 24px 10px 24px;
align-items: center;
}
:host([scrolled]) .header-content {
align-items: flex-end;
flex-direction: row;
}
:host([scrolled]) .header-content .img {
height: 75px;
width: 75px;
}
:host([scrolled]) .breadcrumb {
padding-top: 0;
align-self: center;
}
:host([scrolled][narrow]) .header-content .img {
height: 100px;
width: 100px;
padding-bottom: initial;
margin-bottom: 0;
}
:host([scrolled]) ha-fab {
bottom: 0px;
right: -24px;
--mdc-fab-box-shadow: none;
--mdc-theme-secondary: rgba(var(--rgb-primary-color), 0.5);
}
lit-virtualizer {
display: block;
height: 100%;
overflow: auto !important;
}
lit-virtualizer.not_shown {
height: calc(100% - 36px);
}
ha-browse-media-tts {
direction: var(--direction);
}
`
];
}
};
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "hass", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "entityId", 2);
__decorateClass$l([
n$4()
], HaMediaPlayerBrowse.prototype, "action", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "preferredLayout", 2);
__decorateClass$l([
n$4({ type: Number })
], HaMediaPlayerBrowse.prototype, "itemsPerRow", 2);
__decorateClass$l([
n$4({ type: Boolean })
], HaMediaPlayerBrowse.prototype, "dialog", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "navigateIds", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "accept", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "defaultId", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "defaultType", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "hideContentType", 2);
__decorateClass$l([
n$4({ attribute: false })
], HaMediaPlayerBrowse.prototype, "contentIdHelper", 2);
__decorateClass$l([
n$4({ type: Boolean, reflect: true })
], HaMediaPlayerBrowse.prototype, "narrow", 2);
__decorateClass$l([
n$4({ type: Boolean, reflect: true })
], HaMediaPlayerBrowse.prototype, "scrolled", 2);
__decorateClass$l([
r$3()
], HaMediaPlayerBrowse.prototype, "_error", 2);
__decorateClass$l([
r$3()
], HaMediaPlayerBrowse.prototype, "_parentItem", 2);
__decorateClass$l([
r$3()
], HaMediaPlayerBrowse.prototype, "_currentItem", 2);
__decorateClass$l([
e$2(".header")
], HaMediaPlayerBrowse.prototype, "_header", 2);
__decorateClass$l([
e$2(".content")
], HaMediaPlayerBrowse.prototype, "_content", 2);
__decorateClass$l([
e$2("lit-virtualizer")
], HaMediaPlayerBrowse.prototype, "_virtualizer", 2);
__decorateClass$l([
t$2({ passive: true })
], HaMediaPlayerBrowse.prototype, "_scroll", 1);
HaMediaPlayerBrowse = __decorateClass$l([
t$3("mxmp-ha-media-player-browse")
], HaMediaPlayerBrowse);
const i4 = e$1(class extends i$3 {
constructor() {
super(...arguments), this.key = E;
}
render(r2, t2) {
return this.key = r2, t2;
}
update(r2, [t2, e2]) {
return t2 !== this.key && (m$1(r2), this.key = t2), e2;
}
});
const mediaBrowserStyles = i$8`
:host {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.header {
display: flex;
align-items: center;
padding: 4px 8px;
border-bottom: 1px solid var(--divider-color);
background: var(--card-background-color);
}
.title {
flex: 1;
font-weight: 500;
font-size: calc(var(--mxmp-font-size, 1rem) * 1.1);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.spacer {
width: 48px;
}
.no-items {
text-align: center;
margin-top: 50%;
}
mxmp-icon-button.startpath-active {
color: var(--accent-color);
}
mxmp-icon-button.shortcut-active {
color: var(--accent-color);
}
.loading-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid var(--secondary-text-color);
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
mxmp-ha-media-player-browse,
mxmp-favorites {
--mdc-icon-size: 24px;
--media-browse-item-size: 100px;
flex: 1;
min-height: 0;
overflow: auto;
}
`;
const selectedStyle = { color: "var(--accent-color)" };
function renderLayoutMenu(layout, onSelect) {
return x`
Auto
Grid
List
`;
}
async function playAll(store, children) {
if (!children.length) {
return null;
}
const player = store.activePlayer;
await store.hass.callService("media_player", "play_media", {
entity_id: player.id,
media_content_id: children[0].media_content_id,
media_content_type: children[0].media_content_type,
enqueue: "replace"
});
await Promise.all(
children.slice(1).map(
(child) => store.hass.callService("media_player", "play_media", {
entity_id: player.id,
media_content_id: child.media_content_id,
media_content_type: child.media_content_type,
enqueue: "add"
})
)
);
return children[0];
}
function renderShortcutButton(shortcut, onClick, isActive = false) {
if (!shortcut?.media_content_id || !shortcut?.media_content_type || !shortcut?.name) {
return E;
}
const icon = shortcut.icon ?? mdiBookmark;
return x`
${icon.startsWith("mdi:") ? x`` : E}
`;
}
var __defProp$k = Object.defineProperty;
var __decorateClass$k = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$k(target, key, result);
return result;
};
let currentPath = null;
let currentPathTitle = "";
const ROOT_NAV = { media_content_id: void 0, media_content_type: void 0 };
const START_PATH_KEY$1 = "mxmp-media-browser-start";
class MediaBrowserBrowser extends i$5 {
constructor() {
super(...arguments);
this.layout = "auto";
this.navigateIds = [];
this.currentTitle = "";
this.isCurrentPathStart = false;
this.playAllLoading = false;
this.mediaLoaded = false;
this.toggleStartPath = () => {
if (this.isCurrentPathStart) {
localStorage.removeItem(START_PATH_KEY$1);
} else {
localStorage.setItem(START_PATH_KEY$1, JSON.stringify(this.navigateIds));
}
this.isCurrentPathStart = !this.isCurrentPathStart;
};
this.goToFavorites = () => {
this.dispatchEvent(new CustomEvent("go-to-favorites"));
};
this.goBack = () => {
if (this.navigateIds.length <= 1) {
return;
}
this.navigateIds = this.navigateIds.slice(0, -1);
this.currentTitle = this.navigateIds[this.navigateIds.length - 1]?.title || "";
this.saveCurrentState();
this.updateIsCurrentPathStart();
};
this.handleLayoutChange = (ev) => {
this.dispatchEvent(new CustomEvent("layout-change", { detail: ev.detail.item.value }));
};
this.onMediaPicked = async (event) => {
const mediaItem = event.detail.item;
await this.store.mediaControlService.playMedia(this.store.activePlayer, mediaItem);
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED, mediaItem));
};
this.onMediaBrowsed = (event) => {
this.navigateIds = event.detail.ids;
const isRoot = this.navigateIds.length === 1 && !this.navigateIds[0].media_content_id;
const lastItem = this.navigateIds[this.navigateIds.length - 1];
this.currentTitle = isRoot ? "" : lastItem?.title || event.detail.current?.title || "";
this.mediaLoaded = !this.mediaLoaded;
this.saveCurrentState();
this.updateIsCurrentPathStart();
};
}
connectedCallback() {
super.connectedCallback();
if (currentPath) {
this.navigateIds = currentPath;
this.currentTitle = currentPathTitle;
} else {
this.restoreFromStartPath();
}
this.updateIsCurrentPathStart();
}
restoreFromStartPath() {
const startPath = localStorage.getItem(START_PATH_KEY$1);
if (!startPath) {
this.navigateIds = [ROOT_NAV];
return;
}
try {
this.navigateIds = JSON.parse(startPath);
this.currentTitle = this.navigateIds[this.navigateIds.length - 1]?.title || "";
} catch {
this.navigateIds = [ROOT_NAV];
}
}
navigateToShortcut(shortcut) {
this.navigateIds = [
ROOT_NAV,
{
media_content_id: shortcut.media_content_id,
media_content_type: shortcut.media_content_type,
title: shortcut.name
}
];
this.currentTitle = shortcut.name || "";
this.saveCurrentState();
this.updateIsCurrentPathStart();
}
saveCurrentState() {
currentPath = this.navigateIds;
currentPathTitle = this.currentTitle;
}
updateIsCurrentPathStart() {
const startPath = localStorage.getItem(START_PATH_KEY$1);
this.isCurrentPathStart = startPath === JSON.stringify(this.navigateIds);
}
render() {
const config = this.store.config.mediaBrowser ?? {};
const shortcut = config.shortcut;
return x`
${this.playAllLoading ? x`` : E}
${config.hideHeader ? "" : x``}
${i4(
this.layout,
x``
)}
`;
}
isShortcutActive(shortcut) {
return !!shortcut && this.navigateIds.some((nav) => nav.media_content_id === shortcut.media_content_id);
}
renderPlayAllButton() {
const playableCount = this.mediaBrowser?.getPlayableChildren().length ?? 0;
if (playableCount === 0 || this.playAllLoading) {
return E;
}
return x``;
}
async handlePlayAll() {
const children = this.mediaBrowser?.getPlayableChildren() || [];
if (!children.length) {
return;
}
this.playAllLoading = true;
try {
const firstItem = await playAll(this.store, children);
if (firstItem) {
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED, firstItem));
}
} catch (e2) {
console.error("Failed to play all:", e2);
} finally {
this.playAllLoading = false;
}
}
static get styles() {
return mediaBrowserStyles;
}
}
__decorateClass$k([
n$4({ attribute: false })
], MediaBrowserBrowser.prototype, "store");
__decorateClass$k([
n$4({ type: String })
], MediaBrowserBrowser.prototype, "layout");
__decorateClass$k([
r$3()
], MediaBrowserBrowser.prototype, "navigateIds");
__decorateClass$k([
r$3()
], MediaBrowserBrowser.prototype, "currentTitle");
__decorateClass$k([
r$3()
], MediaBrowserBrowser.prototype, "isCurrentPathStart");
__decorateClass$k([
r$3()
], MediaBrowserBrowser.prototype, "playAllLoading");
__decorateClass$k([
r$3()
], MediaBrowserBrowser.prototype, "mediaLoaded");
__decorateClass$k([
e$2("mxmp-ha-media-player-browse")
], MediaBrowserBrowser.prototype, "mediaBrowser");
var __defProp$j = Object.defineProperty;
var __decorateClass$j = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$j(target, key, result);
return result;
};
const START_PATH_KEY = "mxmp-media-browser-start";
const LAYOUT_KEY = "mxmp-media-browser-layout";
const FAVORITES_VIEW = "favorites";
let currentView = null;
class MediaBrowser extends i$5 {
constructor() {
super(...arguments);
this.isCurrentPathStart = false;
this.layout = "auto";
this.view = "favorites";
this.handleMenuAction = (ev) => {
this.setLayout(ev.detail.item.value);
};
this.toggleStartPath = () => {
if (this.isCurrentPathStart) {
localStorage.removeItem(START_PATH_KEY);
} else {
localStorage.setItem(START_PATH_KEY, FAVORITES_VIEW);
}
this.isCurrentPathStart = !this.isCurrentPathStart;
};
this.goToFavorites = () => {
this.view = "favorites";
currentView = "favorites";
this.updateIsCurrentPathStart();
};
this.goToBrowser = () => {
this.view = "browser";
currentView = "browser";
this.updateIsCurrentPathStart();
};
this.onShortcutClick = () => {
const shortcut = this.store.config.mediaBrowser?.shortcut;
if (!shortcut) {
return;
}
this.view = "browser";
currentView = "browser";
void this.updateComplete.then(() => this.browserComponent?.navigateToShortcut(shortcut));
};
this.onMediaItemSelected = (event) => {
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED, event.detail));
};
this.onBrowserLayoutChange = (event) => {
this.setLayout(event.detail);
};
}
connectedCallback() {
super.connectedCallback();
this.initializeView();
this.loadLayout();
}
loadLayout() {
const savedLayout = localStorage.getItem(LAYOUT_KEY);
if (savedLayout && ["auto", "grid", "list"].includes(savedLayout)) {
this.layout = savedLayout;
}
}
setLayout(layout) {
this.layout = layout;
localStorage.setItem(LAYOUT_KEY, layout);
}
initializeView() {
const onlyFavorites = this.store.config.mediaBrowser?.onlyFavorites ?? false;
if (onlyFavorites) {
this.view = "favorites";
this.updateIsCurrentPathStart();
return;
}
if (currentView !== null) {
this.view = currentView;
this.updateIsCurrentPathStart();
return;
}
const startPath = localStorage.getItem(START_PATH_KEY);
if (startPath && startPath !== FAVORITES_VIEW) {
this.view = "browser";
} else {
this.view = "favorites";
}
this.updateIsCurrentPathStart();
}
updateIsCurrentPathStart() {
const startPath = localStorage.getItem(START_PATH_KEY);
if (this.view === "favorites") {
this.isCurrentPathStart = startPath === FAVORITES_VIEW || startPath === null;
} else {
this.isCurrentPathStart = false;
}
}
render() {
return this.view === "favorites" ? this.renderFavorites() : this.renderBrowser();
}
renderFavorites() {
const config = this.store.config.mediaBrowser ?? {};
const title = config.favorites?.title ?? "Favorites";
const onlyFavorites = config.onlyFavorites ?? false;
return x`
${config.hideHeader ? "" : x``}
`;
}
renderBrowser() {
return x`
`;
}
static get styles() {
return mediaBrowserStyles;
}
}
__decorateClass$j([
n$4({ attribute: false })
], MediaBrowser.prototype, "store");
__decorateClass$j([
r$3()
], MediaBrowser.prototype, "isCurrentPathStart");
__decorateClass$j([
r$3()
], MediaBrowser.prototype, "layout");
__decorateClass$j([
r$3()
], MediaBrowser.prototype, "view");
__decorateClass$j([
e$2("mxmp-media-browser-browser")
], MediaBrowser.prototype, "browserComponent");
var __defProp$i = Object.defineProperty;
var __decorateClass$i = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$i(target, key, result);
return result;
};
class OperationOverlay extends i$5 {
render() {
if (!this.progress) {
return E;
}
const progressText = this.progress.total > 1 ? `${this.progress.label} ${this.progress.current} of ${this.progress.total}` : `${this.progress.label}...`;
return x`
${progressText}
${this.hass?.localize("ui.common.cancel") || "Cancel"}
`;
}
onCancel() {
this.dispatchEvent(customEvent("cancel-operation"));
}
static get styles() {
return i$8`
.operation-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.85);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
.operation-overlay-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
padding: 2rem;
text-align: center;
}
.operation-progress-text {
font-size: 1.2rem;
color: var(--primary-text-color, #fff);
}
.accent {
--control-button-background-color: var(--accent-color);
}
`;
}
}
__decorateClass$i([
n$4({ attribute: false })
], OperationOverlay.prototype, "progress");
__decorateClass$i([
n$4({ attribute: false })
], OperationOverlay.prototype, "hass");
customElements.define("mxmp-operation-overlay", OperationOverlay);
var __defProp$h = Object.defineProperty;
var __decorateClass$h = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$h(target, key, result);
return result;
};
const PLAY_MENU_ACTIONS = [
{ enqueue: "replace" },
{ enqueue: "play", radioMode: true },
{ enqueue: "play" },
{ enqueue: "next" },
{ enqueue: "add" },
{ enqueue: "replace_next" }
];
const _PlayMenu = class _PlayMenu extends i$5 {
constructor() {
super(...arguments);
this.disabled = false;
this.hasSelection = false;
this.inline = false;
}
render() {
if (!this.hasSelection) {
return E;
}
if (this.inline) {
return this.renderInlineMenu();
}
return this.renderButtonMenu();
}
renderButtonMenu() {
return x`
${this.renderMenuItems()}
`;
}
renderInlineMenu() {
return x`
`;
}
renderMenuItems() {
return x`
Play Now (clear queue)
Start Radio
Play Now
Play Next
Add to Queue
Play Next (clear queue)
`;
}
getActionIcon(index) {
return [mdiPlayBoxMultiple, mdiAccessPoint, mdiPlay, mdiSkipNext, mdiPlaylistPlus, mdiSkipNextCircle][index];
}
getActionLabel(index) {
return ["Play Now (clear queue)", "Start Radio", "Play Now", "Play Next", "Add to Queue", "Play Next (clear queue)"][index];
}
handleAction(e2) {
const action = PLAY_MENU_ACTIONS[parseInt(e2.detail.item.value)];
if (action) {
this.dispatchEvent(customEvent("play-menu-action", action));
}
}
selectAction(index) {
const action = PLAY_MENU_ACTIONS[index];
if (action) {
this.dispatchEvent(customEvent("play-menu-action", action));
}
}
closeMenu() {
this.dispatchEvent(customEvent("play-menu-close"));
}
};
_PlayMenu.styles = i$8`
:host {
display: contents;
}
.inline-menu {
position: relative;
background: var(--card-background-color, var(--primary-background-color));
border: 1px solid var(--divider-color, rgba(255, 255, 255, 0.12));
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
min-width: 200px;
padding: 17px 17px;
z-index: 10;
}
.close-btn {
position: absolute;
top: 2px;
right: 2px;
--mdc-icon-button-size: 28px;
--mdc-icon-size: 18px;
color: var(--secondary-text-color);
}
.inline-menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
cursor: pointer;
color: var(--primary-text-color);
font-size: 0.9rem;
}
.inline-menu-item:hover {
background: var(--secondary-background-color);
}
.inline-menu-item ha-svg-icon {
--mdc-icon-size: 20px;
flex-shrink: 0;
}
`;
let PlayMenu = _PlayMenu;
__decorateClass$h([
n$4({ type: Boolean })
], PlayMenu.prototype, "disabled");
__decorateClass$h([
n$4({ type: Boolean })
], PlayMenu.prototype, "hasSelection");
__decorateClass$h([
n$4({ type: Boolean })
], PlayMenu.prototype, "inline");
customElements.define("mxmp-play-menu", PlayMenu);
const QUEUE_SEARCH_STORAGE_KEY = "mxmp-queue-search-state";
function restoreQueueSearchState() {
const saved = localStorage.getItem(QUEUE_SEARCH_STORAGE_KEY);
if (!saved) {
return null;
}
try {
const state = JSON.parse(saved);
return {
expanded: !!state.expanded,
searchText: state.searchText || "",
showOnlyMatches: !!state.showOnlyMatches
};
} catch {
return null;
}
}
function saveQueueSearchState(state) {
localStorage.setItem(QUEUE_SEARCH_STORAGE_KEY, JSON.stringify(state));
}
function findMatchIndices(items, searchText) {
const searchLower = searchText.toLowerCase();
return items.map((item, i5) => item.title?.toLowerCase().includes(searchLower) ? i5 : -1).filter((i5) => i5 !== -1);
}
function getCurrentMatchIndex(matchIndices, continueFromCurrent, lastHighlightedIndex) {
if (!continueFromCurrent || lastHighlightedIndex < 0) {
return 0;
}
const nextMatchAfterLast = matchIndices.find((i5) => i5 >= lastHighlightedIndex);
return nextMatchAfterLast !== void 0 ? matchIndices.indexOf(nextMatchAfterLast) : 0;
}
function createQueueSearchMatch(index, currentMatchIndex, matchIndices) {
return {
index,
currentMatch: currentMatchIndex + 1,
totalMatches: matchIndices.length,
matchIndices
};
}
const queueSearchStyles = i$8`
:host {
display: contents;
}
:host > mxmp-icon-button[selected] {
color: var(--accent-color);
}
.search-row {
display: flex;
align-items: center;
position: absolute;
top: 100%;
left: 0;
right: 0;
padding: 0.5rem;
background: var(--card-background-color, #1c1c1c);
border-bottom: 1px solid var(--divider-color, #e0e0e0);
z-index: 10;
}
input {
flex: 1;
padding: 0.5rem;
border: 1px solid var(--divider-color, #ccc);
border-radius: 4px;
font-size: var(--mxmp-font-size, 1rem);
background: var(--card-background-color, #fff);
color: var(--primary-text-color, #000);
}
input:focus {
outline: none;
border-color: var(--accent-color, #03a9f4);
}
input.no-match {
border-color: var(--error-color, red);
}
.match-info {
padding: 0 0.5rem;
font-size: calc(var(--mxmp-font-size, 1rem) * 0.9);
color: var(--secondary-text-color, #666);
white-space: nowrap;
}
.search-row mxmp-icon-button {
--icon-button-size: 2rem;
--icon-size: 1.2rem;
}
.search-row mxmp-icon-button[selected] {
color: var(--accent-color);
}
`;
var __defProp$g = Object.defineProperty;
var __decorateClass$g = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$g(target, key, result);
return result;
};
const _QueueSearchPanel = class _QueueSearchPanel extends i$5 {
constructor() {
super(...arguments);
this.searchText = "";
this.selectMode = false;
this.matchCount = 0;
this.currentMatchIndex = 0;
this.hasNoMatch = false;
this.showOnlyMatches = false;
}
render() {
const hasText = this.searchText.length > 0;
const hasMatches = this.matchCount > 0;
return x`
${this.currentMatchIndex + 1}/${this.matchCount}
this.dispatchAction({ type: "prev" })} ?hidden=${!hasMatches}>
this.dispatchAction({ type: "next" })} ?hidden=${!hasMatches}>
this.dispatchAction({ type: "select-all" })}
title="Select all matches"
?hidden=${!hasMatches || !this.selectMode}
>
this.dispatchAction({ type: "toggle-show-only" })}
?selected=${this.showOnlyMatches}
title="Show only matches"
?hidden=${!hasText}
>
this.dispatchAction({ type: "clear" })}
title="Clear search"
?hidden=${!hasText}
>
`;
}
focusInput() {
this.shadowRoot?.querySelector("input")?.focus();
}
onInput(e2) {
const value = e2.target.value;
this.dispatchAction({ type: "input", payload: { value } });
}
onKeyDown(e2) {
this.dispatchAction({ type: "keydown", payload: { key: e2.key } });
}
dispatchAction(action) {
this.dispatchEvent(customEvent("queue-search-ui-action", action));
}
};
_QueueSearchPanel.styles = i$8`
:host {
display: block;
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 10;
}
:host([hidden]) {
display: none !important;
}
[hidden] {
display: none !important;
}
.search-row {
display: flex;
align-items: center;
padding: 0.5rem;
background: var(--card-background-color, #1c1c1c);
border-bottom: 1px solid var(--divider-color, #e0e0e0);
}
input {
flex: 1;
padding: 0.5rem;
border: 1px solid var(--divider-color, #ccc);
border-radius: 4px;
font-size: var(--mxmp-font-size, 1rem);
background: var(--card-background-color, #fff);
color: var(--primary-text-color, #000);
}
input:focus {
outline: none;
border-color: var(--accent-color, #03a9f4);
}
input.no-match {
border-color: var(--error-color, red);
}
.match-info {
padding: 0 0.5rem;
font-size: calc(var(--mxmp-font-size, 1rem) * 0.9);
color: var(--secondary-text-color, #666);
white-space: nowrap;
}
.search-row mxmp-icon-button {
--icon-button-size: 2rem;
--icon-size: 1.2rem;
}
.search-row mxmp-icon-button[selected] {
color: var(--accent-color);
}
`;
let QueueSearchPanel = _QueueSearchPanel;
__decorateClass$g([
n$4()
], QueueSearchPanel.prototype, "searchText");
__decorateClass$g([
n$4({ type: Boolean })
], QueueSearchPanel.prototype, "selectMode");
__decorateClass$g([
n$4({ type: Number })
], QueueSearchPanel.prototype, "matchCount");
__decorateClass$g([
n$4({ type: Number })
], QueueSearchPanel.prototype, "currentMatchIndex");
__decorateClass$g([
n$4({ type: Boolean })
], QueueSearchPanel.prototype, "hasNoMatch");
__decorateClass$g([
n$4({ type: Boolean })
], QueueSearchPanel.prototype, "showOnlyMatches");
customElements.define("mxmp-queue-search-panel", QueueSearchPanel);
var __defProp$f = Object.defineProperty;
var __decorateClass$f = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$f(target, key, result);
return result;
};
const _QueueSearch = class _QueueSearch extends i$5 {
constructor() {
super(...arguments);
this.items = [];
this.selectMode = false;
this.expanded = false;
this.searchText = "";
this.matchIndices = [];
this.currentMatchIndex = 0;
this.showOnlyMatches = false;
this.toggleExpanded = () => {
this.expanded = !this.expanded;
this.dispatch({ type: "expanded", payload: { expanded: this.expanded } });
this.persistState();
};
this.onUiAction = (e2) => {
const action = e2.detail;
if (action.type === "input") {
this.searchText = action.payload.value;
this.runSearch(false);
this.persistState();
return;
}
if (action.type === "keydown") {
if (action.payload.key === "Enter") {
this.moveMatch(false);
}
if (action.payload.key === "Escape") {
this.clearSearch();
}
return;
}
if (action.type === "next") {
this.moveMatch(false);
return;
}
if (action.type === "prev") {
this.moveMatch(true);
return;
}
if (action.type === "select-all") {
this.dispatch({ type: "select-all", payload: { indices: this.matchIndices } });
return;
}
if (action.type === "toggle-show-only") {
this.showOnlyMatches = !this.showOnlyMatches;
this.dispatch({ type: "show-only", payload: { showOnlyMatches: this.showOnlyMatches, shownIndices: this.showOnlyMatches ? this.matchIndices : [] } });
this.persistState();
return;
}
if (action.type === "clear") {
this.clearSearch();
}
};
}
connectedCallback() {
super.connectedCallback();
const saved = restoreQueueSearchState();
if (!saved) {
return;
}
this.searchText = saved.searchText.trim();
this.expanded = this.searchText.length > 0 && saved.expanded;
this.showOnlyMatches = saved.showOnlyMatches;
}
willUpdate(changedProperties) {
if (changedProperties.has("items") && this.searchText) {
this.runSearch(false);
}
}
updated(changedProperties) {
if (changedProperties.has("expanded") && this.expanded) {
this.panel?.focusInput();
}
}
render() {
const hasNoMatch = this.searchText.length > 0 && this.matchIndices.length === 0;
return x`
0}
title="Search queue"
>
`;
}
runSearch(continueFromCurrent) {
if (!this.searchText.trim()) {
this.matchIndices = [];
this.currentMatchIndex = 0;
this.showOnlyMatches = false;
this.dispatch({ type: "match", payload: { index: -1, currentMatch: 0, totalMatches: 0, matchIndices: [] } });
this.dispatch({ type: "show-only", payload: { showOnlyMatches: false, shownIndices: [] } });
return;
}
const matchIndices = findMatchIndices(this.items, this.searchText);
this.matchIndices = matchIndices;
if (matchIndices.length === 0) {
this.currentMatchIndex = 0;
this.dispatch({ type: "match", payload: { index: -1, currentMatch: 0, totalMatches: 0, matchIndices: [] } });
this.dispatch({ type: "show-only", payload: { showOnlyMatches: false, shownIndices: [] } });
this.showOnlyMatches = false;
return;
}
const lastHighlighted = this.matchIndices[this.currentMatchIndex] ?? -1;
this.currentMatchIndex = getCurrentMatchIndex(matchIndices, continueFromCurrent, lastHighlighted);
const match = createQueueSearchMatch(matchIndices[this.currentMatchIndex], this.currentMatchIndex, matchIndices);
this.dispatch({ type: "match", payload: match });
this.dispatch({ type: "show-only", payload: { showOnlyMatches: this.showOnlyMatches, shownIndices: this.showOnlyMatches ? this.matchIndices : [] } });
}
moveMatch(reverse) {
if (this.matchIndices.length === 0) {
return;
}
const delta = reverse ? -1 : 1;
this.currentMatchIndex = (this.currentMatchIndex + delta + this.matchIndices.length) % this.matchIndices.length;
const match = createQueueSearchMatch(this.matchIndices[this.currentMatchIndex], this.currentMatchIndex, this.matchIndices);
this.dispatch({ type: "match", payload: match });
}
clearSearch() {
this.searchText = "";
this.matchIndices = [];
this.currentMatchIndex = 0;
this.showOnlyMatches = false;
this.dispatch({ type: "match", payload: { index: -1, currentMatch: 0, totalMatches: 0, matchIndices: [] } });
this.dispatch({ type: "show-only", payload: { showOnlyMatches: false, shownIndices: [] } });
this.persistState();
}
persistState() {
saveQueueSearchState({ expanded: this.expanded, searchText: this.searchText, showOnlyMatches: this.showOnlyMatches });
}
dispatch(action) {
this.dispatchEvent(customEvent("queue-search-action", action));
}
};
_QueueSearch.styles = queueSearchStyles;
let QueueSearch = _QueueSearch;
__decorateClass$f([
n$4({ attribute: false })
], QueueSearch.prototype, "items");
__decorateClass$f([
n$4({ type: Boolean })
], QueueSearch.prototype, "selectMode");
__decorateClass$f([
r$3()
], QueueSearch.prototype, "expanded");
__decorateClass$f([
r$3()
], QueueSearch.prototype, "searchText");
__decorateClass$f([
r$3()
], QueueSearch.prototype, "matchIndices");
__decorateClass$f([
r$3()
], QueueSearch.prototype, "currentMatchIndex");
__decorateClass$f([
r$3()
], QueueSearch.prototype, "showOnlyMatches");
__decorateClass$f([
e$2("mxmp-queue-search-panel")
], QueueSearch.prototype, "panel");
customElements.define("mxmp-queue-search", QueueSearch);
var __defProp$e = Object.defineProperty;
var __decorateClass$e = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$e(target, key, result);
return result;
};
const _SelectionActions = class _SelectionActions extends i$5 {
constructor() {
super(...arguments);
this.hasSelection = false;
this.disabled = false;
this.showInvert = true;
}
render() {
return x`
${this.showInvert ? x`` : E}
`;
}
invertSelection() {
this.dispatchEvent(customEvent("invert-selection"));
}
onPlayMenuAction(e2) {
const action = e2.detail;
switch (action.enqueue) {
case "replace":
this.dispatchEvent(customEvent("play-selected", { enqueue: "replace" }));
break;
case "play":
if (action.radioMode) {
this.dispatchEvent(customEvent("play-selected", { enqueue: "play", radioMode: true }));
} else {
this.dispatchEvent(customEvent("play-selected", { enqueue: "play" }));
}
break;
case "next":
this.dispatchEvent(customEvent("queue-selected", { enqueue: "next" }));
break;
case "add":
this.dispatchEvent(customEvent("queue-selected-at-end", { enqueue: "add" }));
break;
case "replace_next":
this.dispatchEvent(customEvent("queue-selected", { enqueue: "replace_next" }));
break;
}
}
};
_SelectionActions.styles = i$8`
:host {
display: contents;
}
`;
let SelectionActions = _SelectionActions;
__decorateClass$e([
n$4({ type: Boolean })
], SelectionActions.prototype, "hasSelection");
__decorateClass$e([
n$4({ type: Boolean })
], SelectionActions.prototype, "disabled");
__decorateClass$e([
n$4({ type: Boolean })
], SelectionActions.prototype, "showInvert");
customElements.define("mxmp-selection-actions", SelectionActions);
var __defProp$d = Object.defineProperty;
var __decorateClass$d = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$d(target, key, result);
return result;
};
const _QueueHeader = class _QueueHeader extends i$5 {
constructor() {
super(...arguments);
this.queueTitle = "";
this.itemCount = 0;
this.items = [];
this.selectMode = false;
this.hasSelection = false;
this.operationRunning = false;
}
render() {
return x`
`;
}
onSearchAction(e2) {
const { detail } = e2;
this.dispatchEvent(new CustomEvent("queue-search-action", { detail, bubbles: true, composed: true }));
}
dispatchAction(action) {
this.dispatchEvent(customEvent("queue-header-action", action));
}
};
_QueueHeader.styles = i$8`
:host {
display: block;
flex-shrink: 0;
}
[hidden] {
display: none !important;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
position: relative;
}
.header-icons {
white-space: nowrap;
display: flex;
align-items: center;
}
.header-icons > * {
display: inline-block;
}
.title-container {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
padding: 0.5rem;
}
.title {
font-size: calc(var(--mxmp-font-size, 1rem) * 1.2);
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-count {
font-size: calc(var(--mxmp-font-size, 1rem) * 0.9);
color: var(--secondary-text-color);
flex-shrink: 0;
}
.delete-all-btn {
display: inline-flex;
position: relative;
cursor: pointer;
}
.delete-all-btn .all-label {
position: absolute;
bottom: -16px;
left: 63%;
font-size: 2em;
font-weight: bold;
color: var(--secondary-text-color);
pointer-events: none;
-webkit-text-stroke: 0.5px black;
text-shadow: 0 0 2px black;
}
`;
let QueueHeader = _QueueHeader;
__decorateClass$d([
n$4()
], QueueHeader.prototype, "queueTitle");
__decorateClass$d([
n$4({ type: Number })
], QueueHeader.prototype, "itemCount");
__decorateClass$d([
n$4({ attribute: false })
], QueueHeader.prototype, "items");
__decorateClass$d([
n$4({ type: Boolean })
], QueueHeader.prototype, "selectMode");
__decorateClass$d([
n$4({ type: Boolean })
], QueueHeader.prototype, "hasSelection");
__decorateClass$d([
n$4({ type: Boolean })
], QueueHeader.prototype, "operationRunning");
__decorateClass$d([
n$4({ attribute: false })
], QueueHeader.prototype, "store");
customElements.define("mxmp-queue-header", QueueHeader);
var __defProp$c = Object.defineProperty;
var __decorateClass$c = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$c(target, key, result);
return result;
};
class MediaRow extends i$5 {
constructor() {
super(...arguments);
this.selected = false;
this.playing = false;
this.searchHighlight = false;
this.showCheckbox = false;
this.checked = false;
this.showQueueButton = false;
this.queueButtonDisabled = false;
this.showFavoriteBadge = false;
this.showLibraryBadge = false;
this.isFavorite = null;
this.favoriteLoading = false;
this.isInLibrary = null;
this.libraryLoading = false;
}
render() {
const { itemBackgroundColor, itemTextColor, selectedItemBackgroundColor, selectedItemTextColor } = this.store?.config?.queue ?? {};
const bgColor = this.selected ? selectedItemBackgroundColor : itemBackgroundColor;
const textColor = this.selected ? selectedItemTextColor : itemTextColor;
const cssVars = (bgColor ? `--secondary-background-color: ${bgColor};` : "") + (textColor ? `--secondary-text-color: ${textColor};` : "");
const hasBadges = this.showFavoriteBadge || this.showLibraryBadge || this.isFavorite !== null || this.isInLibrary !== null;
const showClickableHeart = this.isFavorite !== null;
const showClickableLibrary = this.isInLibrary !== null;
return x`
${this.showCheckbox ? x`
e2.stopPropagation()}>
` : this.showQueueButton ? x`
` : E}
${renderFavoritesItem(this.item)}
${hasBadges ? x`
${showClickableHeart ? x`
${this.favoriteLoading ? x`` : x``}
` : this.showFavoriteBadge ? x`
` : E}
${showClickableLibrary ? x`
${this.libraryLoading ? x`` : x``}
` : this.showLibraryBadge ? x`
` : E}
` : E}
`;
}
onCheckboxChange(e2) {
const checkbox = e2.target;
this.dispatchEvent(customEvent("checkbox-change", { checked: checkbox.checked }));
}
onQueueClick(e2) {
e2.stopPropagation();
this.dispatchEvent(customEvent("queue-item"));
}
onFavoriteClick(e2) {
e2.stopPropagation();
if (!this.favoriteLoading) {
this.dispatchEvent(customEvent("favorite-toggle", { isFavorite: this.isFavorite }));
}
}
onLibraryClick(e2) {
e2.stopPropagation();
if (!this.libraryLoading) {
this.dispatchEvent(customEvent("library-toggle", { isInLibrary: this.isInLibrary }));
}
}
async firstUpdated(_changedProperties) {
super.firstUpdated(_changedProperties);
await this.scrollToSelected(_changedProperties);
}
async updated(_changedProperties) {
super.updated(_changedProperties);
await this.scrollToSelected(_changedProperties);
}
async scrollToSelected(changedProperties) {
await new Promise((r2) => setTimeout(r2, 0));
const selectedChanged = changedProperties.has("selected") && this.selected;
const highlightChanged = changedProperties.has("searchHighlight") && this.searchHighlight;
if (selectedChanged || highlightChanged) {
this.scrollIntoView({ behavior: "smooth", block: "center" });
}
}
static get styles() {
return [
i$8`
:host {
display: block;
min-width: 0;
}
.mdc-deprecated-list-item__text {
width: 100%;
}
.button {
margin: 0.3rem;
border-radius: 0.3rem;
height: 25px;
padding-inline: 0.1rem;
}
.button.search-highlight {
outline: 2px solid var(--accent-color, #03a9f4);
outline-offset: -2px;
}
.row {
display: flex;
flex: 1;
align-items: center;
min-width: 0;
}
.icon-slot {
width: 36px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
ha-checkbox {
--mdc-checkbox-unchecked-color: var(--secondary-text-color);
flex-shrink: 0;
}
.queue-btn {
flex-shrink: 0;
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
}
.queue-btn.disabled {
color: var(--disabled-text-color);
cursor: default;
}
.thumbnail {
width: var(--icon-width, 20px);
height: var(--icon-width, 20px);
background-size: contain;
background-repeat: no-repeat;
background-position: left;
}
.title {
font-size: calc(var(--mxmp-font-size, 1rem) * 1.1);
align-self: center;
flex: 1;
}
.meta-content {
display: flex;
align-items: center;
gap: 4px;
padding-inline: 4px;
}
.badges {
display: flex;
align-items: center;
gap: 2px;
}
.badges > *:not(.badge-toggle) {
--mdc-icon-size: 16px;
width: 16px;
height: 16px;
opacity: 0.7;
}
.badges ha-svg-icon.accent {
color: var(--accent-color, #03a9f4);
opacity: 1;
}
.badge-toggle {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
}
.badge-toggle ha-svg-icon {
--mdc-icon-size: 16px;
width: 16px;
height: 16px;
opacity: 0.7;
}
.badge-toggle:hover ha-svg-icon {
opacity: 1;
}
.badge-toggle.loading {
pointer-events: none;
}
.badge-toggle ha-circular-progress {
--md-circular-progress-size: 14px;
}
mwc-list-item {
--mdc-list-item-meta-size: auto;
overflow: visible;
}
.mdc-deprecated-list-item__meta {
margin-right: 4px;
}
`,
mediaItemTitleStyle
];
}
}
__decorateClass$c([
n$4({ attribute: false })
], MediaRow.prototype, "store");
__decorateClass$c([
n$4({ attribute: false })
], MediaRow.prototype, "item");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "selected");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "playing");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "searchHighlight");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "showCheckbox");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "checked");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "showQueueButton");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "queueButtonDisabled");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "showFavoriteBadge");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "showLibraryBadge");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "isFavorite");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "favoriteLoading");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "isInLibrary");
__decorateClass$c([
n$4({ type: Boolean })
], MediaRow.prototype, "libraryLoading");
customElements.define("mxmp-media-row", MediaRow);
var __defProp$b = Object.defineProperty;
var __decorateClass$b = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$b(target, key, result);
return result;
};
const _QueueList = class _QueueList extends i$5 {
constructor() {
super(...arguments);
this.loading = false;
this.searchExpanded = false;
this.selectedIndex = -1;
this.searchHighlightIndex = -1;
this.selectMode = false;
this.showQueueButton = false;
this.displayItems = [];
this.shownIndices = [];
this.selectedIndices = /* @__PURE__ */ new Set();
}
scrollToItem(index) {
this.shadowRoot?.querySelectorAll("mxmp-media-row")?.[index]?.scrollIntoView({ behavior: "smooth", block: "center" });
}
render() {
return x`
${this.displayItems.map((item, index) => {
const realIndex = this.shownIndices.length > 0 ? this.shownIndices[index] : index;
const isSelected = this.selectedIndex >= 0 && realIndex === this.selectedIndex;
const isPlaying = isSelected && this.store.activePlayer.isPlaying();
const isSearchHighlight = this.searchHighlightIndex === realIndex;
const isChecked = this.selectedIndices.has(realIndex);
const queueButtonDisabled = isSelected || this.selectedIndex >= 0 && realIndex === this.selectedIndex + 1;
return x`
this.dispatchAction({ type: "item-click", payload: { displayIndex: index } })}
.item=${item}
.selected=${isSelected}
.playing=${isPlaying}
.searchHighlight=${isSearchHighlight}
.showCheckbox=${this.selectMode}
.showQueueButton=${this.showQueueButton}
.queueButtonDisabled=${queueButtonDisabled}
.checked=${isChecked}
@checkbox-change=${(e2) => this.dispatchAction({ type: "checkbox-change", payload: { realIndex, checked: e2.detail.checked } })}
@queue-item=${() => this.dispatchAction({ type: "queue-item", payload: { realIndex } })}
.store=${this.store}
>
`;
})}
`;
}
dispatchAction(action) {
this.dispatchEvent(customEvent("queue-list-action", action));
}
};
_QueueList.styles = i$8`
:host {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
}
[hidden] {
display: none !important;
}
.list {
overflow-y: auto;
overflow-x: hidden;
position: relative;
flex: 1;
--mdc-icon-button-size: 1.5em;
--mdc-icon-size: 1em;
}
.list.search-active {
padding-top: 3rem;
}
.loading {
display: flex;
justify-content: center;
padding: 2rem;
}
`;
let QueueList = _QueueList;
__decorateClass$b([
n$4({ type: Boolean })
], QueueList.prototype, "loading");
__decorateClass$b([
n$4({ type: Boolean })
], QueueList.prototype, "searchExpanded");
__decorateClass$b([
n$4({ type: Number })
], QueueList.prototype, "selectedIndex");
__decorateClass$b([
n$4({ type: Number })
], QueueList.prototype, "searchHighlightIndex");
__decorateClass$b([
n$4({ type: Boolean })
], QueueList.prototype, "selectMode");
__decorateClass$b([
n$4({ type: Boolean })
], QueueList.prototype, "showQueueButton");
__decorateClass$b([
n$4({ attribute: false })
], QueueList.prototype, "store");
__decorateClass$b([
n$4({ attribute: false })
], QueueList.prototype, "displayItems");
__decorateClass$b([
n$4({ attribute: false })
], QueueList.prototype, "shownIndices");
__decorateClass$b([
n$4({ attribute: false })
], QueueList.prototype, "selectedIndices");
customElements.define("mxmp-queue-list", QueueList);
const sectionCommonStyles = i$8`
[hidden] {
display: none !important;
}
:host {
display: flex;
flex-direction: column;
height: 100%;
}
.section-container {
display: flex;
flex-direction: column;
height: 100%;
outline: none;
position: relative;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
position: relative;
}
.header-icons {
white-space: nowrap;
display: flex;
align-items: center;
}
.header-icons > * {
display: inline-block;
}
.title-container {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
padding: 0.5rem;
}
.title {
font-size: calc(var(--mxmp-font-size, 1rem) * 1.2);
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-count {
font-size: calc(var(--mxmp-font-size, 1rem) * 0.9);
color: var(--secondary-text-color);
flex-shrink: 0;
}
.list {
overflow-y: auto;
overflow-x: hidden;
position: relative;
flex: 1;
--icon-button-size: 1.5em;
--icon-size: 1em;
}
mxmp-icon-button[selected] {
color: var(--accent-color);
}
.loading {
display: flex;
justify-content: center;
padding: 2rem;
}
.no-results {
text-align: center;
padding: 2rem;
color: var(--secondary-text-color);
}
.error-message {
text-align: center;
padding: 2rem;
color: var(--error-color, #db4437);
}
.play-menu-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
}
`;
const queueStyles = [
sectionCommonStyles,
i$8`
/* Queue uses section-container class name */
.queue-container {
display: flex;
flex-direction: column;
height: 100%;
outline: none;
position: relative;
overflow: hidden;
}
.queue-content {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
}
.list.search-active {
padding-top: 3rem;
}
.delete-all-btn {
display: inline-flex;
position: relative;
cursor: pointer;
}
.delete-all-btn .all-label {
position: absolute;
bottom: -16px;
left: 63%;
font-size: 2em;
font-weight: bold;
color: var(--secondary-text-color);
pointer-events: none;
-webkit-text-stroke: 0.5px black;
text-shadow: 0 0 2px black;
}
.error-message {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 1rem;
text-align: center;
color: var(--secondary-text-color);
}
.error-message p {
margin: 0;
line-height: 1.5;
}
`
];
const QUEUE_DEBOUNCE_MS = 500;
const MASS_CONFIG_MESSAGE = "To see the Music Assistant queue, enable useMusicAssistant (or set entityPlatform: music_assistant) in the card configuration.";
const MASS_QUEUE_MESSAGE = "The current queue is not managed by Music Assistant.";
const MASS_QUEUE_INSTALL_MESSAGE = "To show the queue for Music Assistant, install the mass_queue integration from HACS: github.com/droans/mass_queue";
function getQueueTitle(store, activePlayer) {
if (store.config.queue?.title) {
return store.config.queue.title;
}
const playlist = activePlayer.attributes.media_playlist ?? "Play Queue";
return activePlayer.attributes.media_channel ? `${playlist} (not active)` : playlist;
}
function getSelectedQueueIndex(activePlayer, currentQueueItemId, queueItems) {
if (activePlayer.attributes.queue_position) {
return activePlayer.attributes.queue_position - 1;
}
if (!currentQueueItemId) {
return -1;
}
return queueItems.findIndex((item) => item.queueItemId === currentQueueItemId);
}
function getDisplayItems(showOnlyMatches, shownIndices, queueItems) {
return showOnlyMatches ? shownIndices.map((index) => queueItems[index]) : queueItems;
}
function shouldShowConfigMessage(store, activePlayer) {
return store.config.entityPlatform !== "music_assistant" && activePlayer.attributes.media_playlist === "Music Assistant";
}
function queueNotManagedByMusicAssistant(store, activePlayer) {
return store.config.entityPlatform === "music_assistant" && activePlayer.attributes.active_queue == null;
}
function resolveQueueItemIndex(displayIndex, showOnlyMatches, shownIndices) {
if (!showOnlyMatches || shownIndices.length === 0) {
return displayIndex;
}
return shownIndices[displayIndex];
}
function shouldSwitchToPlayerSection(action) {
return action.enqueue === "replace" || action.enqueue === "play" && !action.radioMode;
}
async function fetchQueueData(store, activePlayer, forceRefresh, lastQueueHash) {
const [queueItems, currentQueueItemId] = await Promise.all([
store.hassService.getQueue(activePlayer),
store.hassService.musicAssistantService.getCurrentQueueItemId(activePlayer)
]);
const queueHash = queueItems.map((item) => item.title).join("|");
const updatedQueueItems = forceRefresh || queueHash !== lastQueueHash ? queueItems : void 0;
return { queueItems: updatedQueueItems, queueHash, currentQueueItemId, clearError: true };
}
function applyQueueSearchAction(action, searchMatchIndices, selectedIndices) {
if (action.type === "match") {
return { searchHighlightIndex: action.payload.index, searchMatchIndices: action.payload.matchIndices ?? [] };
}
if (action.type === "show-only") {
return { showOnlyMatches: action.payload.showOnlyMatches, shownIndices: action.payload.shownIndices };
}
if (action.type === "expanded") {
return { searchExpanded: action.payload.expanded };
}
if (action.type === "select-all") {
return { selectedIndices: /* @__PURE__ */ new Set([...selectedIndices, ...searchMatchIndices]) };
}
return {};
}
function updateSelection(selectedIndices, index, checked) {
const newSet = new Set(selectedIndices);
if (checked) {
newSet.add(index);
} else {
newSet.delete(index);
}
return newSet;
}
function invertSelection(selectedIndices, totalItems) {
const newSelection = /* @__PURE__ */ new Set();
for (let i5 = 0; i5 < totalItems; i5++) {
if (!selectedIndices.has(i5)) {
newSelection.add(i5);
}
}
return newSelection;
}
function clearSelection() {
return /* @__PURE__ */ new Set();
}
class QueueController {
constructor(host, getStore) {
this.host = host;
this.getStore = getStore;
this.selectMode = false;
this.searchExpanded = false;
this.searchHighlightIndex = -1;
this.searchMatchIndices = [];
this.showOnlyMatches = false;
this.shownIndices = [];
this.selectedIndices = /* @__PURE__ */ new Set();
this.queueItems = [];
this.loading = true;
this.operationProgress = null;
this.cancelOperation = false;
this.errorMessage = null;
this.currentQueueItemId = null;
this.playMenuItemIndex = null;
this.lastQueueHash = "";
this.fetchDebounceTimer = null;
this.lastActivePlayerId = null;
this.lastStoreRef = null;
host.addController(this);
}
get store() {
return this.getStore();
}
requestUpdate() {
this.host.requestUpdate();
}
dispatchEvent(event) {
return this.host.dispatchEvent(event);
}
async scrollToCurrentlyPlaying() {
await this.host.updateComplete;
await new Promise((resolve) => requestAnimationFrame(resolve));
const row = this.host.shadowRoot?.querySelectorAll("mxmp-media-row")[this.selectedQueueIndex];
row?.scrollIntoView({ behavior: "smooth", block: "center" });
}
async fetchQueue(forceRefresh = false) {
try {
this.applyFetchResult(await fetchQueueData(this.store, this.store.activePlayer, forceRefresh, this.lastQueueHash));
} catch (error) {
this.handleFetchError(error);
}
if (this.loading) {
this.loading = false;
}
this.host.requestUpdate();
}
exitSelectMode() {
this.selectMode = false;
this.selectedIndices = clearSelection();
this.playMenuItemIndex = null;
this.host.requestUpdate();
}
get queueTitle() {
return getQueueTitle(this.store, this.activePlayer);
}
get selectedQueueIndex() {
return getSelectedQueueIndex(this.activePlayer, this.currentQueueItemId, this.queueItems);
}
get displayItems() {
return getDisplayItems(this.showOnlyMatches, this.shownIndices, this.queueItems);
}
get showConfigMessage() {
return shouldShowConfigMessage(this.store, this.activePlayer);
}
get showQueueMessage() {
return queueNotManagedByMusicAssistant(this.store, this.activePlayer);
}
get hasError() {
return this.showConfigMessage || this.showQueueMessage || !!this.errorMessage;
}
hostUpdate() {
const store = this.getStore();
if (!store || store === this.lastStoreRef) {
return;
}
this.lastStoreRef = store;
this.activePlayer = store.activePlayer;
const playerChanged = store.activePlayer.id !== this.lastActivePlayerId;
if (playerChanged) {
this.lastActivePlayerId = store.activePlayer.id;
this.lastQueueHash = "";
this.loading = true;
void this.fetchQueue();
return;
}
if (this.fetchDebounceTimer) {
clearTimeout(this.fetchDebounceTimer);
}
this.fetchDebounceTimer = setTimeout(() => void this.fetchQueue(), QUEUE_DEBOUNCE_MS);
}
hostDisconnected() {
if (this.fetchDebounceTimer) {
clearTimeout(this.fetchDebounceTimer);
}
}
applyFetchResult(result) {
if (result.queueHash !== void 0) {
this.lastQueueHash = result.queueHash;
}
if (result.queueItems !== void 0) {
this.queueItems = result.queueItems;
}
if (result.currentQueueItemId !== void 0) {
this.currentQueueItemId = result.currentQueueItemId;
}
if (result.clearError && this.errorMessage !== null) {
this.errorMessage = null;
}
}
handleFetchError(error) {
if (error.message === MASS_QUEUE_NOT_INSTALLED) {
this.errorMessage = MASS_QUEUE_INSTALL_MESSAGE;
this.queueItems = [];
} else {
console.warn("Error getting queue", error);
}
}
}
async function queueItemsAfterCurrent(items, playMedia, onProgress, shouldCancel) {
const total = items.length;
for (let i5 = items.length - 1; i5 >= 0; i5--) {
if (shouldCancel()) {
return;
}
await playMedia(items[i5], "next");
onProgress(total - i5);
}
}
function getParallelBatch$1(sortedIndices, maxParallel = 50) {
if (sortedIndices.length === 0) {
return [];
}
const chunk = [sortedIndices[0]];
for (let i5 = 1; i5 < sortedIndices.length; i5++) {
if (sortedIndices[i5] === sortedIndices[i5 - 1] + 1) {
chunk.push(sortedIndices[i5]);
} else {
break;
}
}
const halfSize = Math.min(maxParallel, Math.max(1, Math.floor(chunk.length / 2)));
return chunk.slice(0, halfSize);
}
function recalculateIndicesAfterDeletion(remaining, deleted) {
if (deleted.length === 0) {
return remaining;
}
const deletedSet = new Set(deleted);
const maxDeleted = Math.max(...deleted);
const deleteCount = deleted.length;
return remaining.filter((i5) => !deletedSet.has(i5)).map((i5) => i5 > maxDeleted ? i5 - deleteCount : i5);
}
const QUEUE_REFRESH_DELAY_MS = 500;
function resetOperationState(ctrl) {
ctrl.operationProgress = null;
ctrl.cancelOperation = false;
ctrl.requestUpdate();
}
async function refreshAfterOperation(ctrl, scrollToPlaying = false) {
ctrl.exitSelectMode();
await delay(QUEUE_REFRESH_DELAY_MS);
await ctrl.fetchQueue(true);
if (scrollToPlaying) {
await ctrl.scrollToCurrentlyPlaying();
}
}
async function runBatchOperation(ctrl, operation, options2 = {}) {
ctrl.cancelOperation = false;
try {
await operation(
(completed) => {
ctrl.operationProgress = { ...ctrl.operationProgress, current: completed };
ctrl.requestUpdate();
},
() => ctrl.cancelOperation
);
if (!ctrl.cancelOperation) {
await refreshAfterOperation(ctrl, options2.scrollToPlaying);
}
} finally {
resetOperationState(ctrl);
}
}
function getSelectedIndicesExcludingCurrent(ctrl) {
const currentIndex = ctrl.selectedQueueIndex;
return Array.from(ctrl.selectedIndices).filter((index) => index !== currentIndex).sort((a2, b2) => a2 - b2);
}
async function queueSelectedAfterCurrent$1(ctrl) {
const selectedIndices = getSelectedIndicesExcludingCurrent(ctrl);
if (selectedIndices.length === 0) {
return;
}
ctrl.operationProgress = { current: 0, total: selectedIndices.length, label: "Moving" };
ctrl.requestUpdate();
await runBatchOperation(
ctrl,
(onProgress, shouldCancel) => ctrl.store.mediaControlService.moveQueueItemsAfterCurrent(
ctrl.activePlayer,
ctrl.queueItems,
selectedIndices,
ctrl.selectedQueueIndex,
onProgress,
shouldCancel
),
{ scrollToPlaying: true }
);
}
async function queueSelectedAtEnd(ctrl) {
const selectedIndices = getSelectedIndicesExcludingCurrent(ctrl);
if (selectedIndices.length === 0) {
return;
}
ctrl.operationProgress = { current: 0, total: selectedIndices.length, label: "Moving" };
ctrl.requestUpdate();
await runBatchOperation(
ctrl,
(onProgress, shouldCancel) => ctrl.store.mediaControlService.moveQueueItemsToEnd(ctrl.activePlayer, ctrl.queueItems, selectedIndices, onProgress, shouldCancel)
);
}
async function playSelected$1(ctrl) {
const selectedIndices = Array.from(ctrl.selectedIndices).sort((a2, b2) => a2 - b2);
if (selectedIndices.length === 0) {
return;
}
const items = selectedIndices.map((index) => ctrl.queueItems[index]).filter((item) => item?.media_content_id);
if (items.length === 0) {
return;
}
ctrl.operationProgress = { current: 0, total: items.length, label: "Loading" };
ctrl.requestUpdate();
await runBatchOperation(
ctrl,
(onProgress, shouldCancel) => ctrl.store.mediaControlService.queueAndPlay(ctrl.activePlayer, items, "replace", onProgress, shouldCancel),
{ scrollToPlaying: true }
);
}
async function handleItemPlayAction(ctrl, itemIndex, action) {
const item = ctrl.queueItems[itemIndex];
if (!item?.media_content_id) {
return;
}
if (action.enqueue === "replace_next" || action.radioMode) {
await ctrl.store.hassService.musicAssistantService.playMedia(ctrl.activePlayer, item.media_content_id, action.enqueue, action.radioMode);
return;
}
if (action.enqueue === "play") {
await ctrl.store.mediaControlService.playQueue(ctrl.activePlayer, itemIndex, item.queueItemId);
return;
}
await ctrl.store.mediaControlService.playMedia(ctrl.activePlayer, item, action.enqueue);
}
async function clearQueue(ctrl) {
await ctrl.store.hassService.clearQueue(ctrl.activePlayer);
ctrl.exitSelectMode();
await delay(QUEUE_REFRESH_DELAY_MS);
await ctrl.fetchQueue(true);
}
async function deleteSelected$1(ctrl) {
if (ctrl.selectedIndices.size === ctrl.queueItems.length) {
await clearQueue(ctrl);
return;
}
const remaining = [...ctrl.selectedIndices].sort((a2, b2) => a2 - b2);
ctrl.operationProgress = { current: 0, total: remaining.length, label: "Deleting" };
ctrl.cancelOperation = false;
ctrl.requestUpdate();
try {
await deleteBatch(ctrl, remaining);
} finally {
resetOperationState(ctrl);
await refreshAfterOperation(ctrl);
}
}
async function deleteBatch(ctrl, remaining) {
const total = remaining.length;
let deleted = 0;
let indices = remaining;
while (indices.length > 0 && !ctrl.cancelOperation) {
const batch = getParallelBatch$1(indices);
const results = await Promise.allSettled(
batch.map((index) => {
const queueItemId = ctrl.queueItems[index]?.queueItemId;
return ctrl.store.hassService.removeFromQueue(ctrl.activePlayer, index, queueItemId);
})
);
const succeededIndices = batch.filter((_2, i5) => results[i5].status === "fulfilled");
deleted += succeededIndices.length;
ctrl.operationProgress = { current: deleted, total, label: "Deleting" };
ctrl.requestUpdate();
if (succeededIndices.length === 0) {
break;
}
indices = recalculateIndicesAfterDeletion(indices, succeededIndices);
}
}
function handleSearchAction(ctrl, action) {
Object.assign(ctrl, applyQueueSearchAction(action, ctrl.searchMatchIndices, ctrl.selectedIndices));
ctrl.requestUpdate();
}
const headerActions = {
"toggle-select-mode": toggleSelectMode,
"invert-selection": (ctrl) => {
ctrl.selectedIndices = invertSelection(ctrl.selectedIndices, ctrl.queueItems.length);
ctrl.requestUpdate();
},
"play-selected": (ctrl) => void playSelected$1(ctrl),
"queue-selected-after-current": (ctrl) => void queueSelectedAfterCurrent$1(ctrl),
"queue-selected-at-end": (ctrl) => void queueSelectedAtEnd(ctrl),
"delete-selected": (ctrl) => void deleteSelected$1(ctrl),
"clear-queue": (ctrl) => void clearQueue(ctrl)
};
function handleHeaderAction(ctrl, action) {
headerActions[action.type](ctrl);
}
function handleListAction(ctrl, action) {
if (action.type === "checkbox-change") {
ctrl.selectedIndices = updateSelection(ctrl.selectedIndices, action.payload.realIndex, action.payload.checked);
ctrl.requestUpdate();
return;
}
if (action.type === "queue-item") {
return;
}
const realIndex = resolveQueueItemIndex(action.payload.displayIndex, ctrl.showOnlyMatches, ctrl.shownIndices);
if (ctrl.selectMode) {
ctrl.selectedIndices = updateSelection(ctrl.selectedIndices, realIndex, !ctrl.selectedIndices.has(realIndex));
ctrl.requestUpdate();
return;
}
ctrl.playMenuItemIndex = ctrl.playMenuItemIndex === realIndex ? null : realIndex;
ctrl.requestUpdate();
}
async function handlePlayMenuAction(ctrl, action) {
if (ctrl.playMenuItemIndex === null) {
return;
}
const itemIndex = ctrl.playMenuItemIndex;
ctrl.playMenuItemIndex = null;
await handleItemPlayAction(ctrl, itemIndex, action);
if (shouldSwitchToPlayerSection(action)) {
ctrl.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED));
}
ctrl.requestUpdate();
}
function handleKeyDown(ctrl, key) {
if (key !== "Escape") {
return;
}
if (ctrl.playMenuItemIndex !== null) {
ctrl.playMenuItemIndex = null;
ctrl.requestUpdate();
} else if (ctrl.selectMode) {
ctrl.exitSelectMode();
}
}
function cancelCurrentOperation(ctrl) {
ctrl.cancelOperation = true;
ctrl.requestUpdate();
}
function dismissPlayMenu(ctrl) {
ctrl.playMenuItemIndex = null;
ctrl.requestUpdate();
}
function toggleSelectMode(ctrl) {
if (ctrl.selectMode) {
ctrl.exitSelectMode();
} else {
ctrl.selectMode = true;
ctrl.selectedIndices = clearSelection();
ctrl.playMenuItemIndex = null;
ctrl.requestUpdate();
}
}
var __defProp$a = Object.defineProperty;
var __decorateClass$a = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$a(target, key, result);
return result;
};
class QueueMass extends i$5 {
constructor() {
super(...arguments);
this.ctrl = new QueueController(this, () => this.store);
}
render() {
const { ctrl } = this;
const hasSelection = ctrl.selectedIndices.size > 0;
const operationRunning = ctrl.operationProgress !== null;
const shownIndices = ctrl.showOnlyMatches ? ctrl.shownIndices : [];
return x`
handleKeyDown(ctrl, e2.key)} tabindex="-1">
cancelCurrentOperation(ctrl)}
>
handleSearchAction(ctrl, e2.detail)}
@queue-header-action=${(e2) => handleHeaderAction(ctrl, e2.detail)}
>
handleListAction(ctrl, e2.detail)}
>
`;
}
static get styles() {
return [listStyle, ...queueStyles];
}
}
__decorateClass$a([
n$4()
], QueueMass.prototype, "store");
async function queueAfterCurrent(store, activePlayer, queueItems, index, setProgress, shouldCancel, onDone) {
const item = queueItems[index];
if (!item?.media_content_id) {
return;
}
const queuePosition = activePlayer.attributes.queue_position;
const currentIndex = queuePosition ? queuePosition - 1 : -1;
setProgress({ current: 0, total: 1, label: "Moving" });
try {
await store.mediaControlService.moveQueueItemAfterCurrent(activePlayer, item, index, currentIndex);
if (!shouldCancel()) {
await onDone();
}
} finally {
setProgress(null);
}
}
async function queueSelectedAfterCurrent(store, activePlayer, queueItems, selectedIndices, setProgress, shouldCancel, onDone) {
const queuePosition = activePlayer.attributes.queue_position;
const currentIndex = queuePosition ? queuePosition - 1 : -1;
const indices = Array.from(selectedIndices).filter((i5) => i5 !== currentIndex).sort((a2, b2) => a2 - b2);
if (indices.length === 0) {
return;
}
const total = indices.length;
setProgress({ current: 0, total, label: "Moving" });
try {
await store.mediaControlService.moveQueueItemsAfterCurrent(
activePlayer,
queueItems,
indices,
currentIndex,
(completed) => setProgress({ current: completed, total, label: "Moving" }),
shouldCancel
);
if (!shouldCancel()) {
await onDone();
}
} finally {
setProgress(null);
}
}
async function playSelected(store, activePlayer, queueItems, selectedIndices, setProgress, shouldCancel, onDone) {
const indices = Array.from(selectedIndices).sort((a2, b2) => a2 - b2);
if (indices.length === 0) {
return;
}
const items = indices.map((i5) => queueItems[i5]).filter((item) => item?.media_content_id);
if (items.length === 0) {
return;
}
const total = items.length;
setProgress({ current: 0, total, label: "Loading" });
try {
await store.mediaControlService.queueAndPlay(
activePlayer,
items,
"replace",
(completed) => setProgress({ current: completed, total, label: "Loading" }),
shouldCancel
);
if (!shouldCancel()) {
await onDone();
}
} finally {
setProgress(null);
}
}
async function deleteSelected(store, activePlayer, queueItems, selectedIndices, setProgress, shouldCancel, onDone) {
if (selectedIndices.size === queueItems.length) {
await store.hassService.clearQueue(activePlayer);
await onDone();
return;
}
const indices = [...selectedIndices].sort((a2, b2) => a2 - b2);
const total = indices.length;
setProgress({ current: 0, total, label: "Deleting" });
try {
let deleted = 0;
let remaining = [...indices];
while (remaining.length > 0 && !shouldCancel()) {
const batch = getParallelBatch(remaining);
const results = await Promise.allSettled(batch.map((index) => store.hassService.removeFromQueue(activePlayer, index)));
const succeededIndices = batch.filter((_2, i5) => results[i5].status === "fulfilled");
deleted += succeededIndices.length;
setProgress({ current: deleted, total, label: "Deleting" });
if (succeededIndices.length === 0) {
console.error("All deletions in batch failed, aborting");
break;
}
remaining = recalculateIndices(remaining, succeededIndices);
}
} finally {
setProgress(null);
await onDone();
}
}
function getParallelBatch(sortedIndices) {
const MAX_PARALLEL = 50;
const chunk = [sortedIndices[0]];
for (let i5 = 1; i5 < sortedIndices.length; i5++) {
if (sortedIndices[i5] === sortedIndices[i5 - 1] + 1) {
chunk.push(sortedIndices[i5]);
} else {
break;
}
}
const halfSize = Math.min(MAX_PARALLEL, Math.max(1, Math.floor(chunk.length / 2)));
return chunk.slice(0, halfSize);
}
function recalculateIndices(remaining, deleted) {
const deletedSet = new Set(deleted);
const maxDeleted = Math.max(...deleted);
const deleteCount = deleted.length;
return remaining.filter((i5) => !deletedSet.has(i5)).map((i5) => i5 > maxDeleted ? i5 - deleteCount : i5);
}
var __defProp$9 = Object.defineProperty;
var __decorateClass$9 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$9(target, key, result);
return result;
};
class QueueSonos extends i$5 {
constructor() {
super(...arguments);
this.selectMode = false;
this.searchExpanded = false;
this.searchHighlightIndex = -1;
this.showOnlyMatches = false;
this.shownIndices = [];
this.selectedIndices = /* @__PURE__ */ new Set();
this.queueItems = [];
this.loading = true;
this.operationProgress = null;
this.cancelOperation = false;
this.lastQueueHash = "";
this.onSearchAction = (e2) => {
const a2 = e2.detail;
if (a2.type === "match") {
this.searchHighlightIndex = a2.payload.index;
} else if (a2.type === "select-all") {
this.selectedIndices = /* @__PURE__ */ new Set([...this.selectedIndices, ...a2.payload.indices]);
} else if (a2.type === "expanded") {
this.searchExpanded = a2.payload.expanded;
} else if (a2.type === "show-only") {
this.showOnlyMatches = a2.payload.showOnlyMatches;
this.shownIndices = a2.payload.shownIndices;
}
};
this.onHeaderAction = (e2) => {
const handlers = {
"toggle-select-mode": this.toggleSelectMode,
"invert-selection": this.invertSelection,
"play-selected": this.handlePlaySelected,
"queue-selected-after-current": this.handleQueueAfterCurrent,
"delete-selected": this.handleDeleteSelected,
"clear-queue": this.clearQueue
};
handlers[e2.detail.type]?.();
};
this.onListAction = (e2) => {
const a2 = e2.detail;
if (a2.type === "item-click") {
this.onItemClick(a2.payload.displayIndex);
} else if (a2.type === "checkbox-change") {
this.selectedIndices = updateSelection(this.selectedIndices, a2.payload.realIndex, a2.payload.checked);
} else if (a2.type === "queue-item") {
this.handleQueueItem(a2.payload.realIndex);
}
};
this.progressSetter = (p2) => this.operationProgress = p2;
this.cancelChecker = () => this.cancelOperation;
this.handleQueueAfterCurrent = () => this.runBatchOp(queueSelectedAfterCurrent, () => this.exitAndRefetch());
this.handlePlaySelected = () => this.runBatchOp(playSelected, () => this.exitAndRefetch());
this.handleDeleteSelected = () => this.runBatchOp(deleteSelected, () => this.exitAndRefetch());
this.onKeyDown = (e2) => {
if (e2.key === "Escape" && this.selectMode) {
this.exitSelectMode();
}
};
this.toggleSelectMode = () => {
this.selectMode = !this.selectMode;
this.selectedIndices = clearSelection();
};
this.invertSelection = () => {
this.selectedIndices = invertSelection(this.selectedIndices, this.queueItems.length);
};
this.clearQueue = async () => {
await this.store.hassService.clearQueue(this.store.activePlayer);
this.exitSelectMode();
await this.fetchQueue();
};
}
get queueTitle() {
if (this.store.config.queue?.title) {
return this.store.config.queue.title;
}
const playlist = this.store.activePlayer.attributes.media_playlist ?? "Play Queue";
return this.store.activePlayer.attributes.media_channel ? `${playlist} (not active)` : playlist;
}
get selectedQueueIndex() {
const pos = this.store.activePlayer.attributes.queue_position;
return pos ? pos - 1 : -1;
}
get displayItems() {
return this.showOnlyMatches ? this.shownIndices.map((i5) => this.queueItems[i5]) : this.queueItems;
}
willUpdate(changed) {
if (changed.has("store")) {
this.fetchQueue();
}
}
async fetchQueue() {
try {
const queue = await this.store.hassService.getQueue(this.store.activePlayer);
const hash = queue.map((item) => item.title).join("|");
if (hash !== this.lastQueueHash) {
this.lastQueueHash = hash;
this.queueItems = queue;
}
} catch (e2) {
console.warn("Error getting queue", e2);
}
this.loading = false;
}
render() {
const hasSelection = this.selectedIndices.size > 0;
const operationRunning = this.operationProgress !== null;
const shownIndices = this.showOnlyMatches ? this.shownIndices : [];
return x`
this.cancelOperation = true}
>
`;
}
async handleQueueItem(index) {
this.cancelOperation = false;
await queueAfterCurrent(
this.store,
this.store.activePlayer,
this.queueItems,
index,
this.progressSetter,
this.cancelChecker,
() => this.refetchAndScroll()
);
}
async runBatchOp(fn, onDone) {
this.cancelOperation = false;
await fn(this.store, this.store.activePlayer, this.queueItems, this.selectedIndices, this.progressSetter, this.cancelChecker, onDone);
}
async refetchAndScroll() {
await this.fetchQueue();
await this.updateComplete;
await new Promise((r2) => requestAnimationFrame(r2));
const pos = this.store.activePlayer.attributes.queue_position;
if (pos) {
this.shadowRoot?.querySelector("mxmp-queue-list")?.scrollToItem(pos - 1);
}
}
async exitAndRefetch() {
this.exitSelectMode();
await this.refetchAndScroll();
}
async onItemClick(displayIndex) {
if (this.selectMode) {
return;
}
const realIndex = this.showOnlyMatches && this.shownIndices.length > 0 ? this.shownIndices[displayIndex] : displayIndex;
await this.store.mediaControlService.playQueue(this.store.activePlayer, realIndex);
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED));
}
exitSelectMode() {
this.selectMode = false;
this.selectedIndices = clearSelection();
}
static get styles() {
return [listStyle, ...queueStyles];
}
}
__decorateClass$9([
n$4()
], QueueSonos.prototype, "store");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "selectMode");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "searchExpanded");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "searchHighlightIndex");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "showOnlyMatches");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "shownIndices");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "selectedIndices");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "queueItems");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "loading");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "operationProgress");
__decorateClass$9([
r$3()
], QueueSonos.prototype, "cancelOperation");
var __defProp$8 = Object.defineProperty;
var __decorateClass$8 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$8(target, key, result);
return result;
};
const _Queue = class _Queue extends i$5 {
render() {
const isMusicAssistant = this.store.config.entityPlatform === "music_assistant";
return isMusicAssistant ? x`` : x``;
}
};
_Queue.styles = i$8`
:host {
display: block;
height: 100%;
overflow: hidden;
}
`;
let Queue = _Queue;
__decorateClass$8([
n$4()
], Queue.prototype, "store");
const LOCAL_STORAGE_KEY = "mxmp-search-state";
const MEDIA_TYPE_ICONS = {
album: mdiAlbum,
artist: mdiAccount,
playlist: mdiPlaylistMusic,
radio: mdiRadio,
track: mdiMusic
};
function getMediaTypeIcon(mediaType) {
return MEDIA_TYPE_ICONS[mediaType] ?? mdiMusic;
}
function toMediaPlayerItem(item) {
return {
title: item.subtitle ? `${item.title} ${item.subtitle}` : item.title,
media_content_id: item.uri,
media_content_type: item.mediaType,
thumbnail: item.imageUrl
};
}
function getSearchTypeLabels(mediaTypes) {
if (mediaTypes.size === 0) {
return "all";
}
const labelMap = {
track: "tracks",
artist: "artists",
album: "albums",
playlist: "playlists",
radio: "radio"
};
return Array.from(mediaTypes).map((t2) => labelMap[t2]).filter(Boolean).join(", ");
}
function saveSearchState(mediaTypes, searchText, libraryFilter, viewMode) {
const state = {
mediaTypes: Array.from(mediaTypes),
searchText,
libraryFilter,
viewMode
};
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(state));
}
function restoreSearchState() {
try {
const saved = localStorage.getItem(LOCAL_STORAGE_KEY);
if (saved) {
const state = JSON.parse(saved);
return {
mediaTypes: state.mediaTypes ? new Set(state.mediaTypes) : /* @__PURE__ */ new Set(),
searchText: state.searchText ?? "",
libraryFilter: state.libraryFilter ?? "all",
viewMode: state.viewMode
};
}
} catch {
}
return { mediaTypes: /* @__PURE__ */ new Set(["track"]), searchText: "", libraryFilter: "all" };
}
function cycleLibraryFilter(current) {
if (current === "all") {
return "library";
}
return current === "library" ? "non-library" : "all";
}
async function toggleMassItemProperty(svc, configEntryId, item, kind) {
const currentValue = kind === "favorite" ? item.favorite : item.inLibrary;
if (currentValue) {
return kind === "favorite" ? svc.removeFromFavorites(configEntryId, item.uri, item.mediaType, item.itemId, item.provider) : svc.removeFromLibrary(configEntryId, item.uri, item.mediaType, item.itemId, item.provider);
}
return kind === "favorite" ? svc.addToFavorites(configEntryId, item.uri) : svc.addToLibrary(configEntryId, item.uri);
}
const ALL_SEARCH_TYPES = ["track", "artist", "album", "playlist", "radio"];
async function performMassSearch(svc, configEntryId, searchText, mediaTypes, libraryFilter, searchLimit) {
const typesToSearch = mediaTypes.size > 0 ? Array.from(mediaTypes) : ALL_SEARCH_TYPES;
return svc.searchMultipleTypes(configEntryId, searchText.trim(), typesToSearch, searchLimit, libraryFilter);
}
class SearchService {
constructor(host) {
this.host = host;
}
updateHost(state) {
Object.assign(this.host, state);
}
dispose() {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
scheduleSearch(searchText, mediaTypes, libraryFilter, config) {
saveSearchState(mediaTypes, searchText, libraryFilter);
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
const { autoSearchMinChars = 2, autoSearchDebounceMs = 1e3 } = config;
if (searchText.trim().length < autoSearchMinChars) {
this.updateHost({ results: [], loading: false });
return;
}
this.updateHost({ loading: true, results: [] });
this.debounceTimer = setTimeout(() => this.execute(searchText, mediaTypes, libraryFilter, config), autoSearchDebounceMs);
}
async execute(searchText, mediaTypes, libraryFilter, config) {
if (!searchText.trim() || !this.host.massConfigEntryId) {
return;
}
this.updateHost({ loading: true, error: null });
const { searchLimit = 50 } = config;
try {
const results = await performMassSearch(this.host.musicAssistantService, this.host.massConfigEntryId, searchText, mediaTypes, libraryFilter, searchLimit);
this.updateHost({ results });
} catch (e2) {
this.updateHost({ error: `Search failed: ${e2 instanceof Error ? e2.message : "Unknown error"}`, results: [] });
} finally {
this.updateHost({ loading: false });
}
}
clear(mediaTypes, libraryFilter) {
this.dispose();
this.debounceTimer = void 0;
this.updateHost({ results: [], loading: false, error: null });
saveSearchState(mediaTypes, "", libraryFilter);
}
}
const searchStyles = [
sectionCommonStyles,
i$8`
/* Search uses section-container class name */
.search-container {
display: flex;
flex-direction: column;
height: 100%;
outline: none;
position: relative;
overflow: hidden;
}
.search-content {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
}
.media-type-icons {
display: flex;
gap: 0;
align-items: center;
}
.media-type-icons mxmp-icon-button[selected] {
color: var(--accent-color);
}
.separator {
width: 1px;
height: 24px;
background: var(--divider-color, rgba(255, 255, 255, 0.12));
margin: 0 4px;
}
.library-filter-btn {
display: inline-flex;
position: relative;
cursor: pointer;
}
.library-filter-btn mxmp-icon-button[selected] {
color: var(--accent-color);
}
.library-filter-btn .overlay-icon {
position: absolute;
bottom: 2px;
right: 2px;
width: 14px;
height: 14px;
fill: var(--accent-color);
pointer-events: none;
}
.search-bar {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--secondary-background-color);
margin: 0 0.5rem;
border-radius: 0.5rem;
}
.search-bar input {
flex: 1;
border: none;
background: transparent;
color: var(--primary-text-color);
font-size: 1rem;
outline: none;
padding: 0.5rem;
}
.search-bar input::placeholder {
color: var(--secondary-text-color);
}
.config-required {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 2rem;
text-align: center;
color: var(--secondary-text-color);
}
.config-required ha-icon {
--mdc-icon-size: 48px;
margin-bottom: 1rem;
opacity: 0.5;
}
.config-required .title {
font-size: 1.2rem;
margin-bottom: 0.5rem;
color: var(--primary-text-color);
}
.browse-header {
display: flex;
align-items: center;
gap: 0;
padding: 0 0.25rem;
}
.browse-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 1rem;
font-weight: 500;
}
.type-indicator {
opacity: 0.4;
pointer-events: none;
}
.filter-menu-anchor {
position: relative;
display: inline-flex;
}
.filter-menu-anchor mxmp-icon-button[selected] {
color: var(--accent-color);
}
.filter-menu {
position: absolute;
top: 100%;
right: 0;
z-index: 10;
background: var(--card-background-color, var(--primary-background-color));
border: 1px solid var(--divider-color, rgba(255, 255, 255, 0.12));
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
min-width: 180px;
padding: 4px 0;
}
.filter-menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
cursor: pointer;
color: var(--primary-text-color);
font-size: 0.9rem;
}
.filter-menu-item:hover {
background: var(--secondary-background-color);
}
.filter-menu-item ha-svg-icon {
--mdc-icon-size: 20px;
flex-shrink: 0;
}
.filter-menu-item span {
flex: 1;
}
.filter-menu-item .check {
--mdc-icon-size: 18px;
color: var(--accent-color);
}
.filter-menu-divider {
height: 1px;
background: var(--divider-color, rgba(255, 255, 255, 0.12));
margin: 4px 0;
}
.filter-menu-done {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
cursor: pointer;
color: var(--accent-color);
font-size: 0.9rem;
font-weight: 500;
}
.filter-menu-done:hover {
background: var(--secondary-background-color);
}
`
];
const searchResultsStyles = [sectionCommonStyles];
var __defProp$7 = Object.defineProperty;
var __decorateClass$7 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$7(target, key, result);
return result;
};
const LIBRARY_LABELS$1 = {
all: "All",
library: "Library only",
"non-library": "Non-library only"
};
class SearchFilterMenu extends i$5 {
constructor() {
super(...arguments);
this.overflowIcons = [];
this.libraryFilter = "all";
}
render() {
const mediaTypeOverflows = this.overflowIcons.filter((i5) => i5.type !== "library-filter");
const hasLibraryFilter = this.overflowIcons.some((i5) => i5.type === "library-filter");
return x`
`;
}
dispatch(action) {
this.dispatchEvent(customEvent("filter-action", action));
}
static get styles() {
return i$8`
[hidden] {
display: none !important;
}
.filter-menu {
position: absolute;
top: 100%;
right: 0;
z-index: 10;
background: var(--card-background-color, var(--primary-background-color));
border: 1px solid var(--divider-color, rgba(255, 255, 255, 0.12));
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
min-width: 180px;
padding: 4px 0;
}
.filter-menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
cursor: pointer;
color: var(--primary-text-color);
font-size: 0.9rem;
}
.filter-menu-item:hover {
background: var(--secondary-background-color);
}
.filter-menu-item ha-svg-icon {
--mdc-icon-size: 20px;
flex-shrink: 0;
}
.filter-menu-item span {
flex: 1;
}
.filter-menu-item .check {
--mdc-icon-size: 18px;
color: var(--accent-color);
}
.filter-menu-divider {
height: 1px;
background: var(--divider-color, rgba(255, 255, 255, 0.12));
margin: 4px 0;
}
.filter-menu-done {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
cursor: pointer;
color: var(--accent-color);
font-size: 0.9rem;
font-weight: 500;
}
.filter-menu-done:hover {
background: var(--secondary-background-color);
}
`;
}
}
__decorateClass$7([
n$4({ attribute: false })
], SearchFilterMenu.prototype, "overflowIcons");
__decorateClass$7([
n$4({ attribute: false })
], SearchFilterMenu.prototype, "mediaTypes");
__decorateClass$7([
n$4()
], SearchFilterMenu.prototype, "libraryFilter");
customElements.define("mxmp-search-filter-menu", SearchFilterMenu);
var __defProp$6 = Object.defineProperty;
var __decorateClass$6 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$6(target, key, result);
return result;
};
const ALL_HEADER_ICONS = [
{ type: "track", icon: mdiMusic, title: "Tracks" },
{ type: "artist", icon: mdiAccount, title: "Artists" },
{ type: "playlist", icon: mdiPlaylistMusic, title: "Playlists" },
{ type: "album", icon: mdiAlbum, title: "Albums" },
{ type: "radio", icon: mdiRadio, title: "Radio" },
{ type: "library-filter", icon: mdiBookshelf, title: "Library filter" }
];
const ICON_BUTTON_WIDTH = 48;
const LIBRARY_LABELS = {
all: "All sources",
library: "Library only",
"non-library": "Non-library only"
};
class SearchHeader extends i$5 {
constructor() {
super(...arguments);
this.title = "Search";
this.selectMode = false;
this.hasSelection = false;
this.operationProgress = null;
this.libraryFilter = "all";
this.viewMode = "list";
this.filterMenuOpen = false;
this.visibleCount = ALL_HEADER_ICONS.length;
}
firstUpdated() {
this.resizeObserver = new ResizeObserver(() => this.calculateVisibleCount());
this.resizeObserver.observe(this);
requestAnimationFrame(() => this.calculateVisibleCount());
}
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver?.disconnect();
}
calculateVisibleCount() {
const hostWidth = this.clientWidth;
if (!hostWidth) {
return;
}
const headerPadding = 16;
const titleMinWidth = 60;
const fixedButtonsWidth = ICON_BUTTON_WIDTH * 2;
const total = ALL_HEADER_ICONS.length;
const availableWithoutDots = hostWidth - headerPadding - titleMinWidth - fixedButtonsWidth;
if (availableWithoutDots >= total * ICON_BUTTON_WIDTH) {
if (this.visibleCount !== total) {
this.visibleCount = total;
}
return;
}
const dotsAndSep = ICON_BUTTON_WIDTH + 9;
const available = hostWidth - headerPadding - titleMinWidth - fixedButtonsWidth - dotsAndSep;
const count = Math.max(0, Math.min(total, Math.floor(available / ICON_BUTTON_WIDTH)));
if (this.visibleCount !== count) {
this.visibleCount = count;
}
}
get visibleIcons() {
return ALL_HEADER_ICONS.slice(0, this.visibleCount);
}
get overflowIcons() {
return ALL_HEADER_ICONS.slice(this.visibleCount);
}
isIconActive(icon) {
if (icon.type === "library-filter") {
return this.libraryFilter !== "all";
}
return this.mediaTypes.has(icon.type);
}
onIconClick(icon) {
if (icon.type === "library-filter") {
this.dispatch({ type: "toggle-library-filter" });
} else {
this.dispatch({ type: "toggle-media-type", mediaType: icon.type });
}
}
getIconTitle(icon) {
if (icon.type === "library-filter") {
return LIBRARY_LABELS[this.libraryFilter];
}
return `Search ${icon.title}`;
}
render() {
const hasOverflow = this.overflowIcons.length > 0;
return x`
`;
}
handleFilterAction(e2) {
const action = e2.detail;
if (action.type === "close") {
this.filterMenuOpen = false;
return;
}
this.dispatch(action);
}
dispatch(action) {
this.dispatchEvent(customEvent("header-action", action));
}
static get styles() {
return i$8`
:host {
display: block;
flex-shrink: 0;
}
[hidden] {
display: none !important;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem;
}
.title-container {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
padding: 0.5rem;
}
.title {
font-size: calc(var(--mxmp-font-size, 1rem) * 1.2);
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header-icons {
display: flex;
align-items: center;
}
.media-type-icons {
display: flex;
gap: 0;
align-items: center;
}
.media-type-icons mxmp-icon-button[selected] {
color: var(--accent-color);
}
.separator {
width: 1px;
height: 24px;
background: var(--divider-color, rgba(255, 255, 255, 0.12));
margin: 0 4px;
}
.filter-menu-anchor {
position: relative;
display: inline-flex;
}
.filter-menu-anchor mxmp-icon-button[selected] {
color: var(--accent-color);
}
`;
}
}
__decorateClass$6([
n$4()
], SearchHeader.prototype, "title");
__decorateClass$6([
n$4({ attribute: false })
], SearchHeader.prototype, "mediaTypes");
__decorateClass$6([
n$4({ type: Boolean })
], SearchHeader.prototype, "selectMode");
__decorateClass$6([
n$4({ type: Boolean })
], SearchHeader.prototype, "hasSelection");
__decorateClass$6([
n$4({ attribute: false })
], SearchHeader.prototype, "operationProgress");
__decorateClass$6([
n$4()
], SearchHeader.prototype, "libraryFilter");
__decorateClass$6([
n$4()
], SearchHeader.prototype, "viewMode");
__decorateClass$6([
r$3()
], SearchHeader.prototype, "filterMenuOpen");
__decorateClass$6([
r$3()
], SearchHeader.prototype, "visibleCount");
customElements.define("mxmp-search-header", SearchHeader);
var __defProp$5 = Object.defineProperty;
var __decorateClass$5 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$5(target, key, result);
return result;
};
class SearchBar extends i$5 {
constructor() {
super(...arguments);
this.searchText = "";
}
render() {
const typeLabels = getSearchTypeLabels(this.mediaTypes);
return x`
this.dispatchEvent(customEvent("search-submit"))}>
this.dispatchEvent(customEvent("clear-search"))}
title="Clear"
?hidden=${!this.searchText}
>
`;
}
focusInput() {
this.updateComplete.then(() => this.input?.focus());
}
onInput(e2) {
const input = e2.target;
this.dispatchEvent(customEvent("search-input", input.value));
}
onKeyDown(e2) {
if (e2.key === "Enter") {
this.dispatchEvent(customEvent("search-submit"));
}
}
static get styles() {
return i$8`
:host {
display: block;
flex-shrink: 0;
}
[hidden] {
display: none !important;
}
.search-bar {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--secondary-background-color);
margin: 0 0.5rem;
border-radius: 0.5rem;
}
.search-bar input {
flex: 1;
border: none;
background: transparent;
color: var(--primary-text-color);
font-size: 1rem;
outline: none;
padding: 0.5rem;
}
.search-bar input::placeholder {
color: var(--secondary-text-color);
}
`;
}
}
__decorateClass$5([
n$4()
], SearchBar.prototype, "searchText");
__decorateClass$5([
n$4({ attribute: false })
], SearchBar.prototype, "mediaTypes");
__decorateClass$5([
e$2("input")
], SearchBar.prototype, "input");
customElements.define("mxmp-search-bar", SearchBar);
function getSelectedItems(results, selectedIndices) {
return Array.from(selectedIndices).sort((a2, b2) => a2 - b2).map((i5) => toMediaPlayerItem(results[i5]));
}
async function executeBatchPlay(store, musicAssistantService, items, firstSelectedItem, enqueue, radioMode, callbacks) {
if (radioMode) {
await musicAssistantService.playMedia(store.activePlayer, firstSelectedItem.uri, enqueue, true);
callbacks.onComplete();
return;
}
await runBatch(
(onProgress, shouldCancel) => store.mediaControlService.queueAndPlay(store.activePlayer, items, enqueue === "replace" ? "replace" : "play", onProgress, shouldCancel),
{ current: 0, total: items.length, label: "Loading" },
callbacks
);
}
async function executeBatchQueue(store, items, playMode, callbacks) {
await runBatch(
(onProgress, shouldCancel) => queueItemsAfterCurrent(items, (item) => store.mediaControlService.playMedia(store.activePlayer, item, playMode), onProgress, shouldCancel),
{ current: 0, total: items.length, label: "Queueing" },
callbacks
);
}
async function runBatch(operation, initialProgress, callbacks) {
callbacks.setProgress(initialProgress);
try {
await operation((completed) => callbacks.setProgress({ ...initialProgress, current: completed }), callbacks.shouldCancel);
if (!callbacks.shouldCancel()) {
callbacks.onComplete();
}
} finally {
callbacks.setProgress(null);
}
}
var __defProp$4 = Object.defineProperty;
var __decorateClass$4 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$4(target, key, result);
return result;
};
class SearchResults extends i$5 {
constructor() {
super(...arguments);
this.results = [];
this.loading = false;
this.error = null;
this.selectMode = false;
this.searchText = "";
this.viewMode = "list";
this.massQueueConfigEntryId = "";
this.selectedIndices = /* @__PURE__ */ new Set();
this.favoriteLoadingIndices = /* @__PURE__ */ new Set();
this.libraryLoadingIndices = /* @__PURE__ */ new Set();
this.playMenuItemIndex = null;
this.operationProgress = null;
this.cancelOperation = false;
}
render() {
const hasContent = !this.loading && !this.error && this.results.length > 0;
const autoSearchMinChars = this.store.config.search?.autoSearchMinChars ?? 2;
const tooShort = this.searchText && this.searchText.trim().length < autoSearchMinChars;
const noResults = !this.loading && this.results.length === 0 && this.searchText && !tooShort;
const emptyPrompt = !this.loading && this.results.length === 0 && !this.searchText;
return x`
this.cancelOperation = true}
>
${this.error}
Type at least ${autoSearchMinChars} characters to search
No results found
Enter a search term
${this.viewMode === "grid" ? this.renderGrid(hasContent) : this.renderList(hasContent)}
`;
}
renderList(hasContent) {
return x`
${this.results.map((item, index) => {
const mediaPlayerItem = toMediaPlayerItem(item);
return x`
this.onItemClick(index)}
.item=${mediaPlayerItem}
.showCheckbox=${this.selectMode}
.checked=${this.selectedIndices.has(index)}
.isFavorite=${item.favorite ?? null}
.favoriteLoading=${this.favoriteLoadingIndices.has(index)}
.isInLibrary=${item.inLibrary ?? null}
.libraryLoading=${this.libraryLoadingIndices.has(index)}
@checkbox-change=${(e2) => this.onCheckboxChange(index, e2.detail.checked)}
@favorite-toggle=${() => this.toggleItemState(index, "favorite")}
@library-toggle=${() => this.toggleItemState(index, "library")}
.store=${this.store}
>
`;
})}
`;
}
renderGrid(hasContent) {
const columns = this.store.config.search?.gridColumns ?? 4;
return x`
`;
}
get hasSelection() {
return this.selectedIndices.size > 0;
}
handleInvertSelection() {
this.selectedIndices = invertSelection(this.selectedIndices, this.results.length);
}
clearSelectionState() {
this.selectedIndices = clearSelection();
this.playMenuItemIndex = null;
}
async executeSelectionAction(detail) {
const enqueue = detail.enqueue ?? "play";
const radioMode = detail.radioMode ?? false;
const items = getSelectedItems(this.results, this.selectedIndices);
if (items.length === 0) {
return;
}
this.cancelOperation = false;
const callbacks = {
setProgress: (p2) => this.operationProgress = p2,
shouldCancel: () => this.cancelOperation,
onComplete: () => {
this.selectedIndices = clearSelection();
this.dispatchEvent(customEvent("has-selection-change", false));
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED));
}
};
if (enqueue === "next" || enqueue === "replace_next" || enqueue === "add") {
await executeBatchQueue(this.store, items, enqueue === "add" ? "add" : "next", callbacks);
} else {
const firstIndex = Array.from(this.selectedIndices).sort((a2, b2) => a2 - b2)[0];
await executeBatchPlay(this.store, this.musicAssistantService, items, this.results[firstIndex], enqueue, radioMode, callbacks);
}
}
onItemClick(index) {
const item = this.results[index];
if (!item) {
return;
}
if (this.selectMode) {
this.selectedIndices = updateSelection(this.selectedIndices, index, !this.selectedIndices.has(index));
this.dispatchEvent(customEvent("has-selection-change", this.selectedIndices.size > 0));
return;
}
if (item.mediaType === "album" || item.mediaType === "playlist" || item.mediaType === "artist") {
this.dispatchEvent(customEvent("browse-collection", item));
return;
}
this.playMenuItemIndex = this.playMenuItemIndex === index ? null : index;
}
async handleItemPlayAction(e2) {
const item = this.results[this.playMenuItemIndex];
if (!item) {
return;
}
this.playMenuItemIndex = null;
await this.musicAssistantService.playMedia(this.store.activePlayer, item.uri, e2.detail.enqueue, e2.detail.radioMode);
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED));
}
onCheckboxChange(index, checked) {
this.selectedIndices = updateSelection(this.selectedIndices, index, checked);
this.dispatchEvent(customEvent("has-selection-change", this.selectedIndices.size > 0));
}
async toggleItemState(index, kind) {
if (!this.massQueueConfigEntryId || !this.results[index]) {
return;
}
const item = this.results[index];
this.setItemLoading(index, kind, true);
try {
const success = await toggleMassItemProperty(this.musicAssistantService, this.massQueueConfigEntryId, item, kind);
if (success) {
const currentValue = kind === "favorite" ? item.favorite : item.inLibrary;
const newResults = [...this.results];
newResults[index] = { ...item, [kind === "favorite" ? "favorite" : "inLibrary"]: !currentValue };
this.dispatchEvent(customEvent("results-updated", newResults));
}
} finally {
this.setItemLoading(index, kind, false);
}
}
setItemLoading(index, kind, loading) {
const prop = kind === "favorite" ? "favoriteLoadingIndices" : "libraryLoadingIndices";
const next = new Set(this[prop]);
if (loading) {
next.add(index);
} else {
next.delete(index);
}
this[prop] = next;
}
static get styles() {
return [
...searchResultsStyles,
i$8`
:host {
flex: 1;
min-height: 0;
position: relative;
overflow: hidden;
}
.grid-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.grid {
display: grid;
gap: 0.75rem;
padding: 0.75rem;
}
.grid-tile {
cursor: pointer;
border-radius: 8px;
background: var(--secondary-background-color);
position: relative;
transition: outline 0.15s;
overflow: hidden;
min-width: 0;
}
.grid-tile:hover {
outline: 2px solid var(--divider-color, rgba(255, 255, 255, 0.2));
}
.grid-tile.selected {
outline: 2px solid var(--accent-color);
}
.grid-checkbox {
position: absolute;
top: 4px;
left: 4px;
z-index: 1;
--mdc-checkbox-unchecked-color: var(--secondary-text-color);
}
.grid-img {
width: 100%;
aspect-ratio: 1 / 1;
display: block;
object-fit: cover;
background-color: var(--primary-background-color);
border-radius: 8px 8px 0 0;
}
.grid-placeholder {
width: 100%;
aspect-ratio: 1 / 1;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--primary-background-color);
border-radius: 8px 8px 0 0;
}
.grid-placeholder ha-svg-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
--mdc-icon-size: 40px;
color: var(--secondary-text-color);
opacity: 0.4;
}
.grid-info {
padding: 8px;
}
.grid-title {
font-size: calc(var(--mxmp-font-size, 1rem) * 0.85);
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--primary-text-color);
}
.grid-subtitle {
font-size: calc(var(--mxmp-font-size, 1rem) * 0.75);
color: var(--secondary-text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
`
];
}
}
__decorateClass$4([
n$4({ attribute: false })
], SearchResults.prototype, "store");
__decorateClass$4([
n$4({ attribute: false })
], SearchResults.prototype, "results");
__decorateClass$4([
n$4({ type: Boolean })
], SearchResults.prototype, "loading");
__decorateClass$4([
n$4()
], SearchResults.prototype, "error");
__decorateClass$4([
n$4({ type: Boolean })
], SearchResults.prototype, "selectMode");
__decorateClass$4([
n$4()
], SearchResults.prototype, "searchText");
__decorateClass$4([
n$4()
], SearchResults.prototype, "viewMode");
__decorateClass$4([
n$4({ attribute: false })
], SearchResults.prototype, "musicAssistantService");
__decorateClass$4([
n$4()
], SearchResults.prototype, "massQueueConfigEntryId");
__decorateClass$4([
r$3()
], SearchResults.prototype, "selectedIndices");
__decorateClass$4([
r$3()
], SearchResults.prototype, "favoriteLoadingIndices");
__decorateClass$4([
r$3()
], SearchResults.prototype, "libraryLoadingIndices");
__decorateClass$4([
r$3()
], SearchResults.prototype, "playMenuItemIndex");
__decorateClass$4([
r$3()
], SearchResults.prototype, "operationProgress");
__decorateClass$4([
r$3()
], SearchResults.prototype, "cancelOperation");
customElements.define("mxmp-search-results", SearchResults);
var __defProp$3 = Object.defineProperty;
var __decorateClass$3 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$3(target, key, result);
return result;
};
class SearchBrowseView extends i$5 {
constructor() {
super(...arguments);
this.item = null;
this.massQueueConfigEntryId = "";
this.browseResults = [];
this.browseLoading = false;
}
willUpdate(changedProperties) {
if (changedProperties.has("item") && this.item) {
this.loadCollection();
}
}
render() {
if (!this.item) {
return x``;
}
const typeIcon = getMediaTypeIcon(this.item.mediaType);
return x`
0}>No items found
${this.browseResults.map((browseItem, index) => {
const mediaPlayerItem = toMediaPlayerItem(browseItem);
return x` this.onBrowseItemClick(index)} .item=${mediaPlayerItem} .store=${this.store}> `;
})}
`;
}
async loadCollection() {
this.browseLoading = true;
this.browseResults = [];
try {
this.browseResults = await this.musicAssistantService.getCollectionItems(this.item.uri, this.item.mediaType, this.massQueueConfigEntryId);
} catch (e2) {
console.error("Failed to browse collection:", e2);
} finally {
this.browseLoading = false;
}
}
goBack() {
this.browseResults = [];
this.browseLoading = false;
this.dispatchEvent(customEvent("go-back"));
}
async onBrowseItemClick(index) {
const browseItem = this.browseResults[index];
if (!browseItem) {
return;
}
await this.store.mediaControlService.playMedia(this.store.activePlayer, toMediaPlayerItem(browseItem), "play");
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED));
}
handlePlayMenuAction(e2) {
const actions = [
{ enqueue: "replace" },
{ enqueue: "play", radioMode: true },
{ enqueue: "play" },
{ enqueue: "next" },
{ enqueue: "add" },
{ enqueue: "replace_next" }
];
const action = actions[parseInt(e2.detail.item.value)];
if (action && this.item) {
this.musicAssistantService.playMedia(this.store.activePlayer, this.item.uri, action.enqueue, action.radioMode);
this.dispatchEvent(customEvent(MEDIA_ITEM_SELECTED));
}
}
static get styles() {
return [
listStyle,
i$8`
:host {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
}
[hidden] {
display: none !important;
}
.browse-header {
display: flex;
align-items: center;
gap: 0;
padding: 0 0.25rem;
}
.browse-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 1rem;
font-weight: 500;
}
.type-indicator {
opacity: 0.4;
pointer-events: none;
}
.loading {
display: flex;
justify-content: center;
padding: 2rem;
}
.no-results {
text-align: center;
padding: 2rem;
color: var(--secondary-text-color);
}
.list {
overflow-y: auto;
overflow-x: hidden;
flex: 1;
min-height: 0;
}
`
];
}
}
__decorateClass$3([
n$4({ attribute: false })
], SearchBrowseView.prototype, "store");
__decorateClass$3([
n$4({ attribute: false })
], SearchBrowseView.prototype, "item");
__decorateClass$3([
n$4({ attribute: false })
], SearchBrowseView.prototype, "musicAssistantService");
__decorateClass$3([
n$4()
], SearchBrowseView.prototype, "massQueueConfigEntryId");
__decorateClass$3([
r$3()
], SearchBrowseView.prototype, "browseResults");
__decorateClass$3([
r$3()
], SearchBrowseView.prototype, "browseLoading");
customElements.define("mxmp-search-browse-view", SearchBrowseView);
var __defProp$2 = Object.defineProperty;
var __decorateClass$2 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$2(target, key, result);
return result;
};
class Search extends i$5 {
constructor() {
super(...arguments);
this.mediaTypes = /* @__PURE__ */ new Set();
this.searchText = "";
this.libraryFilter = "all";
this.results = [];
this.loading = false;
this.error = null;
this.discoveryComplete = false;
this.browsingItem = null;
this.selectMode = false;
this.hasSelection = false;
this.viewMode = "list";
}
connectedCallback() {
super.connectedCallback();
const restored = restoreSearchState();
Object.assign(this, restored);
if (!restored.viewMode) {
this.viewMode = this.store?.config?.search?.defaultViewMode ?? "list";
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.searchService?.dispose();
}
willUpdate(changedProperties) {
if (changedProperties.has("store") && !this.searchService) {
this.musicAssistantService = new MusicAssistantService(this.store.hass);
this.searchService = new SearchService(this);
this.discoverConfigEntry();
const { defaultMediaType } = this.searchConfig;
if (this.mediaTypes.size === 0 && defaultMediaType && defaultMediaType !== "none") {
this.mediaTypes = /* @__PURE__ */ new Set([defaultMediaType]);
}
}
}
async discoverConfigEntry() {
const { massConfigEntryId: configuredId } = this.searchConfig;
this.massConfigEntryId = configuredId ?? await this.musicAssistantService.discoverConfigEntryId();
this.massQueueConfigEntryId = await this.musicAssistantService.discoverMassQueueConfigEntryId();
this.discoveryComplete = true;
if (this.searchText && this.massConfigEntryId) {
this.searchService.execute(this.searchText, this.mediaTypes, this.libraryFilter, this.searchConfig);
}
}
render() {
if (this.store.config.entityPlatform !== "music_assistant") {
return x`
Music Assistant Required
Search requires entityPlatform: music_assistant in the card configuration.
`;
}
if (!this.discoveryComplete) {
return x``;
}
if (!this.massConfigEntryId) {
return x`
Music Assistant Not Found
Could not discover Music Assistant. Configure massConfigEntryId in search settings.
`;
}
const { title } = this.searchConfig;
return x`
this.browsingItem = null}
>
this.onSearchInput(e2.detail)}
@search-submit=${() => this.searchService.execute(this.searchText, this.mediaTypes, this.libraryFilter, this.searchConfig)}
@clear-search=${this.clearSearch}
>
this.browsingItem = e2.detail}
@has-selection-change=${(e2) => this.hasSelection = e2.detail}
@results-updated=${(e2) => this.results = e2.detail}
>
`;
}
get searchConfig() {
return this.store.config.search ?? {};
}
handleHeaderAction({ detail }) {
if (detail.type === "toggle-media-type") {
this.toggleMediaType(detail.mediaType);
} else if (detail.type === "toggle-select-mode") {
this.toggleSelectMode();
} else if (detail.type === "toggle-library-filter") {
this.handleToggleLibraryFilter();
} else if (detail.type === "toggle-view-mode") {
this.toggleViewMode();
} else if (detail.type === "invert-selection") {
this.searchResults?.handleInvertSelection();
} else if (detail.type === "selection-action") {
this.searchResults?.executeSelectionAction(detail.action);
}
}
toggleMediaType(type) {
const newTypes = new Set(this.mediaTypes);
if (newTypes.has(type)) {
newTypes.delete(type);
} else {
newTypes.add(type);
}
this.mediaTypes = newTypes;
this.searchService.scheduleSearch(this.searchText, this.mediaTypes, this.libraryFilter, this.searchConfig);
this.searchBar?.focusInput();
}
handleToggleLibraryFilter() {
this.libraryFilter = cycleLibraryFilter(this.libraryFilter);
this.searchService.scheduleSearch(this.searchText, this.mediaTypes, this.libraryFilter, this.searchConfig);
}
toggleSelectMode() {
this.selectMode = !this.selectMode;
this.searchResults?.clearSelectionState();
this.hasSelection = false;
}
toggleViewMode() {
this.viewMode = this.viewMode === "list" ? "grid" : "list";
saveSearchState(this.mediaTypes, this.searchText, this.libraryFilter, this.viewMode);
}
onSearchInput(value) {
this.searchText = value;
this.searchService.scheduleSearch(this.searchText, this.mediaTypes, this.libraryFilter, this.searchConfig);
}
clearSearch() {
this.searchService.clear(this.mediaTypes, this.libraryFilter);
this.searchText = "";
this.searchBar?.focusInput();
}
onKeyDown(e2) {
if (e2.key === "Escape" && this.selectMode) {
this.toggleSelectMode();
}
}
static get styles() {
return searchStyles;
}
}
__decorateClass$2([
n$4()
], Search.prototype, "store");
__decorateClass$2([
r$3()
], Search.prototype, "mediaTypes");
__decorateClass$2([
r$3()
], Search.prototype, "searchText");
__decorateClass$2([
r$3()
], Search.prototype, "libraryFilter");
__decorateClass$2([
r$3()
], Search.prototype, "results");
__decorateClass$2([
r$3()
], Search.prototype, "loading");
__decorateClass$2([
r$3()
], Search.prototype, "error");
__decorateClass$2([
r$3()
], Search.prototype, "massConfigEntryId");
__decorateClass$2([
r$3()
], Search.prototype, "massQueueConfigEntryId");
__decorateClass$2([
r$3()
], Search.prototype, "discoveryComplete");
__decorateClass$2([
r$3()
], Search.prototype, "browsingItem");
__decorateClass$2([
r$3()
], Search.prototype, "selectMode");
__decorateClass$2([
r$3()
], Search.prototype, "hasSelection");
__decorateClass$2([
r$3()
], Search.prototype, "viewMode");
__decorateClass$2([
e$2("mxmp-search-results")
], Search.prototype, "searchResults");
__decorateClass$2([
e$2("mxmp-search-bar")
], Search.prototype, "searchBar");
var __defProp$1 = Object.defineProperty;
var __decorateClass$1 = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp$1(target, key, result);
return result;
};
class SleepTimer extends i$5 {
render() {
const hassService = this.store.hassService;
if (this.player.attributes.platform !== "sonos") {
return x``;
}
return x`
hassService.setSleepTimer(this.player, this.sleepTimer.valueAsNumber)}
>
hassService.cancelSleepTimer(this.player)}>
`;
}
static get styles() {
return i$8`
#sleepTimer {
display: flex;
color: var(--primary-text-color);
gap: 0.5em;
}
#sleepTimerAlarm {
color: var(--paper-item-icon-color);
}
#sleepTimerSubmit {
color: var(--accent-color);
}
#sleepTimer > label {
align-content: center;
flex: 2;
}
`;
}
}
__decorateClass$1([
n$4({ attribute: false })
], SleepTimer.prototype, "store");
__decorateClass$1([
n$4({ attribute: false })
], SleepTimer.prototype, "player");
__decorateClass$1([
e$2("#sleepTimerInput")
], SleepTimer.prototype, "sleepTimer");
customElements.define("mxmp-sleep-timer", SleepTimer);
var __defProp = Object.defineProperty;
var __decorateClass = (decorators, target, key, kind) => {
var result = void 0;
for (var i5 = decorators.length - 1, decorator; i5 >= 0; i5--)
if (decorator = decorators[i5])
result = decorator(target, key, result) || result;
if (result) __defProp(target, key, result);
return result;
};
class Volumes extends i$5 {
constructor() {
super(...arguments);
this.showSwitches = {};
}
render() {
const members = this.store.activePlayer.members;
const showAll = members.length > 1;
return x`
${showAll ? this.volumeWithName(this.store.activePlayer) : E}
${members.map((member) => this.volumeWithName(member, false))}
`;
}
volumeWithName(player, updateMembers = true) {
const { labelForAllSlider, hideCogwheel } = this.store.config.volumes ?? {};
const { showVolumeUpAndDownButtons } = this.store.config.player ?? {};
const name = updateMembers ? labelForAllSlider ?? "All" : player.name;
const volDown = async () => await this.store.mediaControlService.volumeDown(player, updateMembers);
const volUp = async () => await this.store.mediaControlService.volumeUp(player, updateMembers);
const hideSwitches = updateMembers || !this.showSwitches[player.id];
return x`
this.toggleShowSwitches(player)}
.path=${mdiCog}
show-switches=${this.showSwitches[player.id] || E}
>
${m(this.getAdditionalControls(hideSwitches, player))}
`;
}
toggleShowSwitches(player) {
this.showSwitches[player.id] = !this.showSwitches[player.id];
this.requestUpdate();
}
async getAdditionalControls(hide, player) {
if (hide) {
return [];
}
const relatedEntities = await this.store.hassService.getRelatedEntities(player, "switch", "number", "sensor");
const { additionalControlsFontSize: fontSize = 0.75 } = this.store.config.volumes ?? {};
return relatedEntities.map((relatedEntity) => {
relatedEntity.attributes.friendly_name = relatedEntity.attributes.friendly_name?.replaceAll(player.name, "")?.trim() ?? "";
return x`
`;
});
}
static get styles() {
return i$8`
.row {
display: flex;
flex-direction: column;
padding-top: 0.3rem;
padding-right: 1rem;
padding-bottom: 0.2rem;
}
.row:not(:first-child) {
border-top: solid var(--secondary-background-color);
}
.row:first-child {
padding-top: 1rem;
}
.switches {
display: flex;
justify-content: center;
flex-direction: column;
gap: 1rem;
}
.volume-name {
flex: 1;
overflow: hidden;
flex-direction: column;
text-align: center;
}
.volume-name-text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: calc(var(--mxmp-font-size, 1rem) * 1.1);
font-weight: bold;
min-height: 1rem;
}
.slider-row {
display: flex;
}
mxmp-volume {
flex: 4;
}
*[show-switches] {
color: var(--accent-color);
}
[hidden] {
display: none !important;
}
mxmp-icon-button {
height: 40px;
align-self: start;
}
`;
}
}
__decorateClass([
n$4({ attribute: false })
], Volumes.prototype, "store");
__decorateClass([
r$3()
], Volumes.prototype, "showSwitches");
window.customCards.push({
type: "maxi-media-player",
name: "Maxi Media Player",
description: "Media card for Home Assistant UI with a focus on managing multiple media players",
preview: true
});
customElements.define("maxi-media-player", Card);
customElements.define("mxmp-grouping", Grouping);
customElements.define("mxmp-groups", Groups);
customElements.define("mxmp-media-browser", MediaBrowser);
customElements.define("mxmp-media-browser-browser", MediaBrowserBrowser);
customElements.define("mxmp-favorites", Favorites);
customElements.define("mxmp-player", Player);
customElements.define("mxmp-volumes", Volumes);
customElements.define("mxmp-queue", Queue);
customElements.define("mxmp-queue-mass", QueueMass);
customElements.define("mxmp-queue-sonos", QueueSonos);
customElements.define("mxmp-search", Search);
class SizeCache {
constructor(config) {
this._map = /* @__PURE__ */ new Map();
this._roundAverageSize = false;
this.totalSize = 0;
if (config?.roundAverageSize === true) {
this._roundAverageSize = true;
}
}
set(index, value) {
const prev = this._map.get(index) || 0;
this._map.set(index, value);
this.totalSize += value - prev;
}
get averageSize() {
if (this._map.size > 0) {
const average = this.totalSize / this._map.size;
return this._roundAverageSize ? Math.round(average) : average;
}
return 0;
}
getSize(index) {
return this._map.get(index);
}
clear() {
this._map.clear();
this.totalSize = 0;
}
}
const flow = (config) => Object.assign({
type: FlowLayout
}, config);
function leadingMargin(direction) {
return direction === "horizontal" ? "marginLeft" : "marginTop";
}
function trailingMargin(direction) {
return direction === "horizontal" ? "marginRight" : "marginBottom";
}
function offset(direction) {
return direction === "horizontal" ? "xOffset" : "yOffset";
}
function collapseMargins(a2, b2) {
const m2 = [a2, b2].sort();
return m2[1] <= 0 ? Math.min(...m2) : m2[0] >= 0 ? Math.max(...m2) : m2[0] + m2[1];
}
class MetricsCache {
constructor() {
this._childSizeCache = new SizeCache();
this._marginSizeCache = new SizeCache();
this._metricsCache = /* @__PURE__ */ new Map();
}
update(metrics, direction) {
const marginsToUpdate = /* @__PURE__ */ new Set();
Object.keys(metrics).forEach((key) => {
const k2 = Number(key);
this._metricsCache.set(k2, metrics[k2]);
this._childSizeCache.set(k2, metrics[k2][dim1(direction)]);
marginsToUpdate.add(k2);
marginsToUpdate.add(k2 + 1);
});
for (const k2 of marginsToUpdate) {
const a2 = this._metricsCache.get(k2)?.[leadingMargin(direction)] || 0;
const b2 = this._metricsCache.get(k2 - 1)?.[trailingMargin(direction)] || 0;
this._marginSizeCache.set(k2, collapseMargins(a2, b2));
}
}
get averageChildSize() {
return this._childSizeCache.averageSize;
}
get totalChildSize() {
return this._childSizeCache.totalSize;
}
get averageMarginSize() {
return this._marginSizeCache.averageSize;
}
get totalMarginSize() {
return this._marginSizeCache.totalSize;
}
getLeadingMarginValue(index, direction) {
return this._metricsCache.get(index)?.[leadingMargin(direction)] || 0;
}
getChildSize(index) {
return this._childSizeCache.getSize(index);
}
getMarginSize(index) {
return this._marginSizeCache.getSize(index);
}
clear() {
this._childSizeCache.clear();
this._marginSizeCache.clear();
this._metricsCache.clear();
}
}
class FlowLayout extends BaseLayout {
constructor() {
super(...arguments);
this._itemSize = { width: 100, height: 100 };
this._physicalItems = /* @__PURE__ */ new Map();
this._newPhysicalItems = /* @__PURE__ */ new Map();
this._metricsCache = new MetricsCache();
this._anchorIdx = null;
this._anchorPos = null;
this._stable = true;
this._measureChildren = true;
this._estimate = true;
}
// protected _defaultConfig: BaseLayoutConfig = Object.assign({}, super._defaultConfig, {
// })
// constructor(config: Layout1dConfig) {
// super(config);
// }
get measureChildren() {
return this._measureChildren;
}
/**
* Determine the average size of all children represented in the sizes
* argument.
*/
updateItemSizes(sizes) {
this._metricsCache.update(sizes, this.direction);
this._scheduleReflow();
}
/**
* Set the average item size based on the total length and number of children
* in range.
*/
// _updateItemSize() {
// // Keep integer values.
// this._itemSize[this._sizeDim] = this._metricsCache.averageChildSize;
// }
_getPhysicalItem(idx) {
return this._newPhysicalItems.get(idx) ?? this._physicalItems.get(idx);
}
_getSize(idx) {
const item = this._getPhysicalItem(idx);
return item && this._metricsCache.getChildSize(idx);
}
_getAverageSize() {
return this._metricsCache.averageChildSize || this._itemSize[this._sizeDim];
}
_estimatePosition(idx) {
const c3 = this._metricsCache;
if (this._first === -1 || this._last === -1) {
return c3.averageMarginSize + idx * (c3.averageMarginSize + this._getAverageSize());
} else {
if (idx < this._first) {
const delta = this._first - idx;
const refItem = this._getPhysicalItem(this._first);
return refItem.pos - (c3.getMarginSize(this._first - 1) || c3.averageMarginSize) - (delta * c3.averageChildSize + (delta - 1) * c3.averageMarginSize);
} else {
const delta = idx - this._last;
const refItem = this._getPhysicalItem(this._last);
return refItem.pos + (c3.getChildSize(this._last) || c3.averageChildSize) + (c3.getMarginSize(this._last) || c3.averageMarginSize) + delta * (c3.averageChildSize + c3.averageMarginSize);
}
}
}
/**
* Returns the position in the scrolling direction of the item at idx.
* Estimates it if the item at idx is not in the DOM.
*/
_getPosition(idx) {
const item = this._getPhysicalItem(idx);
const { averageMarginSize } = this._metricsCache;
return idx === 0 ? this._metricsCache.getMarginSize(0) ?? averageMarginSize : item ? item.pos : this._estimatePosition(idx);
}
_calculateAnchor(lower, upper) {
if (lower <= 0) {
return 0;
}
if (upper > this._scrollSize - this._viewDim1) {
return this.items.length - 1;
}
return Math.max(0, Math.min(this.items.length - 1, Math.floor((lower + upper) / 2 / this._delta)));
}
_getAnchor(lower, upper) {
if (this._physicalItems.size === 0) {
return this._calculateAnchor(lower, upper);
}
if (this._first < 0) {
return this._calculateAnchor(lower, upper);
}
if (this._last < 0) {
return this._calculateAnchor(lower, upper);
}
const firstItem = this._getPhysicalItem(this._first), lastItem = this._getPhysicalItem(this._last), firstMin = firstItem.pos, lastMin = lastItem.pos, lastMax = lastMin + this._metricsCache.getChildSize(this._last);
if (lastMax < lower) {
return this._calculateAnchor(lower, upper);
}
if (firstMin > upper) {
return this._calculateAnchor(lower, upper);
}
let candidateIdx = this._firstVisible - 1;
let cMax = -Infinity;
while (cMax < lower) {
const candidate = this._getPhysicalItem(++candidateIdx);
cMax = candidate.pos + this._metricsCache.getChildSize(candidateIdx);
}
return candidateIdx;
}
/**
* Updates _first and _last based on items that should be in the current
* viewed range.
*/
_getActiveItems() {
if (this._viewDim1 === 0 || this.items.length === 0) {
this._clearItems();
} else {
this._getItems();
}
}
/**
* Sets the range to empty.
*/
_clearItems() {
this._first = -1;
this._last = -1;
this._physicalMin = 0;
this._physicalMax = 0;
const items = this._newPhysicalItems;
this._newPhysicalItems = this._physicalItems;
this._newPhysicalItems.clear();
this._physicalItems = items;
this._stable = true;
}
/*
* Updates _first and _last based on items that should be in the given range.
*/
_getItems() {
const items = this._newPhysicalItems;
this._stable = true;
let lower, upper;
if (this.pin !== null) {
const { index } = this.pin;
this._anchorIdx = index;
this._anchorPos = this._getPosition(index);
}
lower = this._scrollPosition - this._overhang;
upper = this._scrollPosition + this._viewDim1 + this._overhang;
if (upper < 0 || lower > this._scrollSize) {
this._clearItems();
return;
}
if (this._anchorIdx === null || this._anchorPos === null) {
this._anchorIdx = this._getAnchor(lower, upper);
this._anchorPos = this._getPosition(this._anchorIdx);
}
let anchorSize = this._getSize(this._anchorIdx);
if (anchorSize === void 0) {
this._stable = false;
anchorSize = this._getAverageSize();
}
const anchorLeadingMargin = this._metricsCache.getMarginSize(this._anchorIdx) ?? this._metricsCache.averageMarginSize;
const anchorTrailingMargin = this._metricsCache.getMarginSize(this._anchorIdx + 1) ?? this._metricsCache.averageMarginSize;
if (this._anchorIdx === 0) {
this._anchorPos = anchorLeadingMargin;
}
if (this._anchorIdx === this.items.length - 1) {
this._anchorPos = this._scrollSize - anchorTrailingMargin - anchorSize;
}
let anchorErr = 0;
if (this._anchorPos + anchorSize + anchorTrailingMargin < lower) {
anchorErr = lower - (this._anchorPos + anchorSize + anchorTrailingMargin);
}
if (this._anchorPos - anchorLeadingMargin > upper) {
anchorErr = upper - (this._anchorPos - anchorLeadingMargin);
}
if (anchorErr) {
this._scrollPosition -= anchorErr;
lower -= anchorErr;
upper -= anchorErr;
this._scrollError += anchorErr;
}
items.set(this._anchorIdx, { pos: this._anchorPos, size: anchorSize });
this._first = this._last = this._anchorIdx;
this._physicalMin = this._anchorPos - anchorLeadingMargin;
this._physicalMax = this._anchorPos + anchorSize + anchorTrailingMargin;
while (this._physicalMin > lower && this._first > 0) {
let size = this._getSize(--this._first);
if (size === void 0) {
this._stable = false;
size = this._getAverageSize();
}
let margin = this._metricsCache.getMarginSize(this._first);
if (margin === void 0) {
this._stable = false;
margin = this._metricsCache.averageMarginSize;
}
this._physicalMin -= size;
const pos = this._physicalMin;
items.set(this._first, { pos, size });
this._physicalMin -= margin;
if (this._stable === false && this._estimate === false) {
break;
}
}
while (this._physicalMax < upper && this._last < this.items.length - 1) {
let size = this._getSize(++this._last);
if (size === void 0) {
this._stable = false;
size = this._getAverageSize();
}
let margin = this._metricsCache.getMarginSize(this._last);
if (margin === void 0) {
this._stable = false;
margin = this._metricsCache.averageMarginSize;
}
const pos = this._physicalMax;
items.set(this._last, { pos, size });
this._physicalMax += size + margin;
if (!this._stable && !this._estimate) {
break;
}
}
const extentErr = this._calculateError();
if (extentErr) {
this._physicalMin -= extentErr;
this._physicalMax -= extentErr;
this._anchorPos -= extentErr;
this._scrollPosition -= extentErr;
items.forEach((item) => item.pos -= extentErr);
this._scrollError += extentErr;
}
if (this._stable) {
this._newPhysicalItems = this._physicalItems;
this._newPhysicalItems.clear();
this._physicalItems = items;
}
}
_calculateError() {
if (this._first === 0) {
return this._physicalMin;
} else if (this._physicalMin <= 0) {
return this._physicalMin - this._first * this._delta;
} else if (this._last === this.items.length - 1) {
return this._physicalMax - this._scrollSize;
} else if (this._physicalMax >= this._scrollSize) {
return this._physicalMax - this._scrollSize + (this.items.length - 1 - this._last) * this._delta;
}
return 0;
}
_reflow() {
const { _first, _last } = this;
super._reflow();
if (this._first === -1 && this._last == -1 || this._first === _first && this._last === _last) {
this._resetReflowState();
}
}
_resetReflowState() {
this._anchorIdx = null;
this._anchorPos = null;
this._stable = true;
}
_updateScrollSize() {
const { averageMarginSize } = this._metricsCache;
this._scrollSize = Math.max(1, this.items.length * (averageMarginSize + this._getAverageSize()) + averageMarginSize);
}
/**
* Returns the average size (precise or estimated) of an item in the scrolling direction,
* including any surrounding space.
*/
get _delta() {
const { averageMarginSize } = this._metricsCache;
return this._getAverageSize() + averageMarginSize;
}
/**
* Returns the top and left positioning of the item at idx.
*/
_getItemPosition(idx) {
return {
[this._positionDim]: this._getPosition(idx),
[this._secondaryPositionDim]: 0,
[offset(this.direction)]: -(this._metricsCache.getLeadingMarginValue(idx, this.direction) ?? this._metricsCache.averageMarginSize)
};
}
/**
* Returns the height and width of the item at idx.
*/
_getItemSize(idx) {
return {
[this._sizeDim]: this._getSize(idx) || this._getAverageSize(),
[this._secondarySizeDim]: this._itemSize[this._secondarySizeDim]
};
}
_viewDim2Changed() {
this._metricsCache.clear();
this._scheduleReflow();
}
}
const flow$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
FlowLayout,
flow
}, Symbol.toStringTag, { value: "Module" }));