fix: scan button visual consistency and responsive cancel
All checks were successful
CI / test (push) Successful in 23s
All checks were successful
CI / test (push) Successful in 23s
- Unified toast variant (info) for scan start/cancel - Added minWidth to scan buttons to prevent layout shift - Simplified button text (Stop Scan vs Stop Scan (X Found)) - Optimistic cancel via clearScan() for instant button response - Shows Starting… state instead of disabled during scan init
This commit is contained in:
parent
3e21acfe2b
commit
b633a23cfb
3 changed files with 28 additions and 10 deletions
|
|
@ -19,7 +19,16 @@
|
|||
"Bash(gh repo:*)",
|
||||
"Bash(git remote:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)"
|
||||
"Bash(git commit:*)",
|
||||
"Bash(npm list:*)",
|
||||
"Bash(npm show:*)",
|
||||
"Bash(gsd --version)",
|
||||
"Bash(gsd version:*)",
|
||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('version', 'no version field'\\)\\)\")",
|
||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d, indent=2\\)\\)\")",
|
||||
"Bash(gsd)",
|
||||
"Read(//home/aux/.config/**)",
|
||||
"Read(//home/aux/**)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ interface DownloadProgressContextValue {
|
|||
scanStoreSubscribe: (listener: () => void) => () => void;
|
||||
/** Get scan store snapshot */
|
||||
scanStoreGetSnapshot: () => Map<number, ScanProgress>;
|
||||
/** Clear scan state for a channel (optimistic update) */
|
||||
clearScan: (channelId: number) => void;
|
||||
}
|
||||
|
||||
const DownloadProgressContext = createContext<DownloadProgressContextValue | null>(null);
|
||||
|
|
@ -252,6 +254,7 @@ export function DownloadProgressProvider({ children }: { children: ReactNode })
|
|||
isConnected,
|
||||
scanStoreSubscribe: scanStore.subscribe,
|
||||
scanStoreGetSnapshot: scanStore.getSnapshot,
|
||||
clearScan: scanStore.clearScan.bind(scanStore),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
@ -288,7 +291,7 @@ export function useDownloadProgressConnection(): boolean {
|
|||
* Returns `{ scanning, newItemCount }` from the scan store via useSyncExternalStore.
|
||||
* Only re-renders components that use this hook when the scan store changes.
|
||||
*/
|
||||
export function useScanProgress(channelId: number): ScanProgress {
|
||||
export function useScanProgress(channelId: number): ScanProgress & { clearScan: () => void } {
|
||||
const context = useContext(DownloadProgressContext);
|
||||
if (!context) {
|
||||
throw new Error('useScanProgress must be used within a DownloadProgressProvider');
|
||||
|
|
@ -297,7 +300,11 @@ export function useScanProgress(channelId: number): ScanProgress {
|
|||
context.scanStoreSubscribe,
|
||||
context.scanStoreGetSnapshot,
|
||||
);
|
||||
return scanMap.get(channelId) ?? { scanning: false, newItemCount: 0 };
|
||||
return {
|
||||
scanning: scanMap.get(channelId)?.scanning ?? false,
|
||||
newItemCount: scanMap.get(channelId)?.newItemCount ?? 0,
|
||||
clearScan: () => context.clearScan(channelId),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Cache Injection Helper ──
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export function ChannelDetail() {
|
|||
const updateContentRating = useUpdateContentRating(channelId);
|
||||
|
||||
// ── Scan state (WebSocket-driven) ──
|
||||
const { scanning: scanInProgress, newItemCount: scanNewItemCount } = useScanProgress(channelId);
|
||||
const { scanning: scanInProgress, newItemCount: scanNewItemCount, clearScan } = useScanProgress(channelId);
|
||||
|
||||
// ── Local state ──
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
|
@ -299,7 +299,7 @@ export function ChannelDetail() {
|
|||
if (result.status === 'already_running') {
|
||||
toast('Scan already in progress', 'info');
|
||||
} else if (result.status === 'started') {
|
||||
toast('Scan started — items will appear as they\'re found', 'success');
|
||||
toast('Scan started', 'info');
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
|
|
@ -309,6 +309,8 @@ export function ChannelDetail() {
|
|||
}, [scanChannel, toast]);
|
||||
|
||||
const handleCancelScan = useCallback(() => {
|
||||
// Optimistic update: clear scan state immediately
|
||||
clearScan();
|
||||
cancelScan.mutate(undefined, {
|
||||
onSuccess: (result) => {
|
||||
if (result.cancelled) {
|
||||
|
|
@ -321,7 +323,7 @@ export function ChannelDetail() {
|
|||
toast(err instanceof Error ? err.message : 'Failed to cancel scan', 'error');
|
||||
},
|
||||
});
|
||||
}, [cancelScan, toast]);
|
||||
}, [cancelScan, toast, clearScan]);
|
||||
|
||||
const handleCollect = useCallback(() => {
|
||||
collectMonitored.mutate(undefined, {
|
||||
|
|
@ -1034,7 +1036,7 @@ export function ChannelDetail() {
|
|||
disabled={scanChannel.isPending || cancelScan.isPending}
|
||||
title={scanInProgress ? 'Cancel scan' : 'Refresh & Scan'}
|
||||
className={scanInProgress ? 'btn btn-danger' : 'btn btn-ghost'}
|
||||
style={{ padding: '4px 10px', fontSize: 'var(--font-size-xs)', opacity: (scanChannel.isPending || cancelScan.isPending) ? 0.6 : 1 }}
|
||||
style={{ padding: '4px 10px', fontSize: 'var(--font-size-xs)', minWidth: 120, opacity: (scanChannel.isPending || cancelScan.isPending) ? 0.6 : 1 }}
|
||||
>
|
||||
{scanInProgress ? (
|
||||
<Square size={12} />
|
||||
|
|
@ -1043,7 +1045,7 @@ export function ChannelDetail() {
|
|||
) : (
|
||||
<RefreshCw size={12} />
|
||||
)}
|
||||
{scanInProgress ? `Stop (${scanNewItemCount})` : 'Scan'}
|
||||
{scanInProgress ? 'Stop Scan' : scanChannel.isPending ? 'Starting…' : 'Scan'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -1359,7 +1361,7 @@ export function ChannelDetail() {
|
|||
disabled={scanChannel.isPending || cancelScan.isPending}
|
||||
title={scanInProgress ? 'Cancel scan' : 'Refresh & Scan'}
|
||||
className={scanInProgress ? 'btn btn-danger' : 'btn btn-ghost'}
|
||||
style={{ opacity: (scanChannel.isPending || cancelScan.isPending) ? 0.6 : 1 }}
|
||||
style={{ opacity: (scanChannel.isPending || cancelScan.isPending) ? 0.6 : 1, minWidth: 140 }}
|
||||
>
|
||||
{scanInProgress ? (
|
||||
<Square size={14} />
|
||||
|
|
@ -1368,7 +1370,7 @@ export function ChannelDetail() {
|
|||
) : (
|
||||
<RefreshCw size={14} />
|
||||
)}
|
||||
{scanInProgress ? `Stop Scan (${scanNewItemCount} found)` : 'Scan'}
|
||||
{scanInProgress ? 'Stop Scan' : scanChannel.isPending ? 'Starting…' : 'Scan'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCollect}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue