99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
"use client";
|
||
/**
|
||
* FullscreenImageViewer 全屏图片查看器
|
||
*
|
||
* 原始 Dart: lib/ui/chat/widgets/fullscreen_image_viewer.dart(76 行)
|
||
*
|
||
* 行为:
|
||
* - 全屏黑色背景 + 图片居中(contain)
|
||
* - 点击空白 / 下滑手势 → 关闭
|
||
* - 双指缩放(CSS `touch-action: pinch-zoom`)
|
||
*/
|
||
import { ChevronLeft } from "lucide-react";
|
||
import Image from "next/image";
|
||
import { useEffect } from "react";
|
||
|
||
import styles from "./fullscreen-image-viewer.module.css";
|
||
|
||
export interface FullscreenImageViewerProps {
|
||
imageUrl: string;
|
||
imagePaywalled?: boolean;
|
||
onUnlockImagePaywall?: () => void;
|
||
onClose: () => void;
|
||
}
|
||
|
||
export function FullscreenImageViewer({
|
||
imageUrl,
|
||
imagePaywalled = false,
|
||
onUnlockImagePaywall,
|
||
onClose,
|
||
}: FullscreenImageViewerProps) {
|
||
useEffect(() => {
|
||
const handleKey = (e: KeyboardEvent) => {
|
||
if (e.key === "Escape") onClose();
|
||
};
|
||
document.addEventListener("keydown", handleKey);
|
||
return () => document.removeEventListener("keydown", handleKey);
|
||
}, [onClose]);
|
||
|
||
const src = imageUrl.startsWith("data:") || imageUrl.startsWith("http")
|
||
? imageUrl
|
||
: `data:image/png;base64,${imageUrl}`;
|
||
|
||
if (imagePaywalled) {
|
||
return (
|
||
<div
|
||
className={`${styles.viewer} ${styles.paywallViewer}`}
|
||
role="dialog"
|
||
aria-label="Locked fullscreen image"
|
||
>
|
||
<Image
|
||
src={src}
|
||
alt=""
|
||
fill
|
||
sizes="100vw"
|
||
className={styles.paywallImage}
|
||
/>
|
||
<div className={styles.paywallScrim} aria-hidden="true" />
|
||
<button
|
||
type="button"
|
||
className={styles.backButton}
|
||
onClick={onClose}
|
||
aria-label="Back"
|
||
>
|
||
<ChevronLeft size={34} strokeWidth={2.5} aria-hidden="true" />
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={styles.unlockButton}
|
||
onClick={onUnlockImagePaywall}
|
||
>
|
||
Unlock high-definition large image
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className={styles.viewer}
|
||
onClick={onClose}
|
||
role="dialog"
|
||
aria-label="Fullscreen image"
|
||
>
|
||
<Image
|
||
src={src}
|
||
alt=""
|
||
width={800}
|
||
height={800}
|
||
className={styles.viewerImage}
|
||
onError={(e) => {
|
||
(e.currentTarget as HTMLImageElement).style.display = "none";
|
||
(e.currentTarget.parentElement as HTMLElement).innerHTML =
|
||
'<div class="error">🖼</div>';
|
||
}}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|