diff --git a/apps/frontend/web/app/layouts/Layout.tsx b/apps/frontend/web/app/layouts/Layout.tsx index f41f32d8..54846b44 100644 --- a/apps/frontend/web/app/layouts/Layout.tsx +++ b/apps/frontend/web/app/layouts/Layout.tsx @@ -14,7 +14,7 @@ const MaxPaddingTop = 152; const MaxPaddingBottom = 36; const SearchHeight = NavHeight; -export default function Layout(props: { children?: React.ReactNode, rss?: string }) { +export default function Layout(props: { children?: React.ReactNode; rss?: string }) { const { rss } = props; const navigation = useNavigation(); @@ -119,7 +119,7 @@ function Hero(props: { rss?: string }) { } function Header(props: { rss?: string }) { - const {rss} = props; + const { rss } = props; return ( ); diff --git a/apps/frontend/web/app/routes/resources.$page/Filter.tsx b/apps/frontend/web/app/routes/resources.$page/Filter.tsx new file mode 100644 index 00000000..36034a81 --- /dev/null +++ b/apps/frontend/web/app/routes/resources.$page/Filter.tsx @@ -0,0 +1,173 @@ +import { format } from 'date-fns'; + +import { findFansub, type ResolvedFilterOptions, type ResourceType } from 'animegarden'; + +import { removeQuote } from '@/utils'; +import { DisplayType, DisplayTypeColor, QueryType } from '@/constant'; +import { NavLink } from '@remix-run/react'; + +export type DisplayResolvedFilterOptions = ReturnType; + +export function resolveFilterOptions(filter: Omit) { + const fansubId = filter.fansubId; + const fansubs = fansubId + ? fansubId.map((id) => { + const provider = 'dmhy'; + const fs = findFansub(provider, id); + return fs ? fs : { provider, providerId: id, name: id }; + }) + : undefined; + + const rawType = ( + filter.type && filter.type in QueryType ? QueryType[filter.type] : filter.type + ) as ResourceType | undefined; + const type = (rawType && rawType in DisplayType) ? DisplayType[rawType] : rawType ?? '動畫'; + + return { + publisher: filter.publisherId, + fansubs, + type: rawType + ? { + name: type, + color: DisplayTypeColor[type as ResourceType] ?? DisplayTypeColor[rawType] + } + : undefined, + before: filter.before ? new Date(filter.before) : undefined, + after: filter.after ? new Date(filter.after) : undefined, + search: filter.search ? removeQuote(filter.search) : [], + include: filter.include ?? [], + keywords: filter.keywords ?? [], + exclude: filter.exclude ?? [] + }; +} + + +interface Props { + filter?: Omit; + feedURL?: string; +} + +const safeFormat: typeof format = (...args) => { + try { + return format(...args); + } catch (error) { + console.log(error); + return ''; + } +}; + +export function Filter(props: Props) { + const { filter } = props; + + if (!filter) return; + + const { type, fansubs, after, before, search, include, keywords, exclude } = resolveFilterOptions(filter); + + if (!(type || search.length > 0 || include.length > 0 || before || after || fansubs)) return; + + return
+ { + type && ( +
+ 类型 + {type.name} +
+ ) + } + { + fansubs && ( +
+ 字幕组 + {fansubs.map((fansub) => ( + + {fansub.name} + + ))} +
+ ) + } + { + after && ( +
+ 搜索开始于 + {safeFormat(after, 'yyyy 年 M 月 d 日 hh:mm')} +
+ ) + } + { + before && ( +
+ 搜索结束于 + {safeFormat(before, 'yyyy 年 M 月 d 日 hh:mm')} +
+ ) + } + { + search.length > 0 && ( +
+ {/* prettier-ignore */} + 标题搜索 + {search.map((i) => ( + {i} + ))} +
+ ) + } + { + search.length === 0 && include.length > 0 && ( +
+ {/* prettier-ignore */} + 标题匹配 + {include.map((i, idx) => ( + <> + {idx > 0 && |} + {/* prettier-ignore */} + {i} + + ))} +
+ ) + } + { + search.length === 0 && keywords.length > 0 && ( +
+ 包含关键词 + {keywords.map((i, idx) => ( + <> + {idx > 0 && &} + {/* prettier-ignore */} + {i} + + ))} +
+ ) + } + { + search.length === 0 && exclude.length > 0 && ( +
+ {/* prettier-ignore */} + 排除关键词 + {exclude.map((i) => ( + {i} + ))} +
+ ) + } + {/* { + (search.length !== 0 || include.length !== 0 || keywords.length !== 0) && ( +
+ + + +
+ ) + } */} +
+} \ No newline at end of file diff --git a/apps/frontend/web/app/routes/resources.$page/route.tsx b/apps/frontend/web/app/routes/resources.$page/route.tsx index 4392baec..b37190ea 100644 --- a/apps/frontend/web/app/routes/resources.$page/route.tsx +++ b/apps/frontend/web/app/routes/resources.$page/route.tsx @@ -7,6 +7,8 @@ import Layout from '@/layouts/Layout'; import Resources from '@/components/Resources'; import { fetchResources } from '@/utils'; +import { Filter } from './Filter'; + export const meta: MetaFunction = () => { return [ { title: 'Anime Garden 動漫花園資源網第三方镜像站' }, @@ -26,11 +28,12 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }; export default function ResourcesIndex() { - const { ok, resources, timestamp } = useLoaderData(); + const { ok, resources, filter, timestamp } = useLoaderData(); return (
+