..
This commit is contained in:
@@ -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
|
||||
? `<div class="err"><strong>Error:</strong> ${this._esc(this._error)}</div>`
|
||||
: "";
|
||||
@@ -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 = `<div class="badge">${this._esc(this._safeText(r?.provider || "repo"))}</div>`;
|
||||
|
||||
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 {
|
||||
<a href="${this._esc(url)}" target="_blank" rel="noreferrer">Open repository</a>
|
||||
</div>
|
||||
</div>
|
||||
${badge}
|
||||
<div class="badge">${this._esc(this._safeText(r?.provider || "repo"))}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 = `<div class="muted">Rendered HTML not available. Use “Show raw Markdown”.</div>`;
|
||||
}
|
||||
mount.innerHTML = `<div class="muted">Rendered HTML not available. Use “Show raw Markdown”.</div>`;
|
||||
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 `
|
||||
<div class="fabs">
|
||||
<div class="fab primary" id="fabOpen" title="Open repository">↗</div>
|
||||
<div class="fab" id="fabReload" title="Reload README">⟳</div>
|
||||
<div class="fab" id="fabInstall" title="${installTitle}" ${installDisabled ? "disabled" : ""}>+</div>
|
||||
<div class="fab" id="fabUpdate" title="${updateTitle}" ${updateDisabled ? "disabled" : ""}>↑</div>
|
||||
<div class="fab" id="fabInfo" title="About">i</div>
|
||||
<button class="fabbtn primary" id="fabOpen" title="Open repository">↗</button>
|
||||
<button class="fabbtn" id="fabReload" title="Reload README">⟳</button>
|
||||
<button class="fabbtn" id="fabInstall" title="${installDisabled ? (installed ? "Already installed" : "Installing…") : "Install"}" ${installDisabled ? "disabled" : ""}>+</button>
|
||||
<button class="fabbtn" id="fabUpdate" title="${updateDisabled ? (!installed ? "Not installed" : "No update available") : "Update"}" ${updateDisabled ? "disabled" : ""}>↑</button>
|
||||
<button class="fabbtn" id="fabInfo" title="About">i</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user