8000 feat(web): main content · yjl9903/AnimeGarden@9b198dd · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit 9b198dd

Browse files
committed
feat(web): main content
1 parent ebe43fd commit 9b198dd

File tree

16 files changed

+1098
-69
lines changed

16 files changed

+1098
-69
lines changed
Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,154 @@
1-
export interface ResourcesTableProps { }
1+
import type { Resource } from 'animegarden';
2+
3+
import { formatInTimeZone } from 'date-fns-tz';
4+
5+
import { getPikPakUrlChecker } from '@/utils';
6+
import { DisplayType, DisplayTypeColor } from '@/constant';
7+
8+
import { Tag } from './tag';
9+
10+
export interface ResourcesTableProps {
11+
className?: string;
12+
13+
resources: Resource<{ tracker: true }>[];
14+
}
15+
16+
function getDetailHref(r: Resource) {
17+
return `/detail/${r.provider}/${r.providerId}`;
18+
}
19+
20+
function followSearch(params: Record<string, string>) {
21+
// const s = new URLSearchParams(search);
22+
const s = new URLSearchParams();
23+
for (const [key, value] of Object.entries(params)) {
24+
s.set(key, value);
25+
}
26+
return s.toString();
27+
}
228

329
export default function ResourcesTable(props: ResourcesTableProps) {
4-
return <table></table>
5-
}
30+
const { resources, className } = props;
31+
32+
return (
33+
<div className={'overflow-y-auto w-full' + (className ? ' ' + className : '')}>
34+
<table className="resources-table border-collapse min-y-[1080px] w-full">
35+
<colgroup>
36+
<col className="py3 w-[160px] min-w-[100px] lt-lg:w-[100px] lt-sm:w-[100px]" />
37+
<col className="py3 px2 text-left min-w-[600px] lg:min-w-[480px] md:min-w-[300px]" />
38+
<col className="w-[1px] whitespace-nowrap" />
39+
<col className="w-[1px] whitespace-nowrap" />
40+
</colgroup>
41+
<thead className="resources-table-head border-b border-b-2 text-lg lt-lg:text-base">
42+
<tr className="">
43+
<th className="py3 w-[160px] min-w-[100px] lt-lg:w-[100px] lt-sm:w-[100px]">
44+
发布时间
45+
</th>
46+
<th className="py3 px2 text-left min-w-[600px] lg:min-w-[480px] md:min-w-[300px]">
47+
标题
48+
</th>
49+
<th className="py3">发布者</th>
50+
<th className="py3 px2 text-left">播放 / 下载</th>
51+
</tr>
52+
</thead>
53+
<tbody className="resources-table-body divide-y border-b text-sm lt-lg:text-xs">
54+
{resources.map((r) => (
55+
<tr key={`${r.provider}/${r.providerId}`} className="">
56 10000 +
<td className="py2 text-center">
57+
<a
58+
href={getDetailHref(r)}
59+
className="text-link-active"
60+
aria-label={`Go to resource detail of ${r.title}`}
61+
>
62+
{formatInTimeZone(new Date(r.createdAt), 'Asia/Shanghai', 'yyyy-MM-dd HH:mm')}
63+
</a>
64+
</td>
65+
<td className="py2 pl2 lt-sm:pl1">
66+
<div className="flex items-center justify-start">
67+
<div className="flex-1">
68+
<span className="mr3">
69+
{['動畫', '季度全集', '日劇', '特攝'].includes(r.type) ? (
70+
<>
71+
<a
72+
href={getPikPakUrlChecker(r.magnet)}
73+
className="text-link mr1"
74+
aria-label={`Go to download resource of ${r.title}`}
75+
target="_blank"
76+
>
77+
{r.title}
78+
</a>
79+
<a
80+
href={getDetailHref(r)}
81+
className="text-link i-carbon-launch vertical-middle"
82+
aria-label={`Go to resource detail of ${r.title}`}
83+
/>
84+
</>
85+
) : (
86+
<a
87+
href={getDetailHref(r)}
88+
className="text-link"
89+
aria-label={`Go to resource detail of ${r.title}`}
90+
>
91+
{r.title}
92+
</a>
93+
)}
94+
</span>
95+
<a
96+
href={`/resources/1?${followSearch({ type: r.type })}`}
97+
className="inline-block select-none"
98+
aria-label={`Go to resources list of type ${r.type}`}
99+
>
100+
<Tag
101+
text={r.type in DisplayType ? DisplayType[r.type] : r.type}
102+
color="bg-light-600 hover:bg-light-700"
103+
className={`px2 py1 text-xs text-center text-base-600 ${DisplayTypeColor[r.type]} `}
104+
/>
105+
</a>
106+
</div>
107+
</div>
108+
</td>
109+
<td className="py2 px2 lt-sm:px0">
110+
<div className="flex justify-center items-center">
111+
{r.fansub ? (
112+
<a
113+
href={`/resources/1?${followSearch({ fansubId: r.fansub.id })}`}
114+
className="block w-max"
115+
aria-label={`Go to resources list of fansub ${r.fansub.name}`}
116+
>
117+
<Tag text={r.fansub.name} className={`text-xs hover:bg-gray-300`} />
118+
</a>
119+
) : r.publisher ? (
120+
<a
121+
href={`/resources/1?${followSearch({ publisherId: r.publisher.id })}`}
122+
className="block w-max"
123+
aria-label={`Go to resources list of publisher ${r.publisher.name}`}
124+
>
125+
<Tag text={r.publisher.name} className={`text-xs hover:bg-gray-300`} />
126+
</a>
127+
) : null}
128+
</div>
129+
</td>
130+
<td className="py2 px2">
131+
<div className="flex gap1 items-center justify-start">
132+
<a
133+
href={getPikPakUrlChecker(r.magnet)}
134+
data-resource-title={r.title}
135+
className="play i-carbon-play text-2xl text-base-500 hover:text-base-900"
136+
aria-label="Play resource"
137+
target="_blank"
138+
/>
139+
<a
140+
href={r.magnet + r.tracker}
141+
data-resource-title={r.title}
142+
className="download i-carbon-download text-2xl text-base-500 hover:text-base-900"
143+
aria-label="Download resource"
144+
/>
145+
<span className="text-xs text-base-400 whitespace-nowrap">{r.size}</span>
146+
</div>
147+
</td>
148+
</tr>
149+
))}
150+
</tbody>
151+
</table>
152+
</div>
153+
);
154+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function Tag(props: { text: string; color?: string; className?: string }) {
2+
const { text, className = '', color = 'bg-gray-200' } = props;
3+
4+
return (
5+
<span
6+
className={`
7+
inline-flex items-center gap1
8+
rounded-md px2 py1 font-mono
9+
transition transition-colors
10+
text-base-800 ${color} ${className}`}
11+
>
12+
{text}
13+
</span>
14+
);
15+
}

