feat: initial project setup for Meme Studio
Next.js 14 App Router application for managing homophone pun game levels: - Better Auth with Prisma adapter for authentication - MySQL database with Prisma ORM - Level CRUD operations with drag-and-drop reordering - Tencent COS integration for image uploads - shadcn/ui components with Tailwind CSS - TanStack Query for server state management
This commit is contained in:
134
components/levels/level-list.tsx
Normal file
134
components/levels/level-list.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
import { Level } from '@/types'
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragEndEvent,
|
||||
} from '@dnd-kit/core'
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
useSortable,
|
||||
} from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { LevelCard } from './level-card'
|
||||
|
||||
interface SortableLevelCardProps {
|
||||
level: Level
|
||||
onEdit: (level: Level) => void
|
||||
onDelete: (id: string) => void
|
||||
}
|
||||
|
||||
function SortableLevelCard({ level, onEdit, onDelete }: SortableLevelCardProps) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: level.id })
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
|
||||
<LevelCard
|
||||
level={level}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
isDragging={isDragging}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface LevelListProps {
|
||||
levels: Level[]
|
||||
onReorder: (orders: { id: string; sortOrder: number }[]) => void
|
||||
onEdit: (level: Level) => void
|
||||
onDelete: (id: string) => void
|
||||
}
|
||||
|
||||
export function LevelList({ levels, onReorder, onEdit, onDelete }: LevelListProps) {
|
||||
const [items, setItems] = useState<Level[]>(levels)
|
||||
|
||||
// Update items when levels prop changes
|
||||
if (JSON.stringify(items.map(i => i.id)) !== JSON.stringify(levels.map(l => l.id))) {
|
||||
setItems(levels)
|
||||
}
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 8,
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
)
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event
|
||||
|
||||
if (over && active.id !== over.id) {
|
||||
const oldIndex = items.findIndex((item) => item.id === active.id)
|
||||
const newIndex = items.findIndex((item) => item.id === over.id)
|
||||
|
||||
const newItems = arrayMove(items, oldIndex, newIndex)
|
||||
setItems(newItems)
|
||||
|
||||
// Notify parent of new order
|
||||
const orders = newItems.map((item, index) => ({
|
||||
id: item.id,
|
||||
sortOrder: index,
|
||||
}))
|
||||
onReorder(orders)
|
||||
}
|
||||
},
|
||||
[items, onReorder]
|
||||
)
|
||||
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
<p>暂无关卡数据</p>
|
||||
<p className="text-sm mt-2">点击上方“添加关卡”按钮创建第一个关卡</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||
<div className="space-y-3">
|
||||
{items.map((level) => (
|
||||
<SortableLevelCard
|
||||
key={level.id}
|
||||
level={level}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user