diff --git a/custom_components/bahmcloud_store/panel/panel.js b/custom_components/bahmcloud_store/panel/panel.js index 5a37b25..aa6bb4e 100644 --- a/custom_components/bahmcloud_store/panel/panel.js +++ b/custom_components/bahmcloud_store/panel/panel.js @@ -18,6 +18,12 @@ class BahmcloudStorePanel extends HTMLElement { this._filter = "all"; // all|installed|not_installed|updates|custom this._sort = "az"; // az|updates_first|installed_first + // Source filter (all|bcs|hacs|custom) + this._sourceFilter = "all"; + + // HACS toggle (settings) + this._hacsEnabled = false; + this._detailRepoId = null; this._detailRepo = null; this._readmeLoading = false; @@ -50,6 +56,10 @@ class BahmcloudStorePanel extends HTMLElement { this._versionsCache = {}; // repo_id -> [{ref,label,source}, ...] this._versionsLoadingRepoId = null; this._selectedVersionByRepoId = {}; // repo_id -> ref ("" means latest) + + // History handling (mobile back button should go back to list, not exit panel) + this._historyBound = false; + this._handlingPopstate = false; } set hass(hass) { @@ -57,10 +67,43 @@ class BahmcloudStorePanel extends HTMLElement { if (!this._rendered) { this._rendered = true; this._render(); + this._ensureHistory(); this._load(); } } + _ensureHistory() { + if (this._historyBound) return; + this._historyBound = true; + + try { + // Keep an internal history state for this panel. + const current = window.history.state || {}; + if (!current || current.__bcs !== true) { + window.history.replaceState({ __bcs: true, view: "store" }, ""); + } + } catch (e) { + // ignore + } + + window.addEventListener("popstate", (ev) => { + const st = ev?.state; + if (!st || st.__bcs !== true) return; + + this._handlingPopstate = true; + try { + const view = st.view || "store"; + if (view === "detail" && st.repo_id) { + this._openRepoDetail(st.repo_id, false); + } else { + this._closeDetail(false); + } + } finally { + this._handlingPopstate = false; + } + }); + } + async _load() { if (!this._hass) return; @@ -72,6 +115,12 @@ class BahmcloudStorePanel extends HTMLElement { const data = await this._hass.callApi("get", "bcs"); this._data = data; + // Persistent settings (e.g. HACS toggle) + this._hacsEnabled = !!data?.settings?.hacs_enabled; + + // Sync settings from backend (e.g. HACS toggle) + this._hacsEnabled = !!data?.settings?.hacs_enabled; + 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; @@ -84,6 +133,19 @@ class BahmcloudStorePanel extends HTMLElement { } } + async _setSettings(updates) { + if (!this._hass) return; + try { + const resp = await this._hass.callApi("post", "bcs/settings", updates || {}); + if (resp?.ok) { + this._hacsEnabled = !!resp?.settings?.hacs_enabled; + } + } catch (e) { + // Do not fail UI for settings. + this._error = e?.message ? String(e.message) : String(e); + } + } + async _refreshAll() { if (!this._hass) return; if (this._refreshing) return; @@ -308,21 +370,15 @@ class BahmcloudStorePanel extends HTMLElement { } _goBack() { - if (this._view === "detail") { - this._view = "store"; - this._detailRepoId = null; - this._detailRepo = null; - this._readmeText = null; - this._readmeHtml = null; - this._readmeError = null; - this._readmeExpanded = false; - this._update(); - return; - } try { + // Prefer browser history so mobile back behaves as expected. history.back(); } catch (_) { - window.location.href = "/"; + if (this._view === "detail") { + this._closeDetail(true); + } else { + window.location.href = "/"; + } } } @@ -368,11 +424,15 @@ class BahmcloudStorePanel extends HTMLElement { } } - _openRepoDetail(repoId) { + _openRepoDetail(repoId, pushHistory = true) { const repos = Array.isArray(this._data?.repos) ? this._data.repos : []; const repo = repos.find((r) => this._safeId(r?.id) === repoId); if (!repo) return; + if (pushHistory) { + this._pushHistory({ view: "detail", repo_id: repoId }); + } + this._view = "detail"; this._detailRepoId = repoId; this._detailRepo = repo; @@ -561,6 +621,24 @@ class BahmcloudStorePanel extends HTMLElement { box-shadow: 0 0 0 2px rgba(30,136,229,.15); } + .toggle{ + display:inline-flex; + align-items:center; + gap:8px; + padding:10px 12px; + border-radius:14px; + border:1px solid var(--divider-color); + background: var(--card-background-color); + color: var(--primary-text-color); + user-select:none; + cursor:pointer; + } + .toggle input{ + margin:0; + width:18px; + height:18px; + } + button{ padding:10px 12px; border-radius:14px; @@ -859,6 +937,11 @@ class BahmcloudStorePanel extends HTMLElement { const cat = this._safeText(r?.category) || ""; if (this._category !== "all" && this._category !== cat) return false; + // Source filter + if (this._sourceFilter === "bcs" && r?.source !== "index") return false; + if (this._sourceFilter === "hacs" && r?.source !== "hacs") return false; + if (this._sourceFilter === "custom" && r?.source !== "custom") return false; + const latest = this._safeText(r?.latest_version); const installed = this._asBoolStrict(r?.installed); const installedVersion = this._safeText(r?.installed_version); @@ -913,7 +996,11 @@ class BahmcloudStorePanel extends HTMLElement { const updateAvailable = installed && !!latest && (!installedVersion || latest !== installedVersion); const badges = []; - if (r?.source === "custom") badges.push("Custom"); + // Source badges + if (r?.source === "index") badges.push("BCS Official"); + else if (r?.source === "hacs") badges.push("HACS"); + else if (r?.source === "custom") badges.push("Custom"); + if (installed) badges.push("Installed"); if (updateAvailable) badges.push("Update"); @@ -939,6 +1026,18 @@ class BahmcloudStorePanel extends HTMLElement { return `
+ + + +