From 58ce9f614a426c461b09f330a7fc8eaebe62964e Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sat, 3 May 2025 18:09:11 +0800 Subject: [PATCH] feat(desktop): trending filters --- .../src/modules/discover/recommendations.tsx | 28 +- .../src/modules/discover/trending.tsx | 57 --- .../renderer/src/modules/trending/index.tsx | 468 ++++++------------ .../(layer)/(subview)/discover/index.tsx | 2 +- icons/mgc/classify_2_cute_re.svg | 1 + icons/mgc/polygon_cute_re.svg | 1 + locales/app/en.json | 1 + 7 files changed, 168 insertions(+), 390 deletions(-) delete mode 100644 apps/desktop/layer/renderer/src/modules/discover/trending.tsx create mode 100644 icons/mgc/classify_2_cute_re.svg create mode 100644 icons/mgc/polygon_cute_re.svg diff --git a/apps/desktop/layer/renderer/src/modules/discover/recommendations.tsx b/apps/desktop/layer/renderer/src/modules/discover/recommendations.tsx index 89a487c21..7f8718b3d 100644 --- a/apps/desktop/layer/renderer/src/modules/discover/recommendations.tsx +++ b/apps/desktop/layer/renderer/src/modules/discover/recommendations.tsx @@ -2,7 +2,6 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" import { EmptyIcon } from "@follow/components/icons/empty.js" import { ActionButton } from "@follow/components/ui/button/action-button.js" import { Card, CardContent } from "@follow/components/ui/card/index.jsx" -import { LoadingCircle } from "@follow/components/ui/loading/index.js" import { ScrollArea } from "@follow/components/ui/scroll-area/ScrollArea.js" import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js" import { CategoryMap, RSSHubCategories } from "@follow/constants" @@ -20,7 +19,6 @@ import { useCurrentModal, useModalStack } from "~/components/ui/modal/stacked/ho import { useAuthQuery } from "~/hooks/common" import { Queries } from "~/queries" -import { TrendingButton } from "../trending" import { RecommendationCard } from "./recommendations-card" const LanguageOptions = [ @@ -69,7 +67,7 @@ export function Recommendations() { firstLoad = false - const { data, isLoading, isFetching } = rsshubPopular + const { data, isLoading } = rsshubPopular const keys = useMemo(() => { if (!data) { @@ -143,10 +141,14 @@ export function Recommendations() { } return ( -
-
-
- Preferred language: +
+
+ + {t("words.categories")} +
+
+
+ Language: -
- {isFetching && ( -
- - {t("discover.popular")} - - - -
- )}
-
+
{RSSHubCategories.map((cat) => ( { - return await apiFetch<{ - data: { - feed: FeedModel - }[] - }>("/trending/feeds", { - method: "GET", - params: { - // language: "en", - // view: 0, - limit: 20, - }, - }) - }, - }) - - return ( -
-
- - {t("words.trending")} -
-
- {data?.data?.map((item, index) => ( - -
- {index + 1} -
-
- ))} -
-
- ) -} diff --git a/apps/desktop/layer/renderer/src/modules/trending/index.tsx b/apps/desktop/layer/renderer/src/modules/trending/index.tsx index 1880aa561..60620208f 100644 --- a/apps/desktop/layer/renderer/src/modules/trending/index.tsx +++ b/apps/desktop/layer/renderer/src/modules/trending/index.tsx @@ -1,330 +1,170 @@ -import { isMobile, useMobile } from "@follow/components/hooks/useMobile.js" -import { PhUsersBold } from "@follow/components/icons/users.jsx" -import { Avatar, AvatarFallback, AvatarImage } from "@follow/components/ui/avatar/index.jsx" -import { ActionButton, Button } from "@follow/components/ui/button/index.js" -import { LoadingWithIcon } from "@follow/components/ui/loading/index.jsx" -import { ScrollArea } from "@follow/components/ui/scroll-area/index.js" -import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js" -import type { FeedModel, Models, UserModel } from "@follow/models" -import { stopPropagation } from "@follow/utils/dom" +import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js" +import { Skeleton } from "@follow/components/ui/skeleton/index.jsx" +import { views } from "@follow/constants" +import type { FeedModel } from "@follow/models" import { cn } from "@follow/utils/utils" import { useQuery } from "@tanstack/react-query" -import type { FC } from "react" +import { useState } from "react" import { useTranslation } from "react-i18next" -import { getTrendingAggregates } from "~/api/trending" -import { DrawerModalLayout } from "~/components/ui/modal/stacked/custom-modal" -import { useCurrentModal, useModalStack } from "~/components/ui/modal/stacked/hooks" -import { useFollow } from "~/hooks/biz/useFollow" -import { UrlBuilder } from "~/lib/url-builder" -import { FeedIcon } from "~/modules/feed/feed-icon" +import { useGeneralSettingKey } from "~/atoms/settings/general" +import { apiFetch } from "~/lib/api-fetch" -import { usePresentUserProfileModal } from "../profile/hooks" +import { FeedCard } from "../discover/feed-card" -interface TrendingProps { - language: string -} -export const TrendingButton = ({ language, className }: TrendingProps & { className?: string }) => { - const { present } = useModalStack() +const LanguageOptions = [ + { + label: "All", + value: "all", + }, + { + label: "English", + value: "eng", + }, + { + label: "中文", + value: "cmn", + }, +] + +type Language = (typeof LanguageOptions)[number]["value"] + +const rangeOptions = [ + { + label: "Today", + value: "1d", + }, + { + label: "Three days", + value: "3d", + }, + { + label: "This week", + value: "7d", + }, + { + label: "This month", + value: "30d", + }, +] + +type Range = (typeof rangeOptions)[number]["value"] + +const viewOptions = [ + { + label: "All", + value: "all", + }, + ...views.map((view) => ({ + label: view.name, + value: `${view.view}`, + })), +] + +type View = (typeof viewOptions)[number]["value"] + +export function Trending() { const { t } = useTranslation() - return ( - - ) -} -const TrendContent: FC = ({ language }) => { - const isMobile = useMobile() + const { t: tCommon } = useTranslation("common") + const lang = useGeneralSettingKey("language") - const { data } = useQuery({ - queryKey: ["trending", language], - queryFn: () => { - return getTrendingAggregates({ language }) + const [selectedLang, setSelectedLang] = useState(lang.startsWith("zh") ? "all" : "eng") + const [selectedRange, setSelectedRange] = useState("7d") + const [selectedView, setSelectedView] = useState("all") + + const { data, isLoading } = useQuery({ + queryKey: ["trending", selectedLang, selectedView, selectedRange], + queryFn: async () => { + return await apiFetch<{ + data: { + feed: FeedModel + }[] + }>("/trending/feeds", { + method: "GET", + params: { + language: selectedLang === "all" ? undefined : selectedLang, + view: selectedView === "all" ? undefined : Number(selectedView), + limit: 20, + }, + }) }, }) - const { t } = useTranslation() - const { dismiss } = useCurrentModal() - if (!data) - return ( -
- } - size="large" - /> -
- ) return ( -
- {!isMobile && ( -
- - {t("words.trending")} +
+
+ + {t("words.trending")} +
+
+
+ Language: + { + setSelectedLang(value as Language) + }} + triggerClassName="w-32 h-8 rounded" + size="sm" + items={LanguageOptions} + />
- )} - - - - - - - - - - +
+ Range: + { + setSelectedRange(value as Range) + }} + triggerClassName="w-32 h-8 rounded" + size="sm" + items={rangeOptions} + /> +
+
+ View: + { + setSelectedView(value as View) + }} + triggerClassName="w-32 h-8 rounded" + size="sm" + items={viewOptions} + renderItem={(item) => <>{tCommon(item.label as any)}} + /> +
+
+
+ {isLoading ? ( + <> + + + + + + + + ) : ( + data?.data?.map((item, index) => ( + +
+ {index + 1} +
+
+ )) + )} +
) } -const TrendingLists: FC<{ - data: Models.TrendingList[] -}> = ({ data }) => { - const follow = useFollow() - const { t } = useTranslation() - return ( -
-

{t("trending.list")}

- -
    - {data.map((item) => ( -
  • - -
  • - ))} -
-
- ) -} - -const UserCount: Component<{ count: number }> = ({ count, className }) => { - return ( - - - {count} - - ) -} - -interface TopUserAvatarProps { - user: UserModel - position: string -} - -const TopUserAvatar: React.FC = ({ user, position }) => ( -
-
- - - {user.name?.slice(0, 2)} - - - {user.name && ( - - {user.name} - - )} -
-) - -const TrendingUsers: FC<{ data: UserModel[] }> = ({ data }) => { - const profile = usePresentUserProfileModal("dialog") - const { t } = useTranslation() - return ( -
-

{t("trending.user")}

-
-
- -
- -
- -
- - {data.slice(0, 3).map((user, index: number) => ( - - ))} -
- - {data.length > 3 && ( -
    - {data.slice(3).map((user) => ( -
  • - -
  • - ))} -
- )} -
- ) -} - -const TrendingFeeds = ({ data }: { data: FeedModel[] }) => { - const follow = useFollow() - const { t } = useTranslation() - return ( -
-

{t("trending.feed")}

- -
    - {data.map((feed) => { - return ( -
  • - - - -
    -
    {feed.title}
    -
    -
    - -
    - - - -
    -
  • - ) - })} -
-
- ) -} - -const TrendingEntries = ({ data }: { data: Models.TrendingEntry[] }) => { - const { t } = useTranslation() - const filteredData = data.filter((entry) => !entry.url.startsWith("https://x.com")) - return ( -
-

{t("trending.entry")}

- - {filteredData.length > 0 ? ( -
    - {filteredData.map((entry) => { - return ( -
  • - - - - {entry.readCount} - -
  • - ) - })} -
- ) : ( -
{t("trending.entry_no_results")}
- )} -
- ) -} diff --git a/apps/desktop/layer/renderer/src/pages/(main)/(layer)/(subview)/discover/index.tsx b/apps/desktop/layer/renderer/src/pages/(main)/(layer)/(subview)/discover/index.tsx index f2fbf35b8..592159a5a 100644 --- a/apps/desktop/layer/renderer/src/pages/(main)/(layer)/(subview)/discover/index.tsx +++ b/apps/desktop/layer/renderer/src/pages/(main)/(layer)/(subview)/discover/index.tsx @@ -16,8 +16,8 @@ import { DiscoverImport } from "~/modules/discover/import" import { DiscoverInboxList } from "~/modules/discover/inbox-list-form" import { Recommendations } from "~/modules/discover/recommendations" import { DiscoverTransform } from "~/modules/discover/transform-form" -import { Trending } from "~/modules/discover/trending" import { DiscoverUser } from "~/modules/discover/user-form" +import { Trending } from "~/modules/trending" const tabs: { name: I18nKeys diff --git a/icons/mgc/classify_2_cute_re.svg b/icons/mgc/classify_2_cute_re.svg new file mode 100644 index 000000000..ec5b2c470 --- /dev/null +++ b/icons/mgc/classify_2_cute_re.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/mgc/polygon_cute_re.svg b/icons/mgc/polygon_cute_re.svg new file mode 100644 index 000000000..de1b27f2c --- /dev/null +++ b/icons/mgc/polygon_cute_re.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/locales/app/en.json b/locales/app/en.json index 3bf4d47cc..47ebdeba8 100644 --- a/locales/app/en.json +++ b/locales/app/en.json @@ -406,6 +406,7 @@ "words.add": "Add", "words.boost": "Boost", "words.browser": "Browser", + "words.categories": "Categories", "words.confirm": "Confirm", "words.discover": "Discover", "words.email": "Email",