diff --git a/apps/admin-x-activitypub/src/components/global/ImageLightbox.tsx b/apps/admin-x-activitypub/src/components/global/ImageLightbox.tsx index 2691530..1e04325 100644 --- a/apps/admin-x-activitypub/src/components/global/ImageLightbox.tsx +++ b/apps/admin-x-activitypub/src/components/global/ImageLightbox.tsx @@ -1,52 +1,72 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import {Button, Dialog, DialogClose, DialogContent, LucideIcon} from '@tryghost/shade'; -import {ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub'; -import {getAttachment} from '@components/feed/FeedItem'; +import React, { useCallback, useEffect, useState } from 'react'; +// 导入UI组件:按钮、对话框及内容容器、Lucide图标库 +import { Button, Dialog, DialogClose, DialogContent, LucideIcon } from '@tryghost/shade'; +// 导入活动发布对象的类型定义 +import { ObjectProperties } from '@tryghost/admin-x-framework/api/activitypub'; +// 导入获取附件的工具函数 +import { getAttachment } from '@components/feed/FeedItem'; +// 定义灯箱中图片的类型接口 export interface LightboxImage { - url: string; - alt: string; + url: string; // 图片URL + alt: string; // 图片替代文本 } +// 定义灯箱状态的类型接口 export interface LightboxState { - images: LightboxImage[]; - currentIndex: number; - isOpen: boolean; + images: LightboxImage[]; // 所有图片列表 + currentIndex: number; // 当前显示图片的索引 + isOpen: boolean; // 灯箱是否打开 } +/** + * 自定义Hook:管理图片灯箱的状态和操作 + * @param object - 包含图片的活动发布对象 + * @returns 灯箱状态及相关操作方法 + */ export function useLightboxImages(object: ObjectProperties | null) { + // 初始化灯箱状态 const [lightboxState, setLightboxState] = useState({ images: [], currentIndex: 0, isOpen: false }); + /** + * 从对象中提取所有图片 + * @param obj - 活动发布对象 + * @returns 提取出的图片数组 + */ const getAllImagesFromAttachment = (obj: ObjectProperties): LightboxImage[] => { + // 获取对象的附件 const attachment = getAttachment(obj); if (!attachment) { return []; } + // 处理附件为数组的情况 if (Array.isArray(attachment)) { return attachment.map((item, index) => ({ url: item.url, - alt: item.name || `Image-${index}` + alt: item.name || `Image-${index}` // 用索引作为默认alt文本 })); } + // 处理单个图片附件 if (attachment.mediaType?.startsWith('image/') || attachment.type === 'Image') { return [{ url: attachment.url, - alt: attachment.name || 'Image' + alt: attachment.name || 'Image' // 用默认文本作为fallback }]; } + // 处理对象中直接包含image字段的情况 if (obj.image) { let imageUrl; if (typeof obj.image === 'string') { - imageUrl = obj.image; + imageUrl = obj.image; // 图片URL直接是字符串 } else { - imageUrl = obj.image?.url; + imageUrl = obj.image?.url; // 图片是对象,取其url属性 } if (imageUrl) { @@ -57,17 +77,23 @@ export function useLightboxImages(object: ObjectProperties | null) { } } - return []; + return []; // 没有找到图片时返回空数组 }; + /** + * 打开灯箱并显示指定图片 + * @param clickedUrl - 被点击图片的URL + */ const openLightbox = (clickedUrl: string) => { if (!object) { - return; + return; // 对象为空时不执行操作 } + // 获取所有图片并找到被点击图片的索引 const images = getAllImagesFromAttachment(object); const clickedIndex = images.findIndex(img => img.url === clickedUrl); + // 找到对应图片时更新灯箱状态 if (clickedIndex !== -1) { setLightboxState({ images, @@ -77,6 +103,9 @@ export function useLightboxImages(object: ObjectProperties | null) { } }; + /** + * 关闭灯箱 + */ const closeLightbox = () => { setLightboxState(prev => ({ ...prev, @@ -84,6 +113,10 @@ export function useLightboxImages(object: ObjectProperties | null) { })); }; + /** + * 导航到指定索引的图片 + * @param newIndex - 目标图片索引 + */ const navigateToIndex = (newIndex: number) => { setLightboxState(prev => ({ ...prev, @@ -99,14 +132,18 @@ export function useLightboxImages(object: ObjectProperties | null) { }; } +// 图片灯箱组件的属性接口 interface ImageLightboxProps { - images: LightboxImage[]; - currentIndex: number; - isOpen: boolean; - onClose: () => void; - onNavigate: (newIndex: number) => void; + images: LightboxImage[]; // 图片列表 + currentIndex: number; // 当前显示图片索引 + isOpen: boolean; // 是否打开 + onClose: () => void; // 关闭回调 + onNavigate: (newIndex: number) => void; // 导航回调 } +/** + * 图片灯箱组件:用于放大查看图片,支持左右导航和键盘控制 + */ const ImageLightbox: React.FC = ({ images, currentIndex, @@ -114,44 +151,65 @@ const ImageLightbox: React.FC = ({ onClose, onNavigate }) => { + // 判断是否是第一张/最后一张图片 const isFirstImage = currentIndex === 0; const isLastImage = currentIndex === images.length - 1; + /** + * 导航到下一张图片 + * 使用useCallback缓存函数,避免不必要的重渲染 + */ const goToNext = useCallback(() => { + // 只有一张图片或已经是最后一张时不执行 if (images.length <= 1 || isLastImage) { return; } + // 计算下一张索引(循环导航) const nextIndex = (currentIndex + 1) % images.length; onNavigate(nextIndex); }, [images.length, isLastImage, currentIndex, onNavigate]); + /** + * 导航到上一张图片 + * 使用useCallback缓存函数 + */ const goToPrev = useCallback(() => { + // 只有一张图片或已经是第一张时不执行 if (images.length <= 1 || isFirstImage) { return; } + // 计算上一张索引(循环导航) const prevIndex = (currentIndex - 1 + images.length) % images.length; onNavigate(prevIndex); }, [images.length, isFirstImage, currentIndex, onNavigate]); + /** + * 监听键盘事件,支持左右箭头导航 + */ useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!isOpen) { - return; + return; // 灯箱关闭时不处理 } + // 右箭头导航到下一张(非最后一张时) if (e.key === 'ArrowRight' && !isLastImage) { goToNext(); - } else if (e.key === 'ArrowLeft' && !isFirstImage) { + } + // 左箭头导航到上一张(非第一张时) + else if (e.key === 'ArrowLeft' && !isFirstImage) { goToPrev(); } }; window.addEventListener('keydown', handleKeyDown); + // 组件卸载时移除事件监听 return () => { window.removeEventListener('keydown', handleKeyDown); }; }, [isOpen, currentIndex, images.length, goToNext, goToPrev, isLastImage, isFirstImage]); + // 灯箱未打开或没有图片时不渲染 if (!isOpen || images.length === 0) { return null; } @@ -159,37 +217,46 @@ const ImageLightbox: React.FC = ({ return ( { if (!open) { onClose(); } }} > - onClose()}> + {/* 灯箱内容容器,全屏显示且居中 */} + onClose()} // 点击空白区域关闭 + > + {/* 当前显示的图片 */} {images[currentIndex].alt} e.stopPropagation()} + onClick={e => e.stopPropagation()} // 点击图片本身不关闭灯箱 /> + {/* 多张图片时显示导航按钮 */} {images.length > 1 && ( <> + {/* 上一张按钮 */} + {/* 下一张按钮 */} )} + + {/* 关闭按钮 */}