mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-05 13:06:36 +08:00
优化
This commit is contained in:
@@ -17,10 +17,16 @@ import {
|
||||
import type { LibraryPreview } from './librariesPageModel'
|
||||
import type { Library } from '../types'
|
||||
import type { SeriesCard } from '../utils/groupSeries'
|
||||
import {
|
||||
readLibraryListSort,
|
||||
sortLibraryPreviewsByField,
|
||||
writeLibraryListSort,
|
||||
type LibraryListSortField,
|
||||
type LibraryListSortOrder,
|
||||
} from '../utils/libraryListSort'
|
||||
import { fetchLibraries, invalidateLibraries, peekLibraries } from '../utils/libraryCache'
|
||||
import { ALL_TAG_ID, buildLibraryTagTabs, filterLibrariesByTag } from '../utils/libraryTags'
|
||||
import type { LibraryTag } from '../utils/libraryTags'
|
||||
import { sortLibraryPreviews } from '../utils/pinnedLibraries'
|
||||
import { partitionPreviewIDs } from '../utils/remoteEmby'
|
||||
|
||||
export function LibrariesPage() {
|
||||
@@ -33,6 +39,8 @@ export function LibrariesPage() {
|
||||
const [repairing, setRepairing] = useState(false)
|
||||
const [repairEpisodeArtwork, setRepairEpisodeArtwork] = useEpisodeArtworkPreference()
|
||||
const [repairMsg, setRepairMsg] = useState('')
|
||||
const [sortField, setSortField] = useState<LibraryListSortField>(() => readLibraryListSort().field)
|
||||
const [sortOrder, setSortOrder] = useState<LibraryListSortOrder>(() => readLibraryListSort().order)
|
||||
|
||||
// 缓存每个库已加载到的预览数量:入口网格只需要 2 张,横向货架需要 10 张。
|
||||
const fetchedPreviewLimitsRef = useRef<Map<string, number>>(new Map())
|
||||
@@ -146,7 +154,16 @@ export function LibrariesPage() {
|
||||
})
|
||||
}, [libraries, libraryData])
|
||||
|
||||
const sortedPreviews = useMemo(() => sortLibraryPreviews(previews, pinnedIds), [previews, pinnedIds])
|
||||
const sortedPreviews = useMemo(
|
||||
() => sortLibraryPreviewsByField(previews, pinnedIds, sortField, sortOrder),
|
||||
[previews, pinnedIds, sortField, sortOrder],
|
||||
)
|
||||
|
||||
const handleSortChange = useCallback((field: LibraryListSortField, order: LibraryListSortOrder) => {
|
||||
setSortField(field)
|
||||
setSortOrder(order)
|
||||
writeLibraryListSort(field, order)
|
||||
}, [])
|
||||
|
||||
const handleTogglePin = useCallback((libraryId: string) => {
|
||||
void togglePin(libraryId)
|
||||
@@ -180,6 +197,9 @@ export function LibrariesPage() {
|
||||
repairMsg={repairMsg}
|
||||
repairEpisodeArtwork={repairEpisodeArtwork}
|
||||
repairing={repairing}
|
||||
sortField={sortField}
|
||||
sortOrder={sortOrder}
|
||||
onSortChange={handleSortChange}
|
||||
onRepairEpisodeArtworkChange={setRepairEpisodeArtwork}
|
||||
onRepairRescrape={handleRepairRescrape}
|
||||
onManageLibraries={handleManageLibraries}
|
||||
@@ -228,7 +248,7 @@ export function LibrariesPage() {
|
||||
)
|
||||
}
|
||||
|
||||
/** 按选中标签过滤媒体库预览(ALL_TAG_ID 时原样返回,保留置顶/手动排序)。 */
|
||||
/** 按选中标签过滤媒体库预览(ALL_TAG_ID 时原样返回,保留置顶/列表排序)。 */
|
||||
function filterPreviewsByTag(previews: LibraryPreview[], tags: LibraryTag[], tagId: string): LibraryPreview[] {
|
||||
if (!tagId || tagId === ALL_TAG_ID) return previews
|
||||
const ordered = filterLibrariesByTag(
|
||||
|
||||
@@ -3,7 +3,9 @@ import { Link } from 'react-router-dom'
|
||||
import { Library as LibraryIcon, RefreshCw, Sparkles } from 'lucide-react'
|
||||
|
||||
import { EpisodeArtworkToggle } from '../components/EpisodeArtworkToggle'
|
||||
import { LibraryListSortDropdown } from '../components/LibraryListSortDropdown'
|
||||
import { useRememberedListPosition } from '../hooks/useListPositionMemory'
|
||||
import type { LibraryListSortField, LibraryListSortOrder } from '../utils/libraryListSort'
|
||||
import { HomeLibrariesSection, HomeLibraryRowSection } from './HomePageSections'
|
||||
import type { LibraryPreview } from './librariesPageModel'
|
||||
|
||||
@@ -18,6 +20,9 @@ export function LibrariesHeader({
|
||||
repairMsg,
|
||||
repairEpisodeArtwork,
|
||||
repairing,
|
||||
sortField,
|
||||
sortOrder,
|
||||
onSortChange,
|
||||
onRepairEpisodeArtworkChange,
|
||||
onRepairRescrape,
|
||||
onManageLibraries,
|
||||
@@ -28,6 +33,9 @@ export function LibrariesHeader({
|
||||
repairMsg: string
|
||||
repairEpisodeArtwork: boolean
|
||||
repairing: boolean
|
||||
sortField: LibraryListSortField
|
||||
sortOrder: LibraryListSortOrder
|
||||
onSortChange: (field: LibraryListSortField, order: LibraryListSortOrder) => void
|
||||
onRepairEpisodeArtworkChange: (value: boolean) => void
|
||||
onRepairRescrape: () => void
|
||||
onManageLibraries: () => void
|
||||
@@ -40,42 +48,45 @@ export function LibrariesHeader({
|
||||
共 {previewCount} 个目录 · {total.toLocaleString()} 个条目。每个目录直接展示最新入库内容。
|
||||
</p>
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
{repairMsg && <span className="w-full text-xs text-ink-50">{repairMsg}</span>}
|
||||
<EpisodeArtworkToggle
|
||||
checked={repairEpisodeArtwork}
|
||||
onChange={onRepairEpisodeArtworkChange}
|
||||
title="关闭后仍会获取主海报和每集文字元数据,只跳过每集图片"
|
||||
className="h-9 sm:h-10 text-xs sm:text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRepairRescrape}
|
||||
disabled={repairing}
|
||||
className="btn-outline !px-3 !py-1.5 text-xs sm:!px-4 sm:!py-2.5 sm:text-sm disabled:cursor-not-allowed disabled:opacity-60"
|
||||
title="从媒体路径回填缺失/错误的外部 ID,再批量重刮整库"
|
||||
>
|
||||
<RefreshCw size={14} className={repairing ? 'animate-spin' : ''} />
|
||||
{repairing ? '正在启动…' : '全库修复+重刮'}
|
||||
</button>
|
||||
<Link
|
||||
to="/scraper/queue"
|
||||
className="btn-outline inline-flex items-center gap-1.5 !px-3 !py-1.5 text-xs sm:!px-4 sm:!py-2.5 sm:text-sm"
|
||||
title="查看正在进行的刮削任务与进度"
|
||||
>
|
||||
<Sparkles size={14} className="text-brand-500" />
|
||||
<span>刮削队列</span>
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onManageLibraries}
|
||||
className="btn-outline !px-3 !py-1.5 text-xs sm:!px-4 sm:!py-2.5 sm:text-sm"
|
||||
>
|
||||
管理媒体库
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
<LibraryListSortDropdown value={sortField} order={sortOrder} onChange={onSortChange} />
|
||||
{isAdmin && (
|
||||
<>
|
||||
{repairMsg && <span className="w-full text-xs text-ink-50">{repairMsg}</span>}
|
||||
<EpisodeArtworkToggle
|
||||
checked={repairEpisodeArtwork}
|
||||
onChange={onRepairEpisodeArtworkChange}
|
||||
title="关闭后仍会获取主海报和每集文字元数据,只跳过每集图片"
|
||||
className="h-9 sm:h-10 text-xs sm:text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRepairRescrape}
|
||||
disabled={repairing}
|
||||
className="btn-outline !px-3 !py-1.5 text-xs sm:!px-4 sm:!py-2.5 sm:text-sm disabled:cursor-not-allowed disabled:opacity-60"
|
||||
title="从媒体路径回填缺失/错误的外部 ID,再批量重刮整库"
|
||||
>
|
||||
<RefreshCw size={14} className={repairing ? 'animate-spin' : ''} />
|
||||
{repairing ? '正在启动…' : '全库修复+重刮'}
|
||||
</button>
|
||||
<Link
|
||||
to="/scraper/queue"
|
||||
className="btn-outline inline-flex items-center gap-1.5 !px-3 !py-1.5 text-xs sm:!px-4 sm:!py-2.5 sm:text-sm"
|
||||
title="查看正在进行的刮削任务与进度"
|
||||
>
|
||||
<Sparkles size={14} className="text-brand-500" />
|
||||
<span>刮削队列</span>
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onManageLibraries}
|
||||
className="btn-outline !px-3 !py-1.5 text-xs sm:!px-4 sm:!py-2.5 sm:text-sm"
|
||||
>
|
||||
管理媒体库
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user