diff --git a/src/renderer/src/components/download/PlaylistDownloadGroup.tsx b/src/renderer/src/components/download/PlaylistDownloadGroup.tsx index e3681f4..4f9a3f4 100644 --- a/src/renderer/src/components/download/PlaylistDownloadGroup.tsx +++ b/src/renderer/src/components/download/PlaylistDownloadGroup.tsx @@ -78,11 +78,11 @@ export function PlaylistDownloadGroup({ const aggregatePercent = totalCount > 0 ? Math.min((totalProgress / totalCount) * 100, 100) : 0 return ( -
+
-
+
{canDeletePlaylist && (
diff --git a/src/renderer/src/components/download/UnifiedDownloadHistory.tsx b/src/renderer/src/components/download/UnifiedDownloadHistory.tsx index 265404d..945d593 100644 --- a/src/renderer/src/components/download/UnifiedDownloadHistory.tsx +++ b/src/renderer/src/components/download/UnifiedDownloadHistory.tsx @@ -113,6 +113,17 @@ const resolveDownloadExtension = (download: DownloadRecord): string => { return download.type === 'audio' ? 'mp3' : 'mp4' } +const isEditableTarget = (target: EventTarget | null): boolean => { + if (!target || !(target instanceof HTMLElement)) { + return false + } + if (target.isContentEditable) { + return true + } + const tagName = target.tagName + return tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT' +} + interface UnifiedDownloadHistoryProps { onOpenSupportedSites?: () => void onOpenSettings?: () => void @@ -163,6 +174,12 @@ export function UnifiedDownloadHistory({ }) }, [allRecords, statusFilter]) + const visibleHistoryIds = useMemo( + () => + filteredRecords.filter((record) => record.entryType === 'history').map((record) => record.id), + [filteredRecords] + ) + const filters: Array<{ key: StatusFilter; label: string; count: number }> = [ { key: 'all', label: t('download.all'), count: downloadStats.total }, { key: 'active', label: t('download.active'), count: downloadStats.active }, @@ -170,16 +187,34 @@ export function UnifiedDownloadHistory({ { key: 'error', label: t('download.error'), count: downloadStats.error } ] - const selectableIds = useMemo( - () => - filteredRecords.filter((record) => record.entryType === 'history').map((record) => record.id), - [filteredRecords] - ) + const selectableIds = useMemo(() => { + if (visibleHistoryIds.length === 0) { + return [] + } + const ids = new Set(visibleHistoryIds) + const playlistIds = new Set( + filteredRecords + .filter((record) => record.entryType === 'history' && record.playlistId) + .map((record) => record.playlistId as string) + ) + if (playlistIds.size === 0) { + return Array.from(ids) + } + for (const record of historyRecords) { + if (record.playlistId && playlistIds.has(record.playlistId)) { + ids.add(record.id) + } + } + return Array.from(ids) + }, [filteredRecords, historyRecords, visibleHistoryIds]) const selectableCount = selectableIds.length + const visibleSelectableCount = visibleHistoryIds.length const selectionSummary = selectableCount === 0 ? t('history.selectedCount', { count: selectedCount }) - : t('history.selectionSummary', { selected: selectedCount, total: selectableCount }) + : selectableCount > visibleSelectableCount + ? t('history.selectedCount', { count: selectedCount }) + : t('history.selectionSummary', { selected: selectedCount, total: selectableCount }) useEffect(() => { if (selectedIds.size === 0) { @@ -216,6 +251,13 @@ export function UnifiedDownloadHistory({ setSelectedIds(new Set()) } + const handleSelectAll = () => { + if (selectableIds.length === 0) { + return + } + setSelectedIds(new Set(selectableIds)) + } + const handleRequestDeleteSelected = () => { if (selectedIds.size === 0) { return @@ -398,6 +440,41 @@ export function UnifiedDownloadHistory({ return { order, groups } }, [filteredRecords]) + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.defaultPrevented) { + return + } + if (isEditableTarget(event.target)) { + return + } + if (event.key === 'Escape') { + if (confirmAction) { + return + } + if (selectedIds.size === 0) { + return + } + setSelectedIds(new Set()) + return + } + if (!(event.metaKey || event.ctrlKey)) { + return + } + if (event.key.toLowerCase() !== 'a') { + return + } + if (selectableIds.length === 0) { + return + } + event.preventDefault() + setSelectedIds(new Set(selectableIds)) + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [confirmAction, selectableIds, selectedIds]) + return (
@@ -431,6 +508,15 @@ export function UnifiedDownloadHistory({ })}
+