diff --git a/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java b/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java index 13b5571d..9f591ccf 100644 --- a/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java +++ b/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java @@ -107,7 +107,7 @@ private static String viewerShellHtml(UUID docId, String initialState) {
- +
diff --git a/src/main/resources/static/assets/viewer/viewer.css b/src/main/resources/static/assets/viewer/viewer.css index 9245c365..6aa04fe5 100644 --- a/src/main/resources/static/assets/viewer/viewer.css +++ b/src/main/resources/static/assets/viewer/viewer.css @@ -209,6 +209,42 @@ a:hover { background: #fbfcfe; } +.btn[aria-disabled="true"] { + opacity: 0.6; + cursor: not-allowed; + pointer-events: none; +} + +.spinner { + display: inline-block; + width: 1em; + height: 1em; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: spin 0.75s linear infinite; + margin-right: 8px; + vertical-align: middle; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + .preview { border-radius: 12px; border: 1px dashed rgba(16, 32, 50, 0.26); diff --git a/src/main/resources/static/assets/viewer/viewer.js b/src/main/resources/static/assets/viewer/viewer.js index 8b0dfe00..e9848169 100644 --- a/src/main/resources/static/assets/viewer/viewer.js +++ b/src/main/resources/static/assets/viewer/viewer.js @@ -45,6 +45,8 @@ function setLoading(message) { el.error.hidden = true; el.liveStatus.textContent = message; el.preview.setAttribute("aria-busy", "true"); + el.retryBtn.setAttribute("aria-disabled", "true"); + el.retryBtn.innerHTML = ` Refresh`; } function showError(message) { @@ -52,6 +54,8 @@ function showError(message) { el.errorMessage.textContent = message; el.liveStatus.textContent = ""; el.preview.setAttribute("aria-busy", "false"); + el.retryBtn.setAttribute("aria-disabled", "false"); + el.retryBtn.textContent = "Refresh"; el.errorTitle.focus(); } @@ -70,9 +74,10 @@ function clearPreview() { function renderPreviewLink(path) { const link = document.createElement("a"); link.href = path; - link.textContent = "Open artifact"; + link.innerHTML = `Open artifact (opens in a new tab)`; link.className = "btn btn-secondary"; - link.rel = "noopener"; + link.target = "_blank"; + link.rel = "noopener noreferrer"; el.preview.appendChild(link); } @@ -156,6 +161,8 @@ async function poll(docId, abortSignal) { el.preview.setAttribute("aria-busy", "false"); el.liveStatus.textContent = "Ready."; + el.retryBtn.setAttribute("aria-disabled", "false"); + el.retryBtn.textContent = "Refresh"; clearPreview(); const path = bootstrap.data.previewResourcePath; @@ -195,6 +202,9 @@ function init() { let controller = new AbortController(); const start = () => { + if (el.retryBtn.getAttribute("aria-disabled") === "true") { + return; + } controller.abort(); controller = new AbortController(); void poll(docId, controller.signal);