194 lines
7.0 KiB
TypeScript
194 lines
7.0 KiB
TypeScript
"use client"
|
|
|
|
import { Accordion } from "@/components/ui/accordion"
|
|
import { COOKIE_VALUES } from "@/data/general"
|
|
import { Player } from "@/lib/schema/player"
|
|
import { closestCenter, DndContext, DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"
|
|
import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from "@dnd-kit/sortable"
|
|
import { useSortable } from "@dnd-kit/sortable"
|
|
import { CSS } from "@dnd-kit/utilities"
|
|
import Cookies from "js-cookie"
|
|
import { GripVertical } from "lucide-react"
|
|
import { usePathname } from "next/navigation"
|
|
import { useEffect, useRef, useState } from "react"
|
|
|
|
import ArcadeStats from "./_stats/arcade/arcade"
|
|
import BedwarsStats from "./_stats/bedwars/bedwars"
|
|
import BlitzStats from "./_stats/blitz/blitz"
|
|
import BuildBattleStats from "./_stats/build-battle/build-battle"
|
|
import ClassicStats from "./_stats/classic/classic"
|
|
import CopsAndCrimsStats from "./_stats/copsandcrims/copsandcrims"
|
|
import DuelsStats from "./_stats/duels/duels"
|
|
import MegaWallsStats from "./_stats/megawalls/megawalls"
|
|
import MurderMysteryStats from "./_stats/murder-mystery/murder-mystery"
|
|
import PitStats from "./_stats/pit/pit"
|
|
import SkyWarsStats from "./_stats/skywars/skywars"
|
|
import SmashHerosStats from "./_stats/smashheros/smashheros"
|
|
import SpeedUHCStats from "./_stats/speeduhc/speeduhc"
|
|
import TNTGamesStats from "./_stats/tnt-games/tnt-games"
|
|
import UHCStats from "./_stats/uhc/uhc"
|
|
import WarlordsStats from "./_stats/warlords/warlords"
|
|
import WoolGamesStats from "./_stats/woolgames/woolgames"
|
|
|
|
export function PlayerPageLoadText() {
|
|
const path = usePathname()
|
|
|
|
return <p>{`Loading stats for ${path.split("/").at(-1)}`}</p>
|
|
}
|
|
|
|
interface SortableStatItemProps {
|
|
id: string
|
|
children: React.ReactNode
|
|
}
|
|
|
|
function SortableStatItem({ id, children }: SortableStatItemProps) {
|
|
const {
|
|
attributes,
|
|
listeners,
|
|
setNodeRef,
|
|
transform,
|
|
transition,
|
|
isDragging
|
|
} = useSortable({ id })
|
|
|
|
const style = {
|
|
transform: CSS.Transform.toString(transform),
|
|
transition,
|
|
opacity: isDragging ? 0.5 : 1
|
|
}
|
|
|
|
return (
|
|
<div ref={setNodeRef} style={style} className="relative group">
|
|
<div
|
|
{...attributes}
|
|
{...listeners}
|
|
className="absolute -left-6 top-1/2 z-10 p-1 opacity-0 transition-opacity -translate-y-1/2 group-hover:opacity-50 hover:opacity-100 cursor-grab active:cursor-grabbing"
|
|
>
|
|
<GripVertical className="size-4 text-muted-foreground" />
|
|
</div>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function PlayerStats(
|
|
{ stats, achievements, layout }: {
|
|
stats: NonNullable<Player["player"]["stats"]>
|
|
achievements: Player["player"]["achievements"]
|
|
layout: string[] | undefined
|
|
}
|
|
) {
|
|
const statsComponents = {
|
|
"bedwars": <BedwarsStats stats={stats.Bedwars} />,
|
|
"skywars": (
|
|
<SkyWarsStats
|
|
stats={stats.SkyWars}
|
|
achievements_skywars_opal_obsession={achievements?.["skywars_opal_obsession"] ?? 0}
|
|
/>
|
|
),
|
|
"duels": <DuelsStats stats={stats.Duels} />,
|
|
"murder-mystery": <MurderMysteryStats stats={stats.MurderMystery} />,
|
|
"build-battle": <BuildBattleStats stats={stats.BuildBattle} />,
|
|
"uhc": <UHCStats stats={stats.UHC} />,
|
|
"pit": <PitStats stats={stats.Pit} />,
|
|
"tnt-games": <TNTGamesStats stats={stats.TNTGames} />,
|
|
"megawalls": <MegaWallsStats stats={stats.MegaWalls} />,
|
|
"copsandcrims": <CopsAndCrimsStats stats={stats.CopsAndCrims} />,
|
|
"woolgames": <WoolGamesStats stats={stats.WoolGames} />,
|
|
"blitz": <BlitzStats stats={stats.Blitz} />,
|
|
"arcade": <ArcadeStats stats={stats.Arcade} />,
|
|
"speeduhc": <SpeedUHCStats stats={stats.SpeedUHC} uhcCoins={stats.UHC?.coins} />,
|
|
"smashheros": <SmashHerosStats stats={stats.SmashHeros} />,
|
|
"warlords": <WarlordsStats stats={stats.Warlords} />,
|
|
"classic": (
|
|
<ClassicStats
|
|
stats={{
|
|
arena: stats.ArenaBrawl,
|
|
paintball: stats.Paintball
|
|
}}
|
|
/>
|
|
)
|
|
} as const
|
|
|
|
const defaultOrder = Object.keys(statsComponents)
|
|
const orderToUse = layout || defaultOrder
|
|
|
|
const [statsOrder, setStatsOrder] = useState<string[]>(layout || defaultOrder)
|
|
const [isClient, setIsClient] = useState(false)
|
|
|
|
const sensors = useSensors(
|
|
useSensor(PointerSensor),
|
|
useSensor(KeyboardSensor, {
|
|
coordinateGetter: sortableKeyboardCoordinates
|
|
})
|
|
)
|
|
|
|
const cookieOpts = useRef<Parameters<typeof Cookies.set>[2]>({
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
expires: 365
|
|
})
|
|
|
|
function updateStatsOrder(arr: string[]) {
|
|
Cookies.set(COOKIE_VALUES.statsOrder, JSON.stringify(arr), cookieOpts.current)
|
|
}
|
|
|
|
function handleDragEnd(event: DragEndEvent) {
|
|
const { active, over } = event
|
|
|
|
if (over && active.id !== over.id) {
|
|
const oldIndex = statsOrder.indexOf(active.id as string)
|
|
const newIndex = statsOrder.indexOf(over.id as string)
|
|
const newOrder = arrayMove(statsOrder, oldIndex, newIndex)
|
|
|
|
setStatsOrder(newOrder)
|
|
updateStatsOrder(newOrder)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
setIsClient(true)
|
|
if (layout && layout.length > 0) {
|
|
setStatsOrder(layout)
|
|
}
|
|
const cookie = Cookies.get(COOKIE_VALUES.statsOrder)
|
|
if (cookie) {
|
|
Cookies.set(COOKIE_VALUES.statsOrder, cookie, cookieOpts.current)
|
|
}
|
|
}, [layout, cookieOpts])
|
|
|
|
if (!isClient) {
|
|
return (
|
|
<div className="mx-auto w-full lg:mx-0 lg:w-3/4 max-w-120 md:max-w-7/10">
|
|
<Accordion type="multiple" className="space-y-4">
|
|
{orderToUse.map((statKey) => (
|
|
<div key={statKey}>
|
|
{statsComponents[statKey as keyof typeof statsComponents]}
|
|
</div>
|
|
))}
|
|
</Accordion>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto w-full lg:mx-0 lg:w-3/4 max-w-120 md:max-w-7/10">
|
|
<DndContext
|
|
sensors={sensors}
|
|
collisionDetection={closestCenter}
|
|
onDragEnd={handleDragEnd}
|
|
>
|
|
<SortableContext items={statsOrder} strategy={verticalListSortingStrategy}>
|
|
<Accordion type="multiple" className="space-y-4">
|
|
{statsOrder.map((statKey) => (
|
|
<SortableStatItem key={statKey} id={statKey}>
|
|
{statsComponents[statKey as keyof typeof statsComponents]}
|
|
</SortableStatItem>
|
|
))}
|
|
</Accordion>
|
|
</SortableContext>
|
|
</DndContext>
|
|
</div>
|
|
)
|
|
}
|