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 ? "" : "", c3 = f$2; for (let i6 = 0; i6 < s2; i6++) { const s3 = t2[i6]; let a2, u2, d2 = -1, y3 = 0; for (; y3 < s3.length && (c3.lastIndex = y3, u2 = c3.exec(s3), null !== u2); ) y3 = c3.lastIndex, c3 === f$2 ? "!--" === u2[1] ? c3 = v$1 : void 0 !== u2[1] ? c3 = _ : void 0 !== u2[2] ? ($.test(u2[2]) && (r2 = RegExp("" === u2[0] ? (c3 = r2 ?? f$2, d2 = -1) : void 0 === u2[1] ? d2 = -2 : (d2 = c3.lastIndex - u2[2].length, a2 = u2[1], c3 = void 0 === u2[3] ? m$2 : '"' === u2[3] ? g : p$1) : c3 === g || c3 === p$1 ? c3 = m$2 : c3 === v$1 || c3 === _ ? c3 = f$2 : (c3 = m$2, r2 = void 0); const x2 = c3 === m$2 && t2[i6 + 1].startsWith("/>") ? " " : ""; l2 += c3 === f$2 ? s3 + n$5 : d2 >= 0 ? (o2.push(a2), s3.slice(0, d2) + e$4 + s3.slice(d2) + h$2 + x2) : s3 + h$2 + (-2 === d2 ? i6 : x2); } return [P(t2, l2 + (t2[s2] || "") + (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,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"; 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):
`; } 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`
${item.name}
`; } 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`
${currentItem.thumbnail ? x`
${this.narrow && currentItem?.can_play && (!this.accept || canPlayChildren.has(currentItem.media_content_id)) ? x` ${this.hass.localize(`ui.components.media-browser.${this.action}`)} ` : ""}
` : E}
${currentItem.can_play && (!currentItem.thumbnail || !this.narrow) ? x` ${this.hass.localize(`ui.components.media-browser.${this.action}`)} ` : ""}
` : ""}
${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`
${this.navigateIds.length > 1 ? x`` : x`
`} ${this.currentTitle || "Media Browser"} ${this.renderPlayAllButton()} ${renderShortcutButton(shortcut, () => this.navigateToShortcut(shortcut), this.isShortcutActive(shortcut))} ${renderLayoutMenu(this.layout, this.handleLayoutChange)}
`} ${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`
${title} ${onlyFavorites ? "" : renderShortcutButton(config.shortcut, this.onShortcutClick)} ${onlyFavorites ? "" : x` `} ${renderLayoutMenu(this.layout, this.handleMenuAction)}
`} `; } 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`
e2.stopPropagation()}> ${PLAY_MENU_ACTIONS.map( (_action, index) => x`
this.selectAction(index)}> ${this.getActionLabel(index)}
` )}
`; } 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`
${this.queueTitle} (${this.itemCount} items)
this.dispatchAction({ type: "invert-selection" })} @play-selected=${() => this.dispatchAction({ type: "play-selected" })} @queue-selected=${() => this.dispatchAction({ type: "queue-selected-after-current" })} @queue-selected-at-end=${() => this.dispatchAction({ type: "queue-selected-at-end" })} > this.dispatchAction({ type: "delete-selected" })} title="Delete selected" ?hidden=${!this.hasSelection} >
this.dispatchAction({ type: "clear-queue" })} title="Delete all"> *
this.dispatchAction({ type: "toggle-select-mode" })} ?selected=${this.selectMode} title="Select mode" ?disabled=${this.operationRunning} >
`; } 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)} >

${MASS_CONFIG_MESSAGE}

${MASS_QUEUE_MESSAGE}

${ctrl.errorMessage}

handleSearchAction(ctrl, e2.detail)} @queue-header-action=${(e2) => handleHeaderAction(ctrl, e2.detail)} > handleListAction(ctrl, e2.detail)} >
dismissPlayMenu(ctrl)} @wheel=${(e2) => e2.preventDefault()} @touchmove=${(e2) => e2.preventDefault()} > handlePlayMenuAction(ctrl, e2.detail)} @play-menu-close=${() => dismissPlayMenu(ctrl)} >
`; } 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`
e2.stopPropagation()}> ${mediaTypeOverflows.map( (icon) => x`
this.dispatch({ type: "toggle-media-type", mediaType: icon.type })}> ${icon.title}
` )} ${hasLibraryFilter ? x` ${mediaTypeOverflows.length > 0 ? x`
` : E}
this.dispatch({ type: "toggle-library-filter" })}> ${LIBRARY_LABELS$1[this.libraryFilter]}
` : E}
this.dispatch({ type: "close" })}>Done
`; } 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`
${this.title}
${this.visibleIcons.map( (icon) => x` this.onIconClick(icon)} ?selected=${this.isIconActive(icon)} title=${this.getIconTitle(icon)} > ` )} ${hasOverflow ? x`
this.filterMenuOpen = !this.filterMenuOpen} title="More filters" ?selected=${this.overflowIcons.some((i5) => this.isIconActive(i5))} >
` : E}
this.dispatch({ type: "invert-selection" })} @play-selected=${(e2) => this.dispatch({ type: "selection-action", action: e2.detail })} @queue-selected=${(e2) => this.dispatch({ type: "selection-action", action: e2.detail })} @queue-selected-at-end=${(e2) => this.dispatch({ type: "selection-action", action: e2.detail })} > this.dispatch({ type: "toggle-view-mode" })} title=${this.viewMode === "list" ? "Grid view" : "List view"} > this.dispatch({ type: "toggle-select-mode" })} ?selected=${this.selectMode} title="Select mode" >
`; } 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` `; } 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)}
this.playMenuItemIndex = null} @wheel=${(e2) => e2.preventDefault()} @touchmove=${(e2) => e2.preventDefault()} > this.handleItemPlayAction(e2)} @play-menu-close=${() => this.playMenuItemIndex = null} >
`; } 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`
${this.results.map((item, index) => { const selected = this.selectedIndices.has(index); return x`
this.onItemClick(index)}> ${this.selectMode ? x` this.onCheckboxChange(index, e2.target.checked)} @click=${(e2) => e2.stopPropagation()} >` : ""} ${item.imageUrl ? x`${item.title}` : x`
`}
${item.title}
${item.subtitle ? x`
${item.subtitle}
` : ""}
`; })}
`; } 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`
Play Now (clear queue) Start Radio Play Now Play Next Add to Queue Play Next (clear queue) ${this.item.title}
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`
${name}
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" }));