'use client'; import React, { useContext, useEffect, useState, type FC, type HTMLProps } from 'react'; import { HiChevronDoubleLeft, HiChevronDoubleRight, HiXMark } from 'react-icons/hi2'; import { PiArrowClockwise, PiArrowCounterClockwise, PiCopy, PiCornersIn, PiCornersOut, PiDownloadSimple, } from 'react-icons/pi'; import { TbTextRecognition } from 'react-icons/tb'; import { createReactContext, useAbortController } from '@wener/reaction'; import { copy, download, formatBytes, getGlobalThis, loadScripts } from '@wener/utils'; import clsx from 'clsx'; import { createStore, useStore } from 'zustand'; import { mutative } from 'zustand-mutative'; import { useShallow } from 'zustand/react/shallow'; import { showErrorToast, showSuccessToast } from '../../toast'; interface ImagePreviewState { detail: boolean; enlarge: boolean; rotate: number; src?: string; text?: string; imgRef?: HTMLImageElement; info: ImageInfo; } interface ImageInfo { width?: number; height?: number; size?: number; sha256?: string; mimeType?: string; format?: string; } function createImagePreviewStore(initial?: Partial) { const state: ImagePreviewState = { detail: false, enlarge: false, rotate: 0, info: {}, ...initial, }; return createStore(mutative(() => state)); } export interface ImagePreviewProps { src?: string; info?: ImageInfo; onOpenChange?: (show: boolean) => void; } const Context = createReactContext('ImagePreviewStoreContext', createImagePreviewStore()); export function useImagePreviewStore() { return useContext(Context); } export function useImagePreviewContext() { let store = useContext(Context); return { store }; } const DetailButton = () => { let { store } = useImagePreviewContext(); const showDetail = useStore(store, (s) => s.detail); const setShowDetail = (v: boolean) => { store.setState({ detail: v }); }; return ( { setShowDetail(!showDetail); }} > {showDetail && } {!showDetail && } ); }; export const ImagePreview: FC = ({ onOpenChange, info, src }) => { const [store] = useState(() => createImagePreviewStore({ src, info: info, detail: true })); useEffect(() => { store.setState((s) => { s.src = src; s.info = { ...s.info, ...info, }; }); }, [src, info]); return (
onOpenChange?.(false)}>
<_Image />
); }; const _Image = () => { const { store } = useImagePreviewContext(); const { rotate, enlarge, src } = useStore( store, useShallow(({ rotate, enlarge, src }) => ({ rotate, enlarge, src })), ); if (!src) { return
无图片
; } return ( {'preview { if (imgRef) { if (store.getState().imgRef !== imgRef) { store.setState({ imgRef }); } } }} style={{ transform: `rotate(${rotate}deg) scale(${enlarge ? 2 : 1})`, }} onLoad={(e) => { let w = e.currentTarget.naturalWidth; let h = e.currentTarget.naturalHeight; store.setState((s) => { s.info.width = w; s.info.height = h; }); }} /> ); }; const Actions = () => { let { store } = useImagePreviewContext(); let { enlarge, src } = useStore( store, useShallow(({ enlarge, src }) => ({ enlarge, src })), ); return (
{ store.setState({ enlarge: !enlarge }); }} > {!enlarge && } {enlarge && } { store.setState((s) => { s.rotate = (s.rotate + 90) % 360; }); }} > { store.setState((s) => { s.rotate = (s.rotate + 270) % 360; }); }} > {src && ( { download('img.jpg', src); }} > )} {/* {}}> */}
); }; const DetailPanel = () => { let store = useContext(Context); const showDetail = useStore(store, (s) => s.detail); const { width, height, size, mimeType, format } = useStore(store, (s) => s.info || {}); if (!showDetail) { return; } return (

图片信息


div>span:first-child]:inline-block', '[&>div>span:first-child]:w-[5ch]', '[&>div>span:last-child]:font-semibold', )} >
尺寸 {width}x{height}
{size && (
大小 {formatBytes(size, true)}
)} {format && (
格式 {format}
)} {mimeType && (
MIME {mimeType}
)}

<_TextRecognitionButton />

<_ExtraInfo />
); }; const _ExtraInfo = () => { const { store } = useImagePreviewContext(); const text = useStore(store, (s) => s.text); return (
{text && (
识别结果