diff --git a/custom_components/bahmcloud_store/panel/app.js b/custom_components/bahmcloud_store/panel/app.js index 412bf0e..588f59e 100644 --- a/custom_components/bahmcloud_store/panel/app.js +++ b/custom_components/bahmcloud_store/panel/app.js @@ -53,7 +53,6 @@ class BahmcloudStorePanel extends HTMLElement { const data = await this._hass.callApi("get", "bcs"); this._data = data; - // keep detail repo fresh (installed state, versions, etc.) if (this._view === "detail" && this._detailRepoId && Array.isArray(data?.repos)) { const fresh = data.repos.find((r) => this._safeId(r?.id) === this._detailRepoId); if (fresh) this._detailRepo = fresh; @@ -72,13 +71,10 @@ class BahmcloudStorePanel extends HTMLElement { this._refreshing = true; this._error = null; - - // Show a loading state immediately this._loading = true; this._update(); try { - // IMPORTANT: This hits POST /api/bcs?action=refresh const resp = await this._hass.callApi("post", "bcs?action=refresh", {}); if (!resp?.ok) { const msg = this._safeText(resp?.message) || "Refresh failed."; @@ -90,7 +86,6 @@ class BahmcloudStorePanel extends HTMLElement { this._refreshing = false; } - // Always reload data after refresh attempt (even on failure) await this._load(); } @@ -298,14 +293,9 @@ class BahmcloudStorePanel extends HTMLElement { } .iconbtn:hover{ filter:brightness(0.98); } - .wrap{ - max-width:1200px; margin:0 auto; padding:16px; - } + .wrap{ max-width:1200px; margin:0 auto; padding:16px; } - .tabs{ - display:flex; gap:10px; flex-wrap:wrap; - margin:8px 0 16px; - } + .tabs{ display:flex; gap:10px; flex-wrap:wrap; margin:8px 0 16px; } .tab{ padding:10px 14px; border-radius:999px; border:1px solid var(--divider-color); @@ -314,21 +304,11 @@ class BahmcloudStorePanel extends HTMLElement { } .tab.active{ border-color: var(--bcs-accent); box-shadow: 0 0 0 2px rgba(30,136,229,.15); } - .grid{ - display:grid; gap:12px; - grid-template-columns: repeat(1, minmax(0, 1fr)); - } - @media (min-width: 900px){ - .grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); } - } + .grid{ display:grid; gap:12px; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 900px){ .grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); } } - .grid2{ - display:grid; gap:12px; - grid-template-columns: 1fr; - } - @media (min-width: 1024px){ - .grid2{ grid-template-columns: 1.2fr .8fr; } - } + .grid2{ display:grid; gap:12px; grid-template-columns: 1fr; } + @media (min-width: 1024px){ .grid2{ grid-template-columns: 1.2fr .8fr; } } .card{ padding:14px 14px; @@ -377,10 +357,7 @@ class BahmcloudStorePanel extends HTMLElement { border-color: rgba(30,136,229,.35); background: rgba(30,136,229,.08); } - button:disabled{ - opacity: .55; - cursor: not-allowed; - } + button:disabled{ opacity: .55; cursor: not-allowed; } .err{ margin:12px 0; @@ -399,7 +376,7 @@ class BahmcloudStorePanel extends HTMLElement { gap:10px; z-index: 60; } - .fab{ + .fabbtn{ width:54px; height:54px; border-radius:18px; border:1px solid var(--divider-color); @@ -409,12 +386,16 @@ class BahmcloudStorePanel extends HTMLElement { box-shadow: 0 8px 18px rgba(0,0,0,.12); user-select:none; font-size: 18px; + padding: 0; } - .fab.primary{ + .fabbtn.primary{ border-color: rgba(30,136,229,.35); background: rgba(30,136,229,.10); } - .fab[disabled]{ opacity: .55; cursor: not-allowed; } + .fabbtn:disabled{ + opacity: .55; + cursor: not-allowed; + } pre.readme{ padding: 12px; @@ -507,7 +488,6 @@ class BahmcloudStorePanel extends HTMLElement { const subtitle = root.getElementById("subtitle"); if (subtitle) subtitle.textContent = this._view === "detail" ? "Details" : this._view[0].toUpperCase() + this._view.slice(1); - // tabs const setActive = (id, on) => { const el = root.getElementById(id); if (!el) return; @@ -521,7 +501,6 @@ class BahmcloudStorePanel extends HTMLElement { const fabs = root.getElementById("fabs"); if (!content || !fabs) return; - // error block const err = this._error ? `
Error: ${this._esc(this._error)}
` : ""; @@ -538,7 +517,6 @@ class BahmcloudStorePanel extends HTMLElement { return; } - // render view let html = ""; if (this._view === "store") html = this._renderStore(); else if (this._view === "manage") html = this._renderManage(); @@ -548,11 +526,10 @@ class BahmcloudStorePanel extends HTMLElement { content.innerHTML = `${err}${html}`; fabs.innerHTML = this._view === "detail" ? this._renderFabs() : ""; - // wire view interactions if (this._view === "store") this._wireStore(); if (this._view === "manage") this._wireManage(); if (this._view === "detail") { - this._wireDetail(); + this._wireDetail(); // now always wires buttons this._wireFabs(); } } @@ -563,8 +540,7 @@ class BahmcloudStorePanel extends HTMLElement { } _safeId(v) { - const s = this._safeText(v).trim(); - return s; + return this._safeText(v).trim(); } _esc(s) { @@ -576,6 +552,10 @@ class BahmcloudStorePanel extends HTMLElement { .replaceAll("'", "'"); } + _asBoolStrict(v) { + return v === true; + } + _renderStore() { const repos = Array.isArray(this._data.repos) ? this._data.repos : []; @@ -618,7 +598,7 @@ class BahmcloudStorePanel extends HTMLElement { const desc = this._safeText(r?.description) || ""; const latest = this._safeText(r?.latest_version); - const installed = !!r?.installed; + const installed = this._asBoolStrict(r?.installed); const installedVersion = this._safeText(r?.installed_version); const updateAvailable = installed && !!latest && (!installedVersion || latest !== installedVersion); @@ -694,7 +674,6 @@ class BahmcloudStorePanel extends HTMLElement { }); } - // card clicks root.querySelectorAll("[data-open]").forEach((el) => { const id = el.getAttribute("data-open"); el.addEventListener("click", () => this._openRepoDetail(id)); @@ -723,12 +702,9 @@ class BahmcloudStorePanel extends HTMLElement { const url = this._safeText(r?.url) || ""; const desc = this._safeText(r?.description) || ""; - const latest = this._safeText(r?.latest_version) ? `Latest: ${this._safeText(r?.latest_version)}` : "Latest: -"; - const badge = `
${this._esc(this._safeText(r?.provider || "repo"))}
`; - const infoBits = [ this._safeText(r?.owner) ? `Creator: ${this._safeText(r?.owner)}` : "Creator: -", - latest, + this._safeText(r?.latest_version) ? `Latest: ${this._safeText(r?.latest_version)}` : "Latest: -", this._safeText(r?.provider) ? `Provider: ${this._safeText(r?.provider)}` : null, this._safeText(r?.category) ? `Category: ${this._safeText(r?.category)}` : null, this._safeText(r?.meta_author) ? `Author: ${this._safeText(r?.meta_author)}` : null, @@ -765,7 +741,7 @@ class BahmcloudStorePanel extends HTMLElement { const repoId = this._safeId(r?.id); - const installed = !!r?.installed; + const installed = this._asBoolStrict(r?.installed); const installedVersion = this._safeText(r?.installed_version); const installedDomains = Array.isArray(r?.installed_domains) ? r.installed_domains : []; const latestVersion = this._safeText(r?.latest_version); @@ -806,7 +782,7 @@ class BahmcloudStorePanel extends HTMLElement { Open repository - ${badge} +
${this._esc(this._safeText(r?.provider || "repo"))}
@@ -840,6 +816,30 @@ class BahmcloudStorePanel extends HTMLElement { _wireDetail() { const root = this.shadowRoot; + + // Always wire action buttons (even if README is already loaded) + const btnInstall = root.getElementById("btnInstall"); + const btnUpdate = root.getElementById("btnUpdate"); + const btnRestart = root.getElementById("btnRestart"); + + if (btnInstall) { + btnInstall.addEventListener("click", () => { + if (btnInstall.disabled) return; + if (this._detailRepoId) this._installRepo(this._detailRepoId); + }); + } + + if (btnUpdate) { + btnUpdate.addEventListener("click", () => { + if (btnUpdate.disabled) return; + if (this._detailRepoId) this._updateRepo(this._detailRepoId); + }); + } + + if (btnRestart) { + btnRestart.addEventListener("click", () => this._restartHA()); + } + const mount = root.getElementById("readmePretty"); if (!mount) return; @@ -847,36 +847,11 @@ class BahmcloudStorePanel extends HTMLElement { if (this._readmeHtml) { mount.innerHTML = this._readmeHtml; this._postprocessRenderedMarkdown(mount); - return; + } else { + mount.innerHTML = `
Rendered HTML not available. Use “Show raw Markdown”.
`; } - mount.innerHTML = `
Rendered HTML not available. Use “Show raw Markdown”.
`; - return; - } - - mount.innerHTML = ""; - - const btnInstall = root.getElementById("btnInstall"); - const btnUpdate = root.getElementById("btnUpdate"); - const btnRestart = root.getElementById("btnRestart"); - - if (btnInstall) { - btnInstall.addEventListener("click", () => { - if (btnInstall.hasAttribute("disabled")) return; - if (this._detailRepoId) this._installRepo(this._detailRepoId); - }); - } - - if (btnUpdate) { - btnUpdate.addEventListener("click", () => { - if (btnUpdate.hasAttribute("disabled")) return; - if (this._detailRepoId) this._updateRepo(this._detailRepoId); - }); - } - - if (btnRestart) { - btnRestart.addEventListener("click", () => { - this._restartHA(); - }); + } else { + mount.innerHTML = ""; } } @@ -896,7 +871,7 @@ class BahmcloudStorePanel extends HTMLElement { if (!r) return ""; const repoId = this._safeId(r?.id); - const installed = !!r?.installed; + const installed = this._asBoolStrict(r?.installed); const latest = this._safeText(r?.latest_version); const installedVersion = this._safeText(r?.installed_version); @@ -906,16 +881,13 @@ class BahmcloudStorePanel extends HTMLElement { const installDisabled = installed || busy; const updateDisabled = !updateAvailable || busy; - const installTitle = installed ? "Already installed" : busy ? "Installing…" : "Install"; - const updateTitle = !installed ? "Not installed" : !updateAvailable ? "No update available" : busy ? "Updating…" : "Update"; - return `
-
-
-
-
-
i
+ + + + +
`; } @@ -926,6 +898,7 @@ class BahmcloudStorePanel extends HTMLElement { if (!r) return; const url = this._safeText(r?.url); + const repoId = this._safeId(r?.id); const open = root.getElementById("fabOpen"); const reload = root.getElementById("fabReload"); @@ -933,35 +906,24 @@ class BahmcloudStorePanel extends HTMLElement { const update = root.getElementById("fabUpdate"); const info = root.getElementById("fabInfo"); - const repoId = this._safeId(r?.id); - if (open) open.addEventListener("click", () => url && window.open(url, "_blank", "noreferrer")); - if (reload) { - reload.addEventListener("click", () => { - if (this._detailRepoId) this._loadReadme(this._detailRepoId); - }); - } + if (reload) reload.addEventListener("click", () => this._detailRepoId && this._loadReadme(this._detailRepoId)); if (install) { install.addEventListener("click", () => { - if (install.hasAttribute("disabled")) return; + if (install.disabled) return; this._installRepo(repoId); }); } if (update) { update.addEventListener("click", () => { - if (update.hasAttribute("disabled")) return; + if (update.disabled) return; this._updateRepo(repoId); }); } - if (info) { - info.addEventListener("click", () => { - this._view = "about"; - this._update(); - }); - } + if (info) info.addEventListener("click", () => { this._view = "about"; this._update(); }); } _renderManage() { @@ -1016,16 +978,8 @@ class BahmcloudStorePanel extends HTMLElement { const name = root.getElementById("customName"); const add = root.getElementById("addCustom"); - if (url) { - url.addEventListener("input", (e) => { - this._customAddUrl = e?.target?.value || ""; - }); - } - if (name) { - name.addEventListener("input", (e) => { - this._customAddName = e?.target?.value || ""; - }); - } + if (url) url.addEventListener("input", (e) => { this._customAddUrl = e?.target?.value || ""; }); + if (name) name.addEventListener("input", (e) => { this._customAddName = e?.target?.value || ""; }); if (add) add.addEventListener("click", () => this._addCustomRepo()); root.querySelectorAll("[data-remove]").forEach((el) => {