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) => {