import { Coins, LockKeyhole, Trophy } from 'lucide-react';
import { type UIEvent, useEffect, useMemo, useRef, useState } from 'react';
import type {
CreationEntryConfig,
CreationEntryEventBannerConfig,
} from '../../services/creationEntryConfigService';
import {
groupVisiblePlatformCreationTypes,
type PlatformCreationTypeCard,
type PlatformCreationTypeId,
} from '../platform-entry/platformEntryCreationTypes';
/** 底部加号创作入口页的渲染参数,最近创作用作品架摘要推导模板入口。 */
type CustomWorldCreationStartCardProps = {
busy?: boolean;
entryConfig: CreationEntryConfig;
creationTypes: readonly PlatformCreationTypeCard[];
recentCreationTypeIds?: readonly PlatformCreationTypeId[];
recentWindowDays?: number;
onCreateType: (type: PlatformCreationTypeId) => void;
};
/** 创作入口公告卡兼容结构化和 HTML 两种后台配置。 */
type CreationEventBannerCard = CreationEntryEventBannerConfig;
const CREATION_ENTRY_BANNER_AUTOPLAY_MS = 4200;
const CREATION_ENTRY_RECENT_TAB_ID = '__recent_creation__';
/** 判断模板 badge 是否需要展示,普通可创建态不额外占用卡片空间。 */
function shouldShowCreationBadge(badge: string) {
const normalizedBadge = badge.trim();
return normalizedBadge !== '可创建' && normalizedBadge !== '可创作';
}
/** 从后端入口配置中解析创作入口公告位,保留旧单条字段兜底。 */
export function resolveCreationEntryEventBanners(
entryConfig: CreationEntryConfig,
): CreationEventBannerCard[] {
const configuredBanners = Array.isArray(entryConfig.eventBanners)
? entryConfig.eventBanners.filter((banner) => banner.title.trim())
: [];
return configuredBanners.length > 0
? configuredBanners
: [entryConfig.eventBanner];
}
/** 渲染创作入口公告位当前页指示点。 */
function CreationEntryBannerPager({
banners,
activeBannerIndex,
}: {
banners: readonly CreationEventBannerCard[];
activeBannerIndex: number;
}) {
return (
{banners.map((dotBanner, dotIndex) => (
))}
);
}
/** 渲染底部加号进入的创作入口页,包括后台公告位和模板分类。 */
export function CustomWorldCreationStartCard({
busy = false,
entryConfig,
creationTypes,
recentCreationTypeIds = [],
recentWindowDays = 7,
onCreateType,
}: CustomWorldCreationStartCardProps) {
const creationTypeGroups = useMemo(
() => groupVisiblePlatformCreationTypes(creationTypes),
[creationTypes],
);
const recentCreationTypes = useMemo(() => {
const creationTypeById = new Map(
creationTypes
.filter((item) => !item.hidden)
.map((item) => [item.id, item] as const),
);
return [...new Set(recentCreationTypeIds)]
.map((id) => creationTypeById.get(id))
.filter((item): item is PlatformCreationTypeCard => Boolean(item));
}, [creationTypes, recentCreationTypeIds]);
const [activeCategoryId, setActiveCategoryId] = useState(null);
const hasRecentCreationTypes = recentCreationTypes.length > 0;
const activeTabId =
activeCategoryId ??
(hasRecentCreationTypes
? CREATION_ENTRY_RECENT_TAB_ID
: (creationTypeGroups[0]?.id ?? null));
const isRecentTabActive =
hasRecentCreationTypes && activeTabId === CREATION_ENTRY_RECENT_TAB_ID;
const activeGroup = isRecentTabActive
? null
: (creationTypeGroups.find((group) => group.id === activeTabId) ??
creationTypeGroups[0] ??
null);
const visibleCreationTypes = isRecentTabActive
? recentCreationTypes
: (activeGroup?.items ?? []);
const eventBanners = useMemo(
() => resolveCreationEntryEventBanners(entryConfig),
[entryConfig],
);
const [activeBannerIndex, setActiveBannerIndex] = useState(0);
const bannerTrackRef = useRef(null);
useEffect(() => {
setActiveBannerIndex(0);
}, [eventBanners.length]);
useEffect(() => {
if (hasRecentCreationTypes) {
return;
}
setActiveCategoryId((currentId) =>
currentId === CREATION_ENTRY_RECENT_TAB_ID ? null : currentId,
);
}, [hasRecentCreationTypes]);
useEffect(() => {
if (eventBanners.length <= 1) {
return undefined;
}
const intervalId = window.setInterval(() => {
setActiveBannerIndex((currentIndex) => {
const nextIndex = (currentIndex + 1) % eventBanners.length;
const track = bannerTrackRef.current;
if (track && typeof track.scrollTo === 'function') {
track.scrollTo({
left: track.clientWidth * nextIndex,
behavior: 'smooth',
});
}
return nextIndex;
});
}, CREATION_ENTRY_BANNER_AUTOPLAY_MS);
return () => window.clearInterval(intervalId);
}, [eventBanners.length]);
/** 同步手势滑动后的 banner 页码,避免自动轮播和手动滑动状态错位。 */
function handleBannerScroll(event: UIEvent) {
const { clientWidth, scrollLeft } = event.currentTarget;
if (clientWidth <= 0) {
return;
}
const nextIndex = Math.max(
0,
Math.min(eventBanners.length - 1, Math.round(scrollLeft / clientWidth)),
);
setActiveBannerIndex((currentIndex) =>
currentIndex === nextIndex ? currentIndex : nextIndex,
);
}
return (
{eventBanners.map((banner, index) => {
const prizePoolText =
banner.prizePoolMudPoints.toLocaleString('zh-CN');
const shouldRenderHtmlBanner =
banner.renderMode === 'html' && Boolean(banner.htmlCode?.trim());
return (
{shouldRenderHtmlBanner ? (
) : (
<>
{banner.title}
{banner.description}
奖池
{prizePoolText}
泥点数
开始时间 {banner.startsAtText}
|
结束时间 {banner.endsAtText}
>
)}
{shouldRenderHtmlBanner ? (
) : null}
);
})}
{hasRecentCreationTypes ? (
) : null}
{creationTypeGroups.map((group) => {
const selected = group.id === activeGroup?.id;
return (
);
})}
{isRecentTabActive ? (
仅显示最近{recentWindowDays}天内使用过的模板
) : null}
{visibleCreationTypes.map((item) => {
const disabled = item.locked || busy;
const lockedBadge = item.badge.trim() || '暂未开放';
return (
);
})}
);
}