diff --git a/custom_components/bahmcloud_store/panel/panel.js b/custom_components/bahmcloud_store/panel/panel.js index 12be78a..008954a 100644 --- a/custom_components/bahmcloud_store/panel/panel.js +++ b/custom_components/bahmcloud_store/panel/panel.js @@ -1,24 +1,472 @@ class BahmcloudStorePanel extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + + this._hass = null; + this._view = "store"; // store | manage | about + this._data = null; + this._loading = true; + this._error = null; + + this._customAddUrl = ""; + this._customAddName = ""; + } + set hass(hass) { - if (this._rendered) return; - this._rendered = true; + this._hass = hass; + if (!this._rendered) { + this._rendered = true; + this._render(); + this._load(); + } + } - const root = this.attachShadow({ mode: "open" }); - const iframe = document.createElement("iframe"); + async _load() { + if (!this._hass) return; - iframe.src = "/api/bahmcloud_store_static/index.html"; - iframe.style.width = "100%"; - iframe.style.height = "100%"; - iframe.style.border = "0"; - iframe.style.display = "block"; + this._loading = true; + this._error = null; + this._update(); - const style = document.createElement("style"); - style.textContent = ` - :host { display: block; height: 100vh; } + try { + const data = await this._hass.callApi("get", "bcs"); + this._data = data; + } catch (e) { + this._error = e?.message ? String(e.message) : String(e); + } finally { + this._loading = false; + this._update(); + } + } + + async _addCustomRepo() { + if (!this._hass) return; + + const url = (this._customAddUrl || "").trim(); + const name = (this._customAddName || "").trim() || null; + + if (!url) { + this._error = "Please enter a repository URL."; + this._update(); + return; + } + + this._error = null; + this._update(); + + try { + await this._hass.callApi("post", "bcs", { + op: "add_custom_repo", + url, + name, + }); + this._customAddUrl = ""; + this._customAddName = ""; + await this._load(); + this._view = "manage"; + this._update(); + } catch (e) { + this._error = e?.message ? String(e.message) : String(e); + this._update(); + } + } + + async _removeCustomRepo(id) { + if (!this._hass) return; + + try { + await this._hass.callApi("delete", `bcs/custom_repo?id=${encodeURIComponent(id)}`); + await this._load(); + this._view = "manage"; + this._update(); + } catch (e) { + this._error = e?.message ? String(e.message) : String(e); + this._update(); + } + } + + _render() { + const root = this.shadowRoot; + + root.innerHTML = ` + + +
+
+
+
+
Bahmcloud Store
+
BCS 0.2.0 — foundation build
+
+
+ +
+ +
+
+ +
+
Store
+
Manage repositories
+
Settings / About
+
+ +
+
+
`; - root.appendChild(style); - root.appendChild(iframe); + root.getElementById("refreshBtn").addEventListener("click", () => this._load()); + + for (const tab of root.querySelectorAll(".tab")) { + tab.addEventListener("click", () => { + this._view = tab.getAttribute("data-view"); + this._update(); + }); + } + } + + _update() { + const root = this.shadowRoot; + const content = root.getElementById("content"); + const err = root.getElementById("error"); + + for (const tab of root.querySelectorAll(".tab")) { + tab.classList.toggle("active", tab.getAttribute("data-view") === this._view); + } + + err.textContent = this._error ? `Error: ${this._error}` : ""; + + if (this._loading) { + content.innerHTML = `
Loading…
`; + return; + } + + if (!this._data) { + content.innerHTML = `
No data.
`; + return; + } + + if (this._view === "store") { + content.innerHTML = this._renderStore(); + return; + } + + if (this._view === "manage") { + content.innerHTML = this._renderManage(); + this._wireManage(); + return; + } + + content.innerHTML = this._renderAbout(); + } + + _renderStore() { + const repos = Array.isArray(this._data.repos) ? this._data.repos : []; + + const rows = repos.map((r) => { + const badge = r.source === "custom" + ? `Custom` + : `Index`; + + const owner = r.owner ? `Owner: ${this._esc(r.owner)}` : "Owner: -"; + const provider = r.provider ? `Provider: ${this._esc(r.provider)}` : "Provider: -"; + + return ` +
+
+
+
${this._esc(r.name)}
+
${this._esc(r.description || "Description will be loaded in a later version.")}
+
${owner} · ${provider}
+ +
+ ${badge} +
+
+ `; + }).join(""); + + return ` +
+
Store
+
Index URL: ${this._esc(this._data.store_url || "-")}
+
Refresh seconds: ${this._esc(String(this._data.refresh_seconds || "-"))}
+
+ + ${rows || `
No repositories configured.
`} + `; + } + + _renderManage() { + const repos = Array.isArray(this._data.repos) ? this._data.repos : []; + const custom = repos.filter((r) => r.source === "custom"); + + const list = custom.map((r) => { + return ` +
+
+
+
${this._esc(r.name)}
+
${this._esc(r.url)}
+
+
+ +
+
+
+ `; + }).join(""); + + return ` +
+
Manage repositories
+
Add public repositories from any git provider.
+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + ${list || `
No custom repositories added yet.
`} + `; + } + + _wireManage() { + const root = this.shadowRoot; + + const addUrl = root.getElementById("addUrl"); + const addName = root.getElementById("addName"); + const addBtn = root.getElementById("addBtn"); + + if (addUrl) { + addUrl.addEventListener("input", (e) => { + this._customAddUrl = e.target.value; + }); + } + if (addName) { + addName.addEventListener("input", (e) => { + this._customAddName = e.target.value; + }); + } + if (addBtn) { + addBtn.addEventListener("click", () => this._addCustomRepo()); + } + + for (const btn of root.querySelectorAll("[data-remove]")) { + btn.addEventListener("click", () => { + const id = btn.getAttribute("data-remove"); + if (id) this._removeCustomRepo(id); + }); + } + } + + _renderAbout() { + return ` +
+
Settings / About
+
Language: English (v1). i18n will be added later.
+
Theme: follows Home Assistant light/dark automatically.
+
Accent: Bahmcloud Blue.
+
BCS version: ${this._esc(this._data.version || "-")}
+
+ +
+
Roadmap
+
+ Next versions will add: repo metadata (bcs.yaml / hacs.*), README view, install/uninstall, update entities. +
+
+ `; + } + + _esc(s) { + return String(s ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); } }