|
|
|
@ -21,6 +21,7 @@ import "./ExploreFeatured.scss"; |
|
|
|
const FEATURED_IMAGE_SUB_TYPE = 103; |
|
|
|
const FEATURED_VIDEO_SUB_TYPE = 202; |
|
|
|
const DEFAULT_ACTIVE_FEATURED_INDEX = 1; |
|
|
|
const FEATURED_CAROUSEL_MEDIA_QUERY = "(max-width: 720px)"; |
|
|
|
|
|
|
|
export interface RecommendBannerItem { |
|
|
|
id?: number; |
|
|
|
@ -41,6 +42,34 @@ type AppDownloadDialogState = { |
|
|
|
|
|
|
|
type FeaturedCardInteractionMode = "grid" | "carousel"; |
|
|
|
|
|
|
|
function getIsFeaturedCarouselEnabled(): boolean { |
|
|
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return window.matchMedia(FEATURED_CAROUSEL_MEDIA_QUERY).matches; |
|
|
|
} |
|
|
|
|
|
|
|
function useIsFeaturedCarouselEnabled(): boolean { |
|
|
|
const [isEnabled, setIsEnabled] = useState(getIsFeaturedCarouselEnabled); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
const mediaQueryList = window.matchMedia(FEATURED_CAROUSEL_MEDIA_QUERY); |
|
|
|
const sync = () => { |
|
|
|
setIsEnabled(mediaQueryList.matches); |
|
|
|
}; |
|
|
|
|
|
|
|
sync(); |
|
|
|
mediaQueryList.addEventListener("change", sync); |
|
|
|
|
|
|
|
return () => { |
|
|
|
mediaQueryList.removeEventListener("change", sync); |
|
|
|
}; |
|
|
|
}, []); |
|
|
|
|
|
|
|
return isEnabled; |
|
|
|
} |
|
|
|
|
|
|
|
function normalizeBannerUrlList(urlList?: string[]) { |
|
|
|
if (!Array.isArray(urlList)) return []; |
|
|
|
return [...new Set(urlList.map((item) => item.trim()).filter(Boolean))]; |
|
|
|
@ -84,6 +113,7 @@ export default function ExploreFeatured() { |
|
|
|
const requestApplyMedia = useGlobalPromptImageStore((s) => s.requestApplyMedia); |
|
|
|
const requestComposerExpand = useComposerStore((s) => s.requestComposerExpand); |
|
|
|
const isSkeletonPreviewing = useExploreSkeletonPreview(); |
|
|
|
const isFeaturedCarouselEnabled = useIsFeaturedCarouselEnabled(); |
|
|
|
const featuredSwiperRef = useRef<SwiperInstance | null>(null); |
|
|
|
|
|
|
|
const [recommendBannerList, setRecommendBannerList] = useState<RecommendBannerItem[]>([]); |
|
|
|
@ -91,6 +121,12 @@ export default function ExploreFeatured() { |
|
|
|
const [activeFeaturedIndex, setActiveFeaturedIndex] = useState(DEFAULT_ACTIVE_FEATURED_INDEX); |
|
|
|
const [appDownloadDialog, setAppDownloadDialog] = useState<AppDownloadDialogState | null>(null); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (!isFeaturedCarouselEnabled) { |
|
|
|
featuredSwiperRef.current = null; |
|
|
|
} |
|
|
|
}, [isFeaturedCarouselEnabled]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
let isActive = true; |
|
|
|
|
|
|
|
@ -175,7 +211,7 @@ export default function ExploreFeatured() { |
|
|
|
); |
|
|
|
|
|
|
|
const renderFeaturedCard = (config: (typeof EXPLORE_FEATURED_ITEMS)[number], index: number, mode: FeaturedCardInteractionMode) => { |
|
|
|
const banner = recommendBannerList[index]; |
|
|
|
const banner = index < recommendBannerList.length ? recommendBannerList[index] : undefined; |
|
|
|
const urlList = normalizeBannerUrlList(banner?.urlList); |
|
|
|
const collapsedCover = getBannerCover(banner); |
|
|
|
const expandedCover = urlList[0] ?? collapsedCover; |
|
|
|
@ -236,28 +272,30 @@ export default function ExploreFeatured() { |
|
|
|
<> |
|
|
|
<div className="explorePage__featuredGrid">{EXPLORE_FEATURED_ITEMS.map((config, index) => renderFeaturedCard(config, index, "grid"))}</div> |
|
|
|
|
|
|
|
<Swiper |
|
|
|
className="explorePage__featuredCarousel" |
|
|
|
modules={[EffectCoverflow]} |
|
|
|
effect="coverflow" |
|
|
|
centeredSlides={true} |
|
|
|
slidesPerView="auto" |
|
|
|
initialSlide={DEFAULT_ACTIVE_FEATURED_INDEX} |
|
|
|
loop={EXPLORE_FEATURED_ITEMS.length > 3} |
|
|
|
coverflowEffect={{ rotate: 0, stretch: -28, depth: 60, modifier: 1, slideShadows: false }} |
|
|
|
onSwiper={(swiper) => { |
|
|
|
featuredSwiperRef.current = swiper; |
|
|
|
}} |
|
|
|
onSlideChange={(swiper) => { |
|
|
|
setActiveFeaturedIndex(swiper.realIndex); |
|
|
|
}} |
|
|
|
> |
|
|
|
{EXPLORE_FEATURED_ITEMS.map((config, index) => ( |
|
|
|
<SwiperSlide className="explorePage__featuredCarouselSlide" key={config.id}> |
|
|
|
{renderFeaturedCard(config, index, "carousel")} |
|
|
|
</SwiperSlide> |
|
|
|
))} |
|
|
|
</Swiper> |
|
|
|
{isFeaturedCarouselEnabled ? ( |
|
|
|
<Swiper |
|
|
|
className="explorePage__featuredCarousel" |
|
|
|
modules={[EffectCoverflow]} |
|
|
|
effect="coverflow" |
|
|
|
centeredSlides={true} |
|
|
|
slidesPerView="auto" |
|
|
|
initialSlide={activeFeaturedIndex} |
|
|
|
loop={EXPLORE_FEATURED_ITEMS.length > 3} |
|
|
|
coverflowEffect={{ rotate: 0, stretch: -28, depth: 60, modifier: 1, slideShadows: false }} |
|
|
|
onSwiper={(swiper) => { |
|
|
|
featuredSwiperRef.current = swiper; |
|
|
|
}} |
|
|
|
onSlideChange={(swiper) => { |
|
|
|
setActiveFeaturedIndex(swiper.realIndex); |
|
|
|
}} |
|
|
|
> |
|
|
|
{EXPLORE_FEATURED_ITEMS.map((config, index) => ( |
|
|
|
<SwiperSlide className="explorePage__featuredCarouselSlide" key={config.id}> |
|
|
|
{renderFeaturedCard(config, index, "carousel")} |
|
|
|
</SwiperSlide> |
|
|
|
))} |
|
|
|
</Swiper> |
|
|
|
) : null} |
|
|
|
</> |
|
|
|
)} |
|
|
|
|
|
|
|
|