import React, { useContext, useEffect, useRef, useState } from 'react'; import { LinkOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons'; import type { Project } from '@stackblitz/sdk'; import stackblitzSdk from '@stackblitz/sdk'; import { Alert, Badge, Space, Tooltip } from 'antd'; import { createStyles, css } from 'antd-style'; import classNames from 'classnames'; import { FormattedMessage, useSiteData } from 'dumi'; import LZString from 'lz-string'; import type { AntdPreviewerProps } from '.'; import useLocation from '../../../hooks/useLocation'; import BrowserFrame from '../../common/BrowserFrame'; import ClientOnly from '../../common/ClientOnly'; import CodePenIcon from '../../common/CodePenIcon'; import CodePreview from '../../common/CodePreview'; import CodeSandboxIcon from '../../common/CodeSandboxIcon'; import EditButton from '../../common/EditButton'; import ExternalLinkIcon from '../../common/ExternalLinkIcon'; import RiddleIcon from '../../common/RiddleIcon'; import type { SiteContextProps } from '../../slots/SiteContext'; import SiteContext from '../../slots/SiteContext'; import { ping } from '../../utils'; const { ErrorBoundary } = Alert; function compress(string: string): string { return LZString.compressToBase64(string) .replace(/\+/g, '-') // Convert '+' to '-' .replace(/\//g, '_') // Convert '/' to '_' .replace(/=+$/, ''); // Remove ending '=' } const track = ({ type, demo }: { type: string; demo: string }) => { if (!window.gtag) { return; } window.gtag('event', 'demo', { event_category: type, event_label: demo }); }; let pingDeferrer: PromiseLike; function useShowRiddleButton() { const [showRiddleButton, setShowRiddleButton] = useState(false); useEffect(() => { pingDeferrer ??= new Promise((resolve) => { ping((status) => { if (status !== 'timeout' && status !== 'error') { return resolve(true); } return resolve(false); }); }); pingDeferrer.then(setShowRiddleButton); }, []); return showRiddleButton; } const useStyle = createStyles(({ token }) => { const { borderRadius } = token; return { codeHideBtn: css` width: 100%; height: 40px; display: flex; justify-content: center; align-items: center; border-radius: 0 0 ${borderRadius}px ${borderRadius}px; border-top: 1px solid ${token.colorSplit}; color: ${token.colorTextSecondary}; transition: all 0.2s ease-in-out; background-color: ${token.colorBgElevated}; cursor: pointer; &:hover { color: ${token.colorPrimary}; } span { margin-right: ${token.marginXXS}px; } `, }; }); const CodePreviewer: React.FC = (props) => { const { asset, expand, iframe, demoUrl, children, title, description, originDebug, jsx, style, compact, background, filename, version, clientOnly, pkgDependencyList, } = props; const { pkg } = useSiteData(); const location = useLocation(); const { styles } = useStyle(); const entryCode = asset.dependencies['index.tsx'].value; const showRiddleButton = useShowRiddleButton(); const liveDemo = useRef(null); const anchorRef = useRef(null); const codeSandboxIconRef = useRef(null); const riddleIconRef = useRef(null); const codepenIconRef = useRef(null); const [codeExpand, setCodeExpand] = useState(false); const [codeType, setCodeType] = useState('tsx'); const { theme } = useContext(SiteContext); const { hash, pathname, search } = location; const docsOnlineUrl = `https://ant.design${pathname}${search}#${asset.id}`; const [showOnlineUrl, setShowOnlineUrl] = useState(false); useEffect(() => { const regexp = /preview-(\d+)-ant-design/; // matching PR preview addresses setShowOnlineUrl( process.env.NODE_ENV === 'development' || regexp.test(window.location.hostname), ); }, []); const handleCodeExpand = (demo: string) => { setCodeExpand((prev) => !prev); track({ type: 'expand', demo }); }; useEffect(() => { if (asset.id === hash.slice(1)) { anchorRef.current?.click(); } }, []); useEffect(() => { setCodeExpand(expand); }, [expand]); const mergedChildren = !iframe && clientOnly ? {children} : children; if (!liveDemo.current) { liveDemo.current = iframe ? (