Compare commits

...

1 Commits

Author SHA1 Message Date
Claude
39a7d92ff0 Add loading timeout with retry buttons to init page
- Add 30 second timeout for loading state
- Show retry options when loading takes too long
- Fix bug where retry interval ran in all states (should only run on error)
- Add 'Clear cache and retry' option to help with stuck loading
- Improve UX especially for Android users without pull-to-refresh
2025-11-07 11:37:45 +00:00

View File

@@ -11,10 +11,14 @@ class HaInitPage extends LitElement {
@state() private _retryInSeconds = 60; @state() private _retryInSeconds = 60;
@state() private _loadingTimeout = false;
private _showProgressIndicatorTimeout?: number; private _showProgressIndicatorTimeout?: number;
private _retryInterval?: number; private _retryInterval?: number;
private _loadingTimeoutHandle?: number;
protected render() { protected render() {
return this.error return this.error
? html` ? html`
@@ -51,8 +55,35 @@ class HaInitPage extends LitElement {
The upgrade may need a long time to complete, please be The upgrade may need a long time to complete, please be
patient. patient.
` `
: this._loadingTimeout
? html`
<p>Loading is taking longer than expected.</p>
<p class="hint">
This could be caused by a slow connection or cached data.
</p>
`
: "Loading data"} : "Loading data"}
</div> </div>
${this._loadingTimeout && !this.migration
? html`
<div class="button-row">
<ha-button
size="small"
appearance="plain"
@click=${this._retry}
>
Retry now
</ha-button>
<ha-button
size="small"
appearance="plain"
@click=${this._clearCacheAndRetry}
>
Clear cache and retry
</ha-button>
</div>
`
: ""}
`; `;
} }
@@ -64,10 +95,16 @@ class HaInitPage extends LitElement {
if (this._retryInterval) { if (this._retryInterval) {
clearInterval(this._retryInterval); clearInterval(this._retryInterval);
} }
if (this._loadingTimeoutHandle) {
clearTimeout(this._loadingTimeoutHandle);
}
} }
protected willUpdate(changedProperties: PropertyValues<this>) { protected willUpdate(changedProperties: PropertyValues<this>) {
if (changedProperties.has("error") && this.error) { if (
(changedProperties.has("error") && this.error) ||
(changedProperties.has("_loadingTimeout") && this._loadingTimeout)
) {
import("../components/ha-button"); import("../components/ha-button");
} }
} }
@@ -77,12 +114,22 @@ class HaInitPage extends LitElement {
import("../components/ha-spinner"); import("../components/ha-spinner");
}, 5000); }, 5000);
this._retryInterval = window.setInterval(() => { // Only start retry interval for error state
const remainingSeconds = this._retryInSeconds--; if (this.error) {
if (remainingSeconds <= 0) { this._retryInterval = window.setInterval(() => {
this._retry(); const remainingSeconds = this._retryInSeconds--;
} if (remainingSeconds <= 0) {
}, 1000); this._retry();
}
}, 1000);
}
// Start loading timeout for normal loading (not error, not migration)
if (!this.error && !this.migration) {
this._loadingTimeoutHandle = window.setTimeout(() => {
this._loadingTimeout = true;
}, 30000); // 30 seconds
}
} }
private _retry() { private _retry() {
@@ -92,6 +139,25 @@ class HaInitPage extends LitElement {
location.reload(); location.reload();
} }
private async _clearCacheAndRetry() {
// Deregister service worker and clear caches
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
await registration.unregister();
}
if ("caches" in window) {
const cacheNames = await caches.keys();
await Promise.all(cacheNames.map((name) => caches.delete(name)));
}
} catch (err: any) {
// Ignore errors, we'll reload anyway
}
}
location.reload();
}
static styles = css` static styles = css`
:host { :host {
flex: 0; flex: 0;
@@ -111,12 +177,23 @@ class HaInitPage extends LitElement {
.retry-text { .retry-text {
margin-top: 0; margin-top: 0;
} }
.hint {
margin-top: 8px;
font-size: 0.9em;
opacity: 0.7;
}
p, p,
#loading-text { #loading-text {
max-width: 350px; max-width: 350px;
color: var(--primary-text-color); color: var(--primary-text-color);
text-align: center; text-align: center;
} }
.button-row {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 16px;
}
`; `;
} }