apps/frontend/web/app/hooks/scroll.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import { useDocument } from './document';
55
export const useDocumentScroll = () => {
66
const document = useDocument();
77

8-
const [state, setState] = useState({
9-
x: 0,
10-
y: 0
11-
});
8+
const [state, setState] = useState({ x: 0, y: 0 });
129

1310
if (!import.meta.env.SSR) {
1411
useLayoutEffect(() => {
Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import Search from '@/components/Search';
2-
import { Sidebar } from '@/components/Sidebar';
31
import { NavLink } from '@remix-run/react';
4-
import { atom, useAtom } from 'jotai';
5-
import { useEffect, useRef, useState } from 'react';
2+
import { useEffect, useState } from 'react';
63

4+
import Search from '~/components/Search';
5+
import { Sidebar } from '~/components/Sidebar';
76
import { useDocumentScroll } from '~/hooks';
87

98
const NavHeight = 68;
@@ -12,24 +11,57 @@ const MaxPaddingBottom = 36;
1211
const SearchHeight = NavHeight;
1312

1413
export const useHero = () => {
14+
const [isClient, setIsClient] = useState(false);
15+
16+
useEffect(() => {
17+
setIsClient(true);
18+
}, []);
19+
1520
const { y } = useDocumentScroll();
1621

1722
const paddingTop = y <= MaxPaddingTop ? MaxPaddingTop - y : 0;
1823

19-
const paddingBottom = Math.max(y > MaxPaddingTop ? MaxPaddingBottom - (y - MaxPaddingTop) : MaxPaddingBottom, 0);
24+
const paddingBottom = Math.max(
25+
y > MaxPaddingTop ? MaxPaddingBottom - (y - MaxPaddingTop) : MaxPaddingBottom,
26+
0
27+
);
2028

2129
const height = paddingTop + SearchHeight + paddingBottom;
2230

23-
return { height, paddingTop, paddingBottom };
31+
return {
32+
height,
33+
paddingTop,
34+
paddingBottom,
35+
injectScript: `requestAnimationFrame(() => {
36+
const y = document.documentElement.scrollTop;
37+
const paddingTop = y <= ${MaxPaddingTop} ? ${MaxPaddingTop} - y : 0;
38+
const paddingBottom = Math.max(
39+
y > ${MaxPaddingTop} ? ${MaxPaddingBottom} - (y - ${MaxPaddingTop}) : ${MaxPaddingBottom},
40+
0
41+
);
42+
const height = paddingTop + ${SearchHeight} + paddingBottom;
43+
44+
const hero = document.querySelector('.hero');
45+
if (hero) hero.style.height = height + 'px';
46+
const top = document.querySelector('.hero-top');
47+
if (top) top.style.top = (paddingTop - ${MaxPaddingTop}) + 'px';
48+
const search = document.querySelector('.hero-search');
49+
if (search) search.style.top = paddingTop + 'px';
50+
const bottom = document.querySelector('.hero-bottom');
51+
if (bottom) { bottom.style.top = (paddingTop + ${SearchHeight}) + 'px'; bottom.style.height = paddingBottom + 'px'; }
52+
});`
53+
};
2454
};
2555

2656
export default function Layout(props: { children?: React.ReactNode }) {
27-
const { height, paddingTop, paddingBottom } = useHero();
28-
2957
return (
3058
<div className="w-full" style={{ '--nav-height': `${NavHeight}px` }}>
31-
<Hero height={height} paddingTop={paddingTop} paddingBottom={paddingBottom}></Hero>
32-
<div className="flex" style={{ paddingTop: `${MaxPaddingTop + NavHeight + MaxPaddingBottom}px` }}>
59+
<Hero></Hero>
60+
<div
61+
className="flex"
62+
style={{ paddingTop: `${MaxPaddingTop + NavHeight + MaxPaddingBottom}px` }}
63+
></div>
64+
<div className="w-full flex">
3365
<Sidebar></Sidebar>
3466
<div className="flex-auto flex items-center">
3567
<div className="main">{props.children}</div>
@@ -39,39 +71,78 @@ export default function Layout(props: { children?: React.ReactNode }) {
3971
);
4072
}
4173

42-
function Hero(props: { height: number, paddingTop: number; paddingBottom: number }) {
43-
const { height, paddingTop, paddingBottom } = props;
74+
function Hero() {
75+
const { height, paddingTop, paddingBottom, injectScript } = useHero();
4476

4577
return (
46-
<div className="w-full fixed bg-[#fef8f7]" style={{ height: `${height}px` }}>
78+
<div className="w-full">
79+
<div
80+
className="hero z-1 w-full fixed bg-[#fef8f7] border-b border-b-gray-200"
81+
suppressHydrationWarning={true}
82+
style={{ height: `${height}px` }}
83+
></div>
4784
<Header></Header>
48-
<div className="hero-top w-full pt-4rem pb-3rem text-4xl font-quicksand font-bold text-center select-none outline-none absolute pointer-events-none" style={{ top: `${paddingTop - MaxPaddingTop}px` }}>
49-
<NavLink to="/" className="pointer-events-auto cursor-pointer">🌸 Anime Garden</NavLink>
85+
<div
86+
className="hero-top z-10 fixed w-full pt-4rem pb-3rem text-4xl font-quicksand font-bold text-center select-none outline-none pointer-events-none"
87+
suppressHydrationWarning={true}
88+
style={{ top: `${paddingTop - MaxPaddingTop}px` }}
89+
>
90+
<NavLink to="/" className="pointer-events-auto cursor-pointer">
91+
🌸 Anime Garden
92+
</NavLink>
5093
</div>
51-
<div className="w-full flex justify-center absolute z-10" style={{ top: `${paddingTop}px`, paddingTop: '8px', paddingBottom: '8px' }}>
94+
<div
95+
className="hero-search w-full flex justify-center fixed z-10"
96+
suppressHydrationWarning={true}
97+
style={{ top: `${paddingTop}px`, paddingTop: '8px', paddingBottom: '8px' }}
98+
>
5299
<div className="h-[52px]">
53100
<Search></Search>
54101
</div>
55102
</div>
56-
<div className="hero-bottom w-full absolute" style={{ top: `${paddingTop + SearchHeight}px`, height: `${paddingBottom}px` }}></div>
103+
<div
104+
className="hero-bottom z-10 fixed w-full"
105+
suppressHydrationWarning={true}
106+
style={{ top: `${paddingTop + SearchHeight}px`, height: `${paddingBottom}px` }}
107+
></div>
108+
{injectScript && (
109+
<script
110+
dangerouslySetInnerHTML={{
111+
__html: injectScript
112+
}}
113+
/>
114+
)}
57115
</div>
58116
);
59117
}
60118

61119
function Header() {
62-
return <nav className="px-8 h-$nav-height flex gap-4 z-1 [&>div]:leading-$nav-height">
63-
<div className="text-2xl font-quicksand font-bold"><NavLink to="/">🌸</NavLink></div>
64-
<div><NavLink to="/" className="rounded-md p-2 hover:(bg-neutral-200)">动画</NavLink></div>
65-
<div><NavLink to="/resources" className="rounded-md p-2 hover:(bg-neutral-200)">资源</NavLink></div>
66-
<div className='flex-auto'></div>
67-
<div>
68-
<a
69-
href={''}
70-
target="_blank"
71-
className="inline cursor-pointer rounded-md p-2 text-[#ee802f] hover:(!text-[#ff7800] !border-b-[#ff7800] bg-neutral-200)"
72-
>
73-
<span className="i-carbon-rss mr1" /><span>RSS</span>
74-
</a>
75-
</div>
76-
</nav>
77-
}
120+
return (
121+
<nav className="z-11 fixed w-full px-8 h-$nav-height flex gap-4 [&>div]:leading-$nav-height">
122+
<div className="text-2xl font-quicksand font-bold">
123+
<NavLink to="/">🌸</NavLink>
124+
</div>
125+
<div>
126+
<NavLink to="/" className="rounded-md p-2 hover:(bg-neutral-200)">
127+
动画
128+
</NavLink>
129+
</div>
130+
<div>
131+
<NavLink to="/resources" className="rounded-md p-2 hover:(bg-neutral-200)">
132+
资源
133+
</NavLink>
134+
</div>
135+
<div className="flex-auto"></div>
136+
<div>
137+
<a
138+
href={''}
139+
target="_blank"
140+
className="inline cursor-pointer rounded-md p-2 text-[#ee802f] hover:(!text-[#ff7800] !border-b-[#ff7800] bg-neutral-200)"
141+
>
142+
<span className="i-carbon-rss mr1" />
143+
<span>RSS</span>
144+
</a>
145+
</div>
146+
</nav>
147+
);
148+
}
File renamed without changes.

apps/frontend/web/app/root.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import {
2-
Links,
3-
Meta,
4-
Outlet,
5-
Scripts,
6-
ScrollRestoration,
7-
} from "@remix-run/react";
1+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
82

9-
import { Provider } from "jotai";
3+
import { Provider } from 'jotai';
104

115
import 'virtual:uno.css';
126

7+
import './styles/sonner.css';
138
import './styles/main.css';
149

1510
import { Toaster } from '~/components/ui/sonner';
@@ -30,9 +25,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
3025
<Links />
3126
</head>
3227
<body className="font-sans">
33-
<Provider>
34-
{children}
35-
</Provider>
28+
<Provider>{children}</Provider>
3629
<Toaster />
3730
<ScrollRestoration />
3831
<Scripts />

0 commit comments

Comments
 (0)
0