feat: add overlay image loading state

This commit is contained in:
2026-06-13 07:29:44 +02:00
parent 7193457f36
commit 5d3b91aa41

View File

@@ -619,6 +619,12 @@ function htmlPage(): string {
color: var(--timeline-text);
}
.overlay-media {
position: relative;
min-height: 48vh;
background: var(--timeline-surface-strong);
}
.overlay-card header {
padding: 16px 18px;
border-bottom: 1px solid var(--timeline-border);
@@ -635,6 +641,43 @@ function htmlPage(): string {
max-height: 78vh;
object-fit: contain;
background: var(--timeline-surface-strong);
transition: opacity 120ms ease;
}
.overlay-card.loading img {
opacity: 0;
}
.overlay-loading {
position: absolute;
inset: 0;
display: none;
place-items: center;
gap: 10px;
padding: 24px;
text-align: center;
color: var(--timeline-text-muted);
}
.overlay-loading.active {
display: grid;
}
.overlay-loading .spinner {
opacity: 1;
transform: scale(1);
animation: spin 0.8s linear infinite;
}
.overlay-loading.error .spinner {
animation: none;
opacity: 0.45;
}
.overlay-loading strong {
color: var(--timeline-text);
font-size: 0.92rem;
font-weight: 600;
}
.close {
@@ -1015,7 +1058,13 @@ function htmlPage(): string {
<span>Close</span>
</button>
</header>
<img id="overlay-image" alt="" />
<div class="overlay-media">
<div class="overlay-loading" id="overlay-loading" aria-live="polite" aria-atomic="true">
<span class="spinner active" aria-hidden="true"></span>
<strong id="overlay-loading-text">Loading full-size image...</strong>
</div>
<img id="overlay-image" alt="" />
</div>
</div>
</div>
@@ -1079,6 +1128,8 @@ function htmlPage(): string {
const overlay = mustGet("overlay");
const overlayTitle = mustGet("overlay-title");
const overlayImage = mustGet("overlay-image");
const overlayLoading = mustGet("overlay-loading");
const overlayLoadingText = mustGet("overlay-loading-text");
const closeOverlay = mustGet("close-overlay");
const photoList = mustGet("photo-list");
const emptyState = mustGet("empty-state");
@@ -1104,6 +1155,7 @@ function htmlPage(): string {
const importStatus = mustGet("share-status");
const importButton = mustGet("load-share");
const stopImportButton = mustGet("cancel-share");
let overlayLoadToken = 0;
function mustGet(id) {
const element = document.getElementById(id);
@@ -1568,11 +1620,34 @@ function htmlPage(): string {
}
function openOverlay(photo) {
const requestToken = ++overlayLoadToken;
overlay.classList.add("open");
overlay.setAttribute("aria-hidden", "false");
overlayTitle.textContent = photo.name;
overlayImage.src = photo.fullUrl;
overlayImage.alt = photo.name;
overlay.classList.add("loading");
overlayLoading.classList.add("active");
overlayLoading.classList.remove("error");
overlayLoadingText.textContent = "Loading full-size image...";
overlayImage.onload = () => {
if (requestToken !== overlayLoadToken) {
return;
}
overlay.classList.remove("loading");
overlayLoading.classList.remove("active");
overlayLoading.classList.remove("error");
};
overlayImage.onerror = () => {
if (requestToken !== overlayLoadToken) {
return;
}
overlayLoadingText.textContent = "Could not load full-size image.";
overlay.classList.remove("loading");
overlayLoading.classList.add("error");
overlayLoading.classList.add("active");
};
overlayImage.removeAttribute("src");
overlayImage.src = photo.fullUrl;
}
function createPhotoMarkerIcon(photo) {
@@ -1588,8 +1663,13 @@ function htmlPage(): string {
}
function closeOverlayView() {
overlayLoadToken += 1;
overlay.classList.remove("open");
overlay.setAttribute("aria-hidden", "true");
overlay.classList.remove("loading");
overlayLoading.classList.remove("active");
overlayLoading.classList.remove("error");
overlayLoadingText.textContent = "Loading full-size image...";
overlayImage.removeAttribute("src");
}