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 { useLocale, useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import useSWR from "swr";
export default function ServerDetailClient({
@ -20,6 +21,32 @@ export default function ServerDetailClient({
const t = useTranslations("ServerDetailClient");
const router = useRouter();
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>(
`/api/detail?server_id=${server_id}`,
nezhaFetcher,
@ -46,9 +73,7 @@ export default function ServerDetailClient({
return (
<div>
<div
onClick={() => {
router.push(`/${locale}/`);
}}
onClick={linkClick}
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
>
<BackIcon />

View File

@ -6,17 +6,53 @@ import Switch from "@/components/Switch";
import getEnv from "@/lib/env-entry";
import { nezhaFetcher } from "@/lib/utils";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import useSWR from "swr";
export default function 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, {
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
});
if (error)
return (
<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>
</div>
);
if (!data?.result) return null;
const { result } = data;
const sortedServers = result.sort((a, b) => {
const displayIndexDiff = (b.display_index || 0) - (a.display_index || 0);
if (displayIndexDiff !== 0) return displayIndexDiff;
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)];
uniqueTags.unshift(t("defaultTag"));
uniqueTags.unshift(defaultTag);
const filteredServers =
tag === t("defaultTag")
tag === defaultTag
? sortedServers
: sortedServers.filter((server) => server.tag === tag);
return (
<>
{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) => (
<ServerCard key={serverInfo.id} serverInfo={serverInfo} />
))}

View File

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

View File

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