mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 20:26:36 +08:00
fix: load full poster wall media
This commit is contained in:
@@ -198,8 +198,10 @@ func (s *MediaService) SearchMedia(ctx context.Context, query string, limit int)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MediaService) SearchMediaVisible(ctx context.Context, query string, limit int, visibility MediaVisibility) ([]model.Media, error) {
|
func (s *MediaService) SearchMediaVisible(ctx context.Context, query string, limit int, visibility MediaVisibility) ([]model.Media, error) {
|
||||||
if limit <= 0 || limit > 200 {
|
if limit <= 0 {
|
||||||
limit = 50
|
limit = 50
|
||||||
|
} else if limit > 2000 {
|
||||||
|
limit = 2000
|
||||||
}
|
}
|
||||||
return s.repo.Media.SearchFiltered(ctx, query, limit, repository.MediaQueryFilter{
|
return s.repo.Media.SearchFiltered(ctx, query, limit, repository.MediaQueryFilter{
|
||||||
IncludeNSFW: visibility.IncludeNSFW,
|
IncludeNSFW: visibility.IncludeNSFW,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -140,6 +141,41 @@ func TestConfiguredAdultLibrariesDoNotHideSafeLibraryWithNSFWItems(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchMediaVisibleHonorsLargePosterWallLimit(t *testing.T) {
|
||||||
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := db.AutoMigrate(&model.Library{}, &model.Media{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
repos := repository.New(db)
|
||||||
|
lib := model.Library{Name: "海报墙", Path: "/media/all", Type: "tv", Enabled: true}
|
||||||
|
if err := repos.Library.Create(t.Context(), &lib); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rows := make([]model.Media, 260)
|
||||||
|
for i := range rows {
|
||||||
|
rows[i] = model.Media{
|
||||||
|
LibraryID: lib.ID,
|
||||||
|
Title: fmt.Sprintf("节目 %03d", i),
|
||||||
|
Path: fmt.Sprintf("/media/all/show-%03d.mkv", i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := db.Create(&rows).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := NewMediaService(&config.Config{}, zap.NewNop(), repos).
|
||||||
|
SearchMediaVisible(t.Context(), "", 240, MediaVisibility{IncludeNSFW: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(items) != 240 {
|
||||||
|
t.Fatalf("large poster wall search returned %d rows, want 240", len(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func sortedMediaTitles(rows []model.Media) []string {
|
func sortedMediaTitles(rows []model.Media) []string {
|
||||||
out := make([]string, 0, len(rows))
|
out := make([]string, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
|||||||
@@ -1,24 +1,55 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { GalleryHorizontalEnd, Layers } from 'lucide-react'
|
import { GalleryHorizontalEnd, Layers } from 'lucide-react'
|
||||||
|
|
||||||
import { mediaAPI } from '../api/library'
|
import { libraryAPI } from '../api/library'
|
||||||
import { MediaCard } from '../components/MediaCard'
|
import { MediaCard } from '../components/MediaCard'
|
||||||
import { groupSeries } from '../utils/groupSeries'
|
import { groupSeries } from '../utils/groupSeries'
|
||||||
import type { Media } from '../types'
|
import type { Media } from '../types'
|
||||||
|
|
||||||
|
const POSTER_WALL_PAGE_SIZE = 2000
|
||||||
|
|
||||||
// PosterWallPage 把所有媒体的代表海报聚合到同一面墙,便于一目了然
|
// PosterWallPage 把所有媒体的代表海报聚合到同一面墙,便于一目了然
|
||||||
// 浏览整个站点的内容。所有 episode 行会按剧集折叠,避免同一海报刷屏。
|
// 浏览整个站点的内容。所有 episode 行会按剧集折叠,避免同一海报刷屏。
|
||||||
export function PosterWallPage() {
|
export function PosterWallPage() {
|
||||||
const [items, setItems] = useState<Media[]>([])
|
const [items, setItems] = useState<Media[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 拉得多一点(500),折叠后才能凑够展示量;后端 page_size 上限可能
|
let cancelled = false
|
||||||
// 需要后续观察,暂以 500 试探。
|
async function loadPosterWall() {
|
||||||
mediaAPI
|
setLoading(true)
|
||||||
.search('', 500)
|
setError('')
|
||||||
.then((d) => setItems(d.items))
|
try {
|
||||||
.finally(() => setLoading(false))
|
const libraries = await libraryAPI.list()
|
||||||
|
const all = (
|
||||||
|
await Promise.all(
|
||||||
|
libraries.map(async (library) => {
|
||||||
|
const rows: Media[] = []
|
||||||
|
let page = 1
|
||||||
|
for (;;) {
|
||||||
|
const data = await libraryAPI.listMedia(library.id, page, POSTER_WALL_PAGE_SIZE)
|
||||||
|
rows.push(...(data.items || []))
|
||||||
|
if (rows.length >= data.total || data.items.length === 0) break
|
||||||
|
page += 1
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).flat()
|
||||||
|
if (!cancelled) setItems(all)
|
||||||
|
} catch (err) {
|
||||||
|
if (!cancelled) {
|
||||||
|
setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '海报墙加载失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadPosterWall()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const cards = useMemo(() => groupSeries(items).slice(0, 240), [items])
|
const cards = useMemo(() => groupSeries(items).slice(0, 240), [items])
|
||||||
@@ -36,7 +67,12 @@ export function PosterWallPage() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{loading && <p className="text-sand-500">加载中…</p>}
|
{loading && <p className="text-sand-500">加载中…</p>}
|
||||||
{!loading && cards.length === 0 && (
|
{!loading && error && (
|
||||||
|
<p className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{!loading && !error && cards.length === 0 && (
|
||||||
<p className="text-ink-50">暂无媒体。请先添加媒体库并扫描。</p>
|
<p className="text-ink-50">暂无媒体。请先添加媒体库并扫描。</p>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10">
|
<div className="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10">
|
||||||
|
|||||||
Reference in New Issue
Block a user