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 _loadingTimeout = false;
private _showProgressIndicatorTimeout?: number;
private _retryInterval?: number;
private _loadingTimeoutHandle?: number;
protected render() {
return this.error
? html`
@@ -51,8 +55,35 @@ class HaInitPage extends LitElement {
The upgrade may need a long time to complete, please be
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"}
</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) {
clearInterval(this._retryInterval);
}
if (this._loadingTimeoutHandle) {
clearTimeout(this._loadingTimeoutHandle);
}
}
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");
}
}
@@ -77,12 +114,22 @@ class HaInitPage extends LitElement {
import("../components/ha-spinner");
}, 5000);
this._retryInterval = window.setInterval(() => {
const remainingSeconds = this._retryInSeconds--;
if (remainingSeconds <= 0) {
this._retry();
}
}, 1000);
// Only start retry interval for error state
if (this.error) {
this._retryInterval = window.setInterval(() => {
const remainingSeconds = this._retryInSeconds--;
if (remainingSeconds <= 0) {
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() {
@@ -92,6 +139,25 @@ class HaInitPage extends LitElement {
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`
:host {
flex: 0;
@@ -111,12 +177,23 @@ class HaInitPage extends LitElement {
.retry-text {
margin-top: 0;
}
.hint {
margin-top: 8px;
font-size: 0.9em;
opacity: 0.7;
}
p,
#loading-text {
max-width: 350px;
color: var(--primary-text-color);
text-align: center;
}
.button-row {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 16px;
}
`;
}