Skip to content

Commit

Permalink
feat(web): add to collection
Browse files Browse the repository at this point in the history
  • Loading branch information
yjl9903 committed Oct 4, 2024
1 parent 3422a5d commit 7798ff5
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 42 deletions.
43 changes: 43 additions & 0 deletions apps/frontend/web/app/components/Help/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useState } from 'react';

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui/tooltip';

export function TorrentTooltip() {
const [open, setOpen] = useState(false);

return (
<TooltipProvider>
<Tooltip open={open} onOpenChange={setOpen}>
<TooltipTrigger
className="ml-2 inline-flex items-center"
onTouchStart={() => setOpen(true)}
>
<span className="i-carbon-help text-xl"></span>
</TooltipTrigger>
<TooltipContent>
<p>
桌面端推荐使用{' '}
<a
href="https://www.qbittorrent.org/"
target="_blank"
className="text-link text-link-active"
>
qBittorrent
</a>{' '}
下载
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

export function SearchTooltip() {
return (
<a
href="https://animespace.onekuma.cn/animegarden/search.html"
target="_blank"
className="i-carbon-help text-2xl text-link-active"
></a>
);
}
27 changes: 12 additions & 15 deletions apps/frontend/web/app/components/Resources/pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,15 @@ export const Pagination = (props: PaginationProps) => {
? [page - 1, page, page + 1, page + 2, page + 3]
: [page, page + 1, page + 2, page + 3, page + 4];

if (page === 1 && complete) {
// Only one page data, no pagination
return;
} else {
return (
<div className="mt-4 flex lt-md:flex-col font-sm">
<div className="text-base-400 py-1 pl3 lt-sm:pl1">
<span className="mr1 text-sm i-carbon-update-now op-80"></span>
<span className="select-none">数据更新于 </span>
<span>{formatChinaTime(timestamp)}</span>
</div>
<div className="flex-auto"></div>
return (
<div className="mt-4 flex lt-md:flex-col font-sm">
<div className="text-base-400 py-1 pl3 lt-sm:pl1">
<span className="mr1 text-sm i-carbon-update-now op-80"></span>
<span className="select-none">数据更新于 </span>
<span>{formatChinaTime(timestamp)}</span>
</div>
<div className="flex-auto"></div>
{page !== 1 && !complete && (
<div className="flex lt-md:(mt-4 justify-center) items-center gap-2 text-base-500">
<PageItem
page={page - 1}
Expand Down Expand Up @@ -72,9 +69,9 @@ export const Pagination = (props: PaginationProps) => {
<span>下一页</span>
</PageItem>
</div>
</div>
);
}
)}
</div>
);
};

const PageItem = (
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/web/app/layouts/Sidebar/sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
top: var(--sidebar-pt);
left: 0;

@apply: w-[200px] lt-lg:w-[400px];
@apply: w-[200px] lt-lg:w-[300px];
@apply: bg-layer-on z-5;
@apply: fixed border-r-1 flex-shrink-0 flex-grow-0;
}
Expand Down
61 changes: 57 additions & 4 deletions apps/frontend/web/app/layouts/Sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { collectionsAtom, type Collection } from '~/states/collection';

import './sidebar.css';
import { isOpenSidebar } from './atom';
import { stringifySearch } from '~/components/Search/utils';
import { findFansub } from 'animegarden';

type CollectionItem = Collection['items'][0];

export const Sidebar = memo(() => {
const [isOpen] = useAtom(isOpenSidebar);
Expand Down Expand Up @@ -91,7 +95,7 @@ const Collection = memo((props: { collection: Collection }) => {
return (
<div>
<div className="px2 flex items-center text-sm">
<NavLink to={`/collection`} className={'block text-xs text-base-500 text-link-active'}>
<NavLink to={`/collection/filter/${JSON.stringify(collection)}`} className={'block text-xs text-base-500 text-link-active'}>
<span className="select-none">{collection.name}</span>
</NavLink>
<div className="flex-auto flex items-center pl-2 pr-1">
Expand All @@ -102,16 +106,65 @@ const Collection = memo((props: { collection: Collection }) => {
</div>
</div>
{collection.items.length > 0 ? (
<div></div>
<div className="space-y-2">
{collection.items.map((item) => (
<NavLink
to={`/resources/1${item.searchParams}`}
key={item.searchParams}
className="block mx2 px2 py1 hover:bg-layer-subtle-overlay rounded-md text-base-800 text-xs"
>
<CollectionName item={item}></CollectionName>
</NavLink>
))}
</div>
) : (
<NavLink to='/resources/1?search=%5B"败犬女主太多了"%5D&type=動畫' className="h-[80px] px2 flex items-center justify-center text-base-700 text-link-active">
<NavLink
to='/resources/1?search=%5B"败犬女主太多了"%5D&type=動畫'
className="h-[80px] px2 flex items-center justify-center text-base-700 text-link-active"
>
<span className="text-sm">收藏一个搜索条件吧</span>
<span className="i-carbon:arrow-up-right"></span>
</NavLink>
)}
<div className="px2 flex items-center">
<div className="mt2 px2 flex items-center">
<div className="h-[1px] w-full bg-zinc-200"></div>
</div>
</div>
);
});

const CollectionName = memo((props: { item: CollectionItem }) => {
const name = inferCollectionItemName(props.item);
if (name.title) {
const fansub = name.fansubs?.map(f => f.name).join(' ');
return <span>{name.title + (fansub ? ' 字幕组:' + fansub : '')}</span>
// return <span>{name.title}</span>
}
return <span>{name.text}</span>;
});

function inferCollectionItemName(item: CollectionItem) {
let title;
if (item.search) {
title = item.search.join(' ');
}
if (item.include) {
title = item.include.join(' ');
}
if (title) {
const fansubId = item.fansubId;
const fansubs = fansubId
? fansubId.map((id) => {
const provider = 'dmhy';
const fs = findFansub(provider, id);
return fs ? fs : { provider, providerId: id, name: id };
})
: undefined;

return { title, fansubs };
}

return {
text: stringifySearch(new URLSearchParams(item.searchParams))
};
}
92 changes: 75 additions & 17 deletions apps/frontend/web/app/routes/resources.($page)/Filter.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { toast } from 'sonner';
import { format } from 'date-fns';
import { NavLink, useLocation } from '@remix-run/react';
import { useCallback } from 'react';
import { useAtom, useSetAtom } from 'jotai';

import { findFansub, type ResolvedFilterOptions, type ResourceType } from 'animegarden';

import { removeQuote } from '@/utils';
import { DisplayType, DisplayTypeColor, QueryType } from '@/constant';
import { NavLink } from '@remix-run/react';
import { APP_HOST } from '~build/env';

import { removeQuote } from '~/utils';
import { Button } from '~/components/ui/button';
import { SearchTooltip } from '~/components/Help';
import { isOpenSidebar } from '~/layouts/Sidebar/atom';
import { currentCollectionAtom } from '~/states/collection';
import { DisplayType, DisplayTypeColor, QueryType } from '~/constant';

export type DisplayResolvedFilterOptions = ReturnType<typeof resolveFilterOptions>;

Expand Down Expand Up @@ -56,7 +65,52 @@ const safeFormat: typeof format = (...args) => {
};

export function Filter(props: Props) {
const { filter } = props;
const { filter, feedURL } = props;

const location = useLocation();
const [collection, setCollection] = useAtom(currentCollectionAtom);
const setIsOpen = useSetAtom(isOpenSidebar);

const copyRSS = useCallback(
async (e: React.MouseEvent) => {
try {
if (!feedURL) throw new Error(`RSS URL is empty`);
const query = encodeURI(feedURL.slice(`/feed.xml?filter=`.length));
await navigator.clipboard.writeText(`https://${APP_HOST}/feed.xml?filter=${query}`);
toast.success('复制 RSS 订阅成功', {
dismissible: true,
duration: 3000,
closeButton: true
});
} catch (error) {
console.error(error);
toast.error('复制 RSS 订阅失败', { closeButton: true });
}
},
[feedURL]
);
const addToCollection = useCallback(() => {
if (!filter) return;
if (!collection.items.find((i) => i.searchParams === location.search)) {
setCollection({
name: collection.name,
items: [{ ...filter, name: '', searchParams: location.search }, ...collection.items]
});

toast.success(`成功添加到 ${collection.name}`, {
dismissible: true,
duration: 3000,
closeButton: true
});
} else {
toast.warning(`已添加到 ${collection.name}`, {
dismissible: true,
duration: 3000,
closeButton: true
});
}
setIsOpen(true);
}, [filter, collection, setCollection]);

if (!filter) return;

Expand Down Expand Up @@ -150,19 +204,23 @@ export function Filter(props: Props) {
))}
</div>
)}
{/* {
(search.length !== 0 || include.length !== 0 || keywords.length !== 0) && (
<div className="flex items-center gap4 pt-4">
<Button client:load variant="default" size="sm" className="copy-rss" data-rss={feedURL}>
复制 RSS 订阅链接
</Button>
<Button client:load size="sm" className="add-collection">
添加到收藏夹
</Button>
<SearchTooltip />
</div>
)
} */}
{(search.length !== 0 || include.length !== 0 || keywords.length !== 0) && (
<div className="flex items-center gap4 pt-4">
<Button
variant="default"
size="sm"
className="copy-rss"
data-rss={feedURL}
onClick={(e) => copyRSS(e)}
>
<span>复制 RSS 订阅链接</span>
</Button>
<Button size="sm" className="add-collection" onClick={() => addToCollection()}>
<span>添加到收藏夹</span>
</Button>
<SearchTooltip />
</div>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion apps/frontend/web/app/routes/resources.($page)/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function ResourcesIndex() {
<div className="w-full pt-12 pb-24">
{ok ? (
<>
<Filter filter={filter as any}></Filter>
<Filter filter={filter as any} feedURL={feedURL}></Filter>
<Resources
resources={resources}
page={page}
Expand Down
9 changes: 5 additions & 4 deletions apps/frontend/web/app/states/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { atomWithStorage, createJSONStorage } from 'jotai/utils';

export interface Collection {
name: string;
items: (ResolvedFilterOptions & { searchParams: string })[];
items: (Omit<ResolvedFilterOptions, 'page' | 'pageSize'> & {
name: string;
searchParams: string;
})[];
}

export const collectionsAtom = atomWithStorage(
Expand All @@ -15,13 +18,11 @@ export const collectionsAtom = atomWithStorage(
);

export const currentCollectionNameAtom = atomWithStorage(
'animegarden:cur_collections',
'animegarden:cur_collection_name',
'收藏夹',
createJSONStorage<string>(() => localStorage)
);

export const openCollectionAtom = atom(false);

export const currentCollectionAtom = atom<Collection, [Collection], void>(
(get) => {
const collections = get(collectionsAtom);
Expand Down

0 comments on commit 7798ff5

Please sign in to comment.