Show a loading spinner on the Export/Preview buttons while exporting
The export dialog's Export and Preview buttons now disable and show a spinner with status text while the corresponding async operation is in flight, so the user knows the (sometimes slow) export is running and the app isn't stuck. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
+34
-19
@@ -413,28 +413,43 @@ function showExportDialog({
|
||||
outDir: outDirInput.value.trim() || null,
|
||||
});
|
||||
|
||||
const cancelBtn = el('button', { onClick: () => { close(); resolve(false); } }, 'Cancel');
|
||||
const previewBtn = el('button', {
|
||||
onClick: async () => {
|
||||
if (typeof onPreview !== 'function') return;
|
||||
setButtonLoading(previewBtn, true, 'Preview…');
|
||||
try {
|
||||
await onPreview(payload()); // keep dialog open so settings can be tweaked
|
||||
} finally {
|
||||
setButtonLoading(previewBtn, false);
|
||||
}
|
||||
},
|
||||
}, 'Preview');
|
||||
const exportBtn = el('button.primary', {
|
||||
onClick: async () => {
|
||||
if (typeof onExport !== 'function') return;
|
||||
cancelBtn.disabled = true;
|
||||
previewBtn.disabled = true;
|
||||
setButtonLoading(exportBtn, true, 'Exporting…');
|
||||
try {
|
||||
const ok = await onExport(payload());
|
||||
if (ok !== false) {
|
||||
close();
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
cancelBtn.disabled = false;
|
||||
previewBtn.disabled = false;
|
||||
setButtonLoading(exportBtn, false);
|
||||
}
|
||||
},
|
||||
}, 'Export');
|
||||
|
||||
const { close } = openModal({
|
||||
title: 'Export',
|
||||
body,
|
||||
footer: [
|
||||
el('button', { onClick: () => { close(); resolve(false); } }, 'Cancel'),
|
||||
el('button', {
|
||||
onClick: async () => {
|
||||
if (typeof onPreview !== 'function') return;
|
||||
await onPreview(payload()); // keep dialog open so settings can be tweaked
|
||||
},
|
||||
}, 'Preview'),
|
||||
el('button.primary', {
|
||||
onClick: async () => {
|
||||
if (typeof onExport !== 'function') return;
|
||||
const ok = await onExport(payload());
|
||||
if (ok !== false) {
|
||||
close();
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
}, 'Export'),
|
||||
],
|
||||
footer: [cancelBtn, previewBtn, exportBtn],
|
||||
wide: true,
|
||||
onClose: () => resolve(false),
|
||||
});
|
||||
|
||||
@@ -90,6 +90,25 @@ button.tool.active {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-fg);
|
||||
}
|
||||
button:disabled { opacity: 0.6; cursor: default; }
|
||||
button.loading {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border: 2px solid currentColor;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
opacity: 0.85;
|
||||
animation: spin 0.7s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
input, select, textarea {
|
||||
font: inherit;
|
||||
|
||||
@@ -34,6 +34,27 @@ function clearNode(node) {
|
||||
while (node.firstChild) node.removeChild(node.firstChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a button into/out of a busy state: disables it and swaps its label
|
||||
* for a spinner + `label` (e.g. "Exporting…"), restoring the original label
|
||||
* afterwards. Used for actions that block on a slow main-process call.
|
||||
*/
|
||||
function setButtonLoading(btn, loading, label) {
|
||||
if (loading) {
|
||||
if (btn.dataset.origLabel === undefined) btn.dataset.origLabel = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.classList.add('loading');
|
||||
clearNode(btn);
|
||||
btn.append(el('span.spinner'), label || btn.dataset.origLabel);
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
clearNode(btn);
|
||||
btn.append(btn.dataset.origLabel ?? '');
|
||||
delete btn.dataset.origLabel;
|
||||
}
|
||||
}
|
||||
|
||||
function debounce(fn, ms) {
|
||||
let t = null;
|
||||
const wrapped = (...args) => {
|
||||
|
||||
Reference in New Issue
Block a user