Add pinned repositories
This commit is contained in:
5
.idea/changes.md
generated
5
.idea/changes.md
generated
@@ -9,6 +9,11 @@
|
||||
- Bumped the Home Assistant panel asset cache-buster from `v=109` to `v=110` so the updated frontend loads reliably after deployment.
|
||||
- Bumped the integration version from `0.7.2` to `0.7.3`.
|
||||
- Added the `0.7.3` release entry to `CHANGELOG.md` with the new release-notes and project-workflow changes.
|
||||
- Added a persistent rule that commit messages, pushes, release notes, and changelog entries must never mention prompts, AI/KI tools, or `.idea` folder files.
|
||||
- Added a persistent release rule that future git tags and release names must use plain version numbers without a leading `v`.
|
||||
- Added a broader persistent rule that internal workflow or prompt/process decisions must never appear in any public-facing project communication.
|
||||
- Added a persistent language rule that all project-facing repository content must stay in English regardless of the chat language.
|
||||
- Added persistent pinned repositories support: favorites are stored in settings, exposed by the backend, filterable and sortable in the store view, and toggleable from the detail view without forcing a full repository refresh.
|
||||
|
||||
### Documented
|
||||
- Captured the verified project identity from the repository and README files: Bahmcloud Store is a Home Assistant custom integration intended to behave like a provider-neutral store for custom integrations, similar to HACS but broader than GitHub-only workflows.
|
||||
|
||||
4
.idea/start prompt.md
generated
4
.idea/start prompt.md
generated
@@ -8,9 +8,13 @@ Project identity:
|
||||
- Current real provider implementation is strongest for GitHub, GitLab, and Gitea-compatible providers. Unknown providers currently fall through the Gitea-style code paths, so do not assume every arbitrary Git provider works without verification.
|
||||
|
||||
Working rules:
|
||||
- All project-facing work must be done in English only, regardless of the language the user speaks in chat. This applies to code comments, documentation, changelog entries, release notes, commit messages, PR text, UI text, issue text, and any other project artifacts unless the user explicitly requests a specific exception for repository content.
|
||||
- Never push, commit, tag, or create a release without explicit user approval.
|
||||
- Always document every change in `.idea/changes.md` with the current date and a detailed description of what changed, why it changed, and any verification done.
|
||||
- If a new release is created, update `CHANGELOG.md` with all relevant changes since the last released version.
|
||||
- Never mention prompts, AI tools, KI tools, or files inside the `.idea` folder in commit messages, push descriptions, release notes, or changelog entries.
|
||||
- Use plain version numbers for future git tags and release names, without a leading `v` prefix. Example: `0.7.4`, not `v0.7.4`.
|
||||
- Never mention internal workflow rules or repository-internal prompt/process decisions in any public-facing project communication. This includes commit messages, push descriptions, git tags, release names, release notes, changelog entries, and similar outward-facing texts.
|
||||
- Prefer changing the real active code paths, not legacy or duplicate files.
|
||||
- When docs and code disagree, trust the current code first, then update docs to match verified behavior.
|
||||
- Do not remove user changes or perform destructive git actions unless the user explicitly asks for them.
|
||||
|
||||
@@ -90,7 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
frontend_url_path="bahmcloud-store",
|
||||
webcomponent_name="bahmcloud-store-panel",
|
||||
# IMPORTANT: bump v to avoid caching old JS
|
||||
module_url="/api/bahmcloud_store_static/panel.js?v=110",
|
||||
module_url="/api/bahmcloud_store_static/panel.js?v=111",
|
||||
sidebar_title="Bahmcloud Store",
|
||||
sidebar_icon="mdi:store",
|
||||
require_admin=True,
|
||||
|
||||
@@ -118,7 +118,7 @@ class BCSCore:
|
||||
self._installed_cache: dict[str, Any] = {}
|
||||
|
||||
# Persistent settings (UI toggles etc.)
|
||||
self.settings: dict[str, Any] = {"hacs_enabled": False}
|
||||
self.settings: dict[str, Any] = {"hacs_enabled": False, "favorite_repo_ids": []}
|
||||
|
||||
# Cached HACS metadata (display names/descriptions). Loaded from storage.
|
||||
self._hacs_meta_fetched_at: int = 0
|
||||
@@ -348,24 +348,52 @@ class BCSCore:
|
||||
|
||||
def get_settings_public(self) -> dict[str, Any]:
|
||||
"""Return UI-relevant settings (no I/O)."""
|
||||
favorite_repo_ids = self.settings.get("favorite_repo_ids") or []
|
||||
if not isinstance(favorite_repo_ids, list):
|
||||
favorite_repo_ids = []
|
||||
return {
|
||||
"hacs_enabled": bool(self.settings.get("hacs_enabled", False)),
|
||||
"favorite_repo_ids": [str(x) for x in favorite_repo_ids if str(x).strip()],
|
||||
}
|
||||
|
||||
async def set_settings(self, updates: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Persist settings and apply them."""
|
||||
safe_updates: dict[str, Any] = {}
|
||||
reload_required = False
|
||||
if "hacs_enabled" in (updates or {}):
|
||||
safe_updates["hacs_enabled"] = bool(updates.get("hacs_enabled"))
|
||||
reload_required = True
|
||||
if "favorite_repo_ids" in (updates or {}):
|
||||
raw = updates.get("favorite_repo_ids") or []
|
||||
if not isinstance(raw, list):
|
||||
raw = []
|
||||
favorite_ids: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for item in raw:
|
||||
rid = str(item or "").strip()
|
||||
if not rid or rid in seen:
|
||||
continue
|
||||
seen.add(rid)
|
||||
favorite_ids.append(rid)
|
||||
safe_updates["favorite_repo_ids"] = favorite_ids
|
||||
|
||||
merged = await self.storage.set_settings(safe_updates)
|
||||
if isinstance(merged, dict):
|
||||
self.settings.update(merged)
|
||||
|
||||
# Reload repo list after changing settings.
|
||||
await self.full_refresh(source="settings")
|
||||
if reload_required:
|
||||
await self.full_refresh(source="settings")
|
||||
else:
|
||||
self.signal_updated()
|
||||
return self.get_settings_public()
|
||||
|
||||
def is_favorite_repo(self, repo_id: str) -> bool:
|
||||
favorite_repo_ids = self.settings.get("favorite_repo_ids") or []
|
||||
if not isinstance(favorite_repo_ids, list):
|
||||
return False
|
||||
target = str(repo_id or "").strip()
|
||||
return bool(target) and target in [str(x).strip() for x in favorite_repo_ids]
|
||||
|
||||
async def refresh(self) -> None:
|
||||
index_repos, refresh_seconds = await self._load_index_repos()
|
||||
self.refresh_seconds = refresh_seconds
|
||||
@@ -1151,6 +1179,7 @@ class BCSCore:
|
||||
"installed_version": installed_version,
|
||||
"installed_manifest_version": installed_manifest_version,
|
||||
"installed_domains": installed_domains,
|
||||
"favorite": self.is_favorite_repo(r.id),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
@@ -23,6 +23,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
|
||||
// HACS toggle (settings)
|
||||
this._hacsEnabled = false;
|
||||
this._favoriteRepoIds = [];
|
||||
|
||||
this._detailRepoId = null;
|
||||
this._detailRepo = null;
|
||||
@@ -121,9 +122,15 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
|
||||
// Persistent settings (e.g. HACS toggle)
|
||||
this._hacsEnabled = !!data?.settings?.hacs_enabled;
|
||||
this._favoriteRepoIds = Array.isArray(data?.settings?.favorite_repo_ids)
|
||||
? data.settings.favorite_repo_ids.map((x) => String(x))
|
||||
: [];
|
||||
|
||||
// Sync settings from backend (e.g. HACS toggle)
|
||||
this._hacsEnabled = !!data?.settings?.hacs_enabled;
|
||||
this._favoriteRepoIds = Array.isArray(data?.settings?.favorite_repo_ids)
|
||||
? data.settings.favorite_repo_ids.map((x) => String(x))
|
||||
: [];
|
||||
|
||||
if (this._view === "detail" && this._detailRepoId && Array.isArray(data?.repos)) {
|
||||
const fresh = data.repos.find((r) => this._safeId(r?.id) === this._detailRepoId);
|
||||
@@ -143,6 +150,9 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
const resp = await this._hass.callApi("post", "bcs/settings", updates || {});
|
||||
if (resp?.ok) {
|
||||
this._hacsEnabled = !!resp?.settings?.hacs_enabled;
|
||||
this._favoriteRepoIds = Array.isArray(resp?.settings?.favorite_repo_ids)
|
||||
? resp.settings.favorite_repo_ids.map((x) => String(x))
|
||||
: [];
|
||||
}
|
||||
} catch (e) {
|
||||
// Do not fail UI for settings.
|
||||
@@ -966,6 +976,37 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
return v === true;
|
||||
}
|
||||
|
||||
_isFavoriteRepo(repoId) {
|
||||
const id = this._safeId(repoId);
|
||||
return !!id && Array.isArray(this._favoriteRepoIds) && this._favoriteRepoIds.includes(id);
|
||||
}
|
||||
|
||||
async _toggleFavorite(repoId) {
|
||||
if (!this._hass || !repoId) return;
|
||||
|
||||
const id = this._safeId(repoId);
|
||||
const current = Array.isArray(this._favoriteRepoIds) ? this._favoriteRepoIds.slice() : [];
|
||||
const next = current.includes(id)
|
||||
? current.filter((x) => x !== id)
|
||||
: current.concat([id]);
|
||||
|
||||
this._favoriteRepoIds = next;
|
||||
|
||||
if (Array.isArray(this._data?.repos)) {
|
||||
this._data.repos = this._data.repos.map((r) => {
|
||||
if (this._safeId(r?.id) !== id) return r;
|
||||
return { ...r, favorite: next.includes(id) };
|
||||
});
|
||||
}
|
||||
if (this._detailRepo && this._safeId(this._detailRepo?.id) === id) {
|
||||
this._detailRepo = { ...this._detailRepo, favorite: next.includes(id) };
|
||||
}
|
||||
|
||||
this._update();
|
||||
await this._setSettings({ favorite_repo_ids: next });
|
||||
await this._load();
|
||||
}
|
||||
|
||||
_renderStore() {
|
||||
const repos = Array.isArray(this._data.repos) ? this._data.repos : [];
|
||||
|
||||
@@ -990,11 +1031,13 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
const installed = this._asBoolStrict(r?.installed);
|
||||
const installedVersion = this._safeText(r?.installed_version);
|
||||
const updateAvailable = installed && !!latest && (!installedVersion || latest !== installedVersion);
|
||||
const favorite = this._asBoolStrict(r?.favorite) || this._isFavoriteRepo(r?.id);
|
||||
|
||||
if (this._filter === "installed" && !installed) return false;
|
||||
if (this._filter === "not_installed" && installed) return false;
|
||||
if (this._filter === "updates" && !updateAvailable) return false;
|
||||
if (this._filter === "custom" && r?.source !== "custom") return false;
|
||||
if (this._filter === "favorites" && !favorite) return false;
|
||||
|
||||
return true;
|
||||
})
|
||||
@@ -1006,16 +1049,22 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
const ainstalled = this._asBoolStrict(a?.installed);
|
||||
const ainstalledVersion = this._safeText(a?.installed_version);
|
||||
const aupdate = ainstalled && !!alatest && (!ainstalledVersion || alatest !== ainstalledVersion);
|
||||
const afavorite = this._asBoolStrict(a?.favorite) || this._isFavoriteRepo(a?.id);
|
||||
|
||||
const blatest = this._safeText(b?.latest_version);
|
||||
const binstalled = this._asBoolStrict(b?.installed);
|
||||
const binstalledVersion = this._safeText(b?.installed_version);
|
||||
const bupdate = binstalled && !!blatest && (!binstalledVersion || blatest !== binstalledVersion);
|
||||
const bfavorite = this._asBoolStrict(b?.favorite) || this._isFavoriteRepo(b?.id);
|
||||
|
||||
if (this._sort === "updates_first") {
|
||||
if (aupdate !== bupdate) return aupdate ? -1 : 1;
|
||||
return an.localeCompare(bn);
|
||||
}
|
||||
if (this._sort === "favorites_first") {
|
||||
if (afavorite !== bfavorite) return afavorite ? -1 : 1;
|
||||
return an.localeCompare(bn);
|
||||
}
|
||||
if (this._sort === "installed_first") {
|
||||
if (ainstalled !== binstalled) return ainstalled ? -1 : 1;
|
||||
return an.localeCompare(bn);
|
||||
@@ -1038,6 +1087,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
const installed = this._asBoolStrict(r?.installed);
|
||||
const installedVersion = this._safeText(r?.installed_version);
|
||||
const updateAvailable = installed && !!latest && (!installedVersion || latest !== installedVersion);
|
||||
const favorite = this._asBoolStrict(r?.favorite) || this._isFavoriteRepo(r?.id);
|
||||
|
||||
const badges = [];
|
||||
// Source badges
|
||||
@@ -1045,6 +1095,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
else if (r?.source === "hacs") badges.push("HACS");
|
||||
else if (r?.source === "custom") badges.push("Custom");
|
||||
|
||||
if (favorite) badges.push("Pinned");
|
||||
if (installed) badges.push("Installed");
|
||||
if (updateAvailable) badges.push("Update");
|
||||
|
||||
@@ -1088,6 +1139,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
</select>
|
||||
<select id="filter">
|
||||
<option value="all" ${this._filter === "all" ? "selected" : ""}>All</option>
|
||||
<option value="favorites" ${this._filter === "favorites" ? "selected" : ""}>Pinned</option>
|
||||
<option value="installed" ${this._filter === "installed" ? "selected" : ""}>Installed</option>
|
||||
<option value="not_installed" ${this._filter === "not_installed" ? "selected" : ""}>Not installed</option>
|
||||
<option value="updates" ${this._filter === "updates" ? "selected" : ""}>Updates available</option>
|
||||
@@ -1095,6 +1147,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
</select>
|
||||
<select id="sort">
|
||||
<option value="az" ${this._sort === "az" ? "selected" : ""}>A–Z</option>
|
||||
<option value="favorites_first" ${this._sort === "favorites_first" ? "selected" : ""}>Pinned first</option>
|
||||
<option value="updates_first" ${this._sort === "updates_first" ? "selected" : ""}>Updates first</option>
|
||||
<option value="installed_first" ${this._sort === "installed_first" ? "selected" : ""}>Installed first</option>
|
||||
</select>
|
||||
@@ -1260,6 +1313,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
const installedVersion = this._safeText(r?.installed_version);
|
||||
const installedDomains = Array.isArray(r?.installed_domains) ? r.installed_domains : [];
|
||||
const latestVersion = this._safeText(r?.latest_version);
|
||||
const favorite = this._asBoolStrict(r?.favorite) || this._isFavoriteRepo(repoId);
|
||||
|
||||
const busyInstall = this._installingRepoId === repoId;
|
||||
const busyUpdate = this._updatingRepoId === repoId;
|
||||
@@ -1323,6 +1377,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
const updateBtn = `<button class="primary" id="btnUpdate" ${!updateAvailable || busy ? "disabled" : ""}>${busyUpdate ? "Updating…" : updateAvailable ? "Update" : "Up to date"}</button>`;
|
||||
const uninstallBtn = `<button class="primary" id="btnUninstall" ${!installed || busy ? "disabled" : ""}>${busyUninstall ? "Uninstalling…" : "Uninstall"}</button>`;
|
||||
const restoreBtn = `<button class="primary" id="btnRestore" ${!installed || busy ? "disabled" : ""}>Restore</button>`;
|
||||
const favoriteBtn = `<button id="btnFavorite">${favorite ? "Unpin" : "Pin"}</button>`;
|
||||
|
||||
const restartHint = this._restartRequired
|
||||
? `
|
||||
@@ -1375,6 +1430,7 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
${releaseNotesBlock}
|
||||
|
||||
<div class="row" style="margin-top:14px; gap:10px; flex-wrap:wrap;">
|
||||
${favoriteBtn}
|
||||
${installBtn}
|
||||
${updateBtn}
|
||||
${uninstallBtn}
|
||||
@@ -1399,6 +1455,13 @@ class BahmcloudStorePanel extends HTMLElement {
|
||||
const btnRestart = root.getElementById("btnRestart");
|
||||
const btnReadmeToggle = root.getElementById("btnReadmeToggle");
|
||||
const selVersion = root.getElementById("selVersion");
|
||||
const btnFavorite = root.getElementById("btnFavorite");
|
||||
|
||||
if (btnFavorite) {
|
||||
btnFavorite.addEventListener("click", () => {
|
||||
if (this._detailRepoId) this._toggleFavorite(this._detailRepoId);
|
||||
});
|
||||
}
|
||||
|
||||
if (btnInstall) {
|
||||
btnInstall.addEventListener("click", () => {
|
||||
|
||||
@@ -275,6 +275,9 @@ class BCSSettingsView(HomeAssistantView):
|
||||
updates: dict[str, Any] = {}
|
||||
if "hacs_enabled" in data:
|
||||
updates["hacs_enabled"] = bool(data.get("hacs_enabled"))
|
||||
if "favorite_repo_ids" in data:
|
||||
raw = data.get("favorite_repo_ids") or []
|
||||
updates["favorite_repo_ids"] = raw if isinstance(raw, list) else []
|
||||
|
||||
try:
|
||||
settings = await self.core.set_settings(updates)
|
||||
@@ -560,6 +563,7 @@ class BCSRepoDetailView(HomeAssistantView):
|
||||
"installed_version": inst.get("installed_version"),
|
||||
"installed_manifest_version": inst.get("installed_manifest_version"),
|
||||
"installed_domains": domains,
|
||||
"favorite": self.core.is_favorite_repo(repo.id),
|
||||
}
|
||||
}, status=200)
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user