feat: optimised scroll position

This commit is contained in:
hamster1963 2024-10-22 19:13:47 +08:00
parent 6e8691f9de
commit 1b868a90b5
4 changed files with 92 additions and 22 deletions

View File

@ -10,6 +10,7 @@ import getEnv from "@/lib/env-entry";
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils"; import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
import { useLocale, useTranslations } from "next-intl"; import { useLocale, useTranslations } from "next-intl";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
export default function ServerDetailClient({ export default function ServerDetailClient({
@ -20,6 +21,32 @@ export default function ServerDetailClient({
const t = useTranslations("ServerDetailClient"); const t = useTranslations("ServerDetailClient");
const router = useRouter(); const router = useRouter();
const locale = useLocale(); const locale = useLocale();
const [hasHistory, setHasHistory] = useState(false);
useEffect(() => {
window.scrollTo(0, 0);
}, []);
useEffect(() => {
const previousPath = sessionStorage.getItem("lastPath");
const currentPath = window.location.pathname;
if (previousPath && previousPath !== currentPath) {
setHasHistory(true);
} else {
sessionStorage.setItem("lastPath", currentPath);
}
}, []);
const linkClick = () => {
if (hasHistory) {
router.back();
} else {
router.push(`/${locale}/`);
}
};
const { data, error } = useSWR<NezhaAPISafe>( const { data, error } = useSWR<NezhaAPISafe>(
`/api/detail?server_id=${server_id}`, `/api/detail?server_id=${server_id}`,
nezhaFetcher, nezhaFetcher,
@ -46,9 +73,7 @@ export default function ServerDetailClient({
return ( return (
<div> <div>
<div <div
onClick={() => { onClick={linkClick}
router.push(`/${locale}/`);
}}
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl" className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
> >
<BackIcon /> <BackIcon />

View File

@ -6,17 +6,53 @@ import Switch from "@/components/Switch";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import { nezhaFetcher } from "@/lib/utils"; import { nezhaFetcher } from "@/lib/utils";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useState } from "react"; import { useEffect, useRef, useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
export default function ServerListClient() { export default function ServerListClient() {
const t = useTranslations("ServerListClient"); const t = useTranslations("ServerListClient");
const containerRef = useRef<HTMLDivElement>(null);
const [tag, setTag] = useState<string>(t("defaultTag")); const defaultTag = t("defaultTag");
const [tag, setTag] = useState<string>(
sessionStorage.getItem("selectedTag") || defaultTag,
);
const handleTagChange = (newTag: string) => {
setTag(newTag);
sessionStorage.setItem("selectedTag", newTag);
sessionStorage.setItem(
"scrollPosition",
String(containerRef.current?.scrollTop || 0),
);
};
const restoreScrollPosition = () => {
const savedPosition = sessionStorage.getItem("scrollPosition");
if (savedPosition && containerRef.current) {
containerRef.current.scrollTop = Number(savedPosition);
}
};
useEffect(() => {
restoreScrollPosition();
}, [tag]);
useEffect(() => {
const handleRouteChange = () => {
restoreScrollPosition();
};
window.addEventListener("popstate", handleRouteChange);
return () => {
window.removeEventListener("popstate", handleRouteChange);
};
}, []);
const { data, error } = useSWR<ServerApi>("/api/server", nezhaFetcher, { const { data, error } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000, refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
}); });
if (error) if (error)
return ( return (
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
@ -24,32 +60,38 @@ export default function ServerListClient() {
<p className="text-sm font-medium opacity-40">{t("error_message")}</p> <p className="text-sm font-medium opacity-40">{t("error_message")}</p>
</div> </div>
); );
if (!data?.result) return null; if (!data?.result) return null;
const { result } = data; const { result } = data;
const sortedServers = result.sort((a, b) => { const sortedServers = result.sort((a, b) => {
const displayIndexDiff = (b.display_index || 0) - (a.display_index || 0); const displayIndexDiff = (b.display_index || 0) - (a.display_index || 0);
if (displayIndexDiff !== 0) return displayIndexDiff; if (displayIndexDiff !== 0) return displayIndexDiff;
return a.id - b.id; return a.id - b.id;
}); });
const allTag = sortedServers.map((server) => server.tag).filter((tag) => tag); const allTag = sortedServers.map((server) => server.tag).filter(Boolean);
const uniqueTags = [...new Set(allTag)]; const uniqueTags = [...new Set(allTag)];
uniqueTags.unshift(defaultTag);
uniqueTags.unshift(t("defaultTag"));
const filteredServers = const filteredServers =
tag === t("defaultTag") tag === defaultTag
? sortedServers ? sortedServers
: sortedServers.filter((server) => server.tag === tag); : sortedServers.filter((server) => server.tag === tag);
return ( return (
<> <>
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && ( {getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
<Switch allTag={uniqueTags} nowTag={tag} setTag={setTag} /> <Switch
allTag={uniqueTags}
nowTag={tag}
onTagChange={handleTagChange}
/>
)} )}
<section className="grid grid-cols-1 gap-2 md:grid-cols-2"> <section
ref={containerRef}
className="grid grid-cols-1 gap-2 md:grid-cols-2"
>
{filteredServers.map((serverInfo) => ( {filteredServers.map((serverInfo) => (
<ServerCard key={serverInfo.id} serverInfo={serverInfo} /> <ServerCard key={serverInfo.id} serverInfo={serverInfo} />
))} ))}

View File

@ -2,32 +2,35 @@
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef, useState } from "react";
export default function Switch({ export default function Switch({
allTag, allTag,
nowTag, nowTag,
setTag, onTagChange,
}: { }: {
allTag: string[]; allTag: string[];
nowTag: string; nowTag: string;
setTag: (tag: string) => void; onTagChange: (tag: string) => void;
}) { }) {
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const savedTag = sessionStorage.getItem("selectedTag");
if (savedTag && allTag.includes(savedTag)) {
onTagChange(savedTag);
}
}, [allTag]);
useEffect(() => { useEffect(() => {
const container = scrollRef.current; const container = scrollRef.current;
if (!container) return; if (!container) return;
const isOverflowing = container.scrollWidth > container.clientWidth; const isOverflowing = container.scrollWidth > container.clientWidth;
if (!isOverflowing) return;
if (!isOverflowing) {
return;
}
const onWheel = (e: WheelEvent) => { const onWheel = (e: WheelEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
container.scrollLeft += e.deltaY; container.scrollLeft += e.deltaY;
}; };
@ -47,7 +50,7 @@ export default function Switch({
{allTag.map((tag) => ( {allTag.map((tag) => (
<div <div
key={tag} key={tag}
onClick={() => setTag(tag)} onClick={() => onTagChange(tag)}
className={cn( className={cn(
"relative cursor-pointer rounded-3xl px-2.5 py-[8px] text-[13px] font-[600] transition-all duration-500", "relative cursor-pointer rounded-3xl px-2.5 py-[8px] text-[13px] font-[600] transition-all duration-500",
nowTag === tag nowTag === tag

View File

@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3020 --turbo", "dev": "next dev -p 3020",
"start": "node .next/standalone/server.js", "start": "node .next/standalone/server.js",
"lint": "next lint", "lint": "next lint",
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/", "build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",