fixed offline mode preparation UX issue

This commit is contained in:
Dmitri 2026-04-30 10:27:40 +02:00
parent 99f126396f
commit 2bc7b64707

View File

@ -15,6 +15,7 @@ import {
mdiCloudDownload, mdiCloudDownload,
mdiCloudCheck, mdiCloudCheck,
mdiCloudOff, mdiCloudOff,
mdiLoading,
mdiFullscreen, mdiFullscreen,
mdiFullscreenExit, mdiFullscreenExit,
mdiDelete, mdiDelete,
@ -61,6 +62,27 @@ const buttonColors = {
}, },
}; };
/**
* Keyframes for spin animation (injected once)
*/
const spinKeyframes = `
@keyframes runtime-controls-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
// Inject keyframes into document head (only once)
if (typeof document !== 'undefined') {
const styleId = 'runtime-controls-spin-style';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = spinKeyframes;
document.head.appendChild(style);
}
}
/** /**
* Icon component using fixed pixel sizes * Icon component using fixed pixel sizes
*/ */
@ -68,10 +90,12 @@ function ControlIcon({
path, path,
size = 20, size = 20,
fill = 'currentColor', fill = 'currentColor',
spinning = false,
}: { }: {
path: string; path: string;
size?: number; size?: number;
fill?: string; fill?: string;
spinning?: boolean;
}) { }) {
return ( return (
<svg <svg
@ -81,6 +105,7 @@ function ControlIcon({
style={{ style={{
display: 'inline-block', display: 'inline-block',
flexShrink: 0, flexShrink: 0,
animation: spinning ? 'runtime-controls-spin 1s linear infinite' : undefined,
}} }}
> >
<path fill={fill} d={path} /> <path fill={fill} d={path} />
@ -97,12 +122,14 @@ function ControlButton({
onClick, onClick,
disabled = false, disabled = false,
title, title,
spinning = false,
}: { }: {
icon: string; icon: string;
color?: 'info' | 'success' | 'danger' | 'warning'; color?: 'info' | 'success' | 'danger' | 'warning';
onClick: () => void; onClick: () => void;
disabled?: boolean; disabled?: boolean;
title?: string; title?: string;
spinning?: boolean;
}) { }) {
const colors = buttonColors[color]; const colors = buttonColors[color];
@ -140,7 +167,7 @@ function ControlButton({
e.currentTarget.style.borderColor = colors.border; e.currentTarget.style.borderColor = colors.border;
}} }}
> >
<ControlIcon path={icon} size={20} /> <ControlIcon path={icon} size={20} spinning={spinning} />
</button> </button>
); );
} }
@ -267,19 +294,21 @@ function OfflineControl({
} }
}; };
// Determine icon and color // Determine icon, color, and spinning state
let icon = mdiCloudDownload; let icon = mdiCloudDownload;
let color: 'info' | 'success' | 'danger' | 'warning' = 'info'; let color: 'info' | 'success' | 'danger' | 'warning' = 'info';
let title = 'Download for offline'; let title = 'Download for offline';
let spinning = false;
if (isDownloaded) { if (isDownloaded) {
icon = mdiCloudCheck; icon = mdiCloudCheck;
color = 'success'; color = 'success';
title = 'Available offline'; title = 'Available offline';
} else if (isDownloading) { } else if (isDownloading) {
icon = mdiCloudDownload; icon = mdiLoading;
color = 'info'; color = 'info';
title = `Downloading ${progress}%`; title = `Downloading ${progress}%`;
spinning = true;
} else if (status === 'error') { } else if (status === 'error') {
icon = mdiCloudOff; icon = mdiCloudOff;
color = 'danger'; color = 'danger';
@ -304,6 +333,7 @@ function OfflineControl({
onClick={handleClick} onClick={handleClick}
disabled={!canStore(estimatedSize) && !isDownloaded} disabled={!canStore(estimatedSize) && !isDownloaded}
title={title} title={title}
spinning={spinning}
/> />
{isDownloaded && ( {isDownloaded && (
<DeleteButton <DeleteButton