| 
                        123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 | 
                        - import type { FC } from 'react'
 - import {
 -   memo,
 -   useCallback,
 -   useState,
 - } from 'react'
 - import { useTranslation } from 'react-i18next'
 - import { toJpeg, toPng, toSvg } from 'html-to-image'
 - import { useNodesReadOnly } from '../hooks'
 - import TipPopup from './tip-popup'
 - import { RiExportLine } from '@remixicon/react'
 - import cn from '@/utils/classnames'
 - import { useStore as useAppStore } from '@/app/components/app/store'
 - import {
 -   PortalToFollowElem,
 -   PortalToFollowElemContent,
 -   PortalToFollowElemTrigger,
 - } from '@/app/components/base/portal-to-follow-elem'
 - 
 - const ExportImage: FC = () => {
 -   const { t } = useTranslation()
 -   const { getNodesReadOnly } = useNodesReadOnly()
 - 
 -   const appDetail = useAppStore(s => s.appDetail)
 -   const [open, setOpen] = useState(false)
 - 
 -   const handleExportImage = useCallback(async (type: 'png' | 'jpeg' | 'svg') => {
 -     if (!appDetail)
 -       return
 - 
 -     if (getNodesReadOnly())
 -       return
 - 
 -     setOpen(false)
 -     const flowElement = document.querySelector('.react-flow__viewport') as HTMLElement
 -     if (!flowElement) return
 - 
 -     try {
 -       const filter = (node: HTMLElement) => {
 -         if (node instanceof HTMLImageElement)
 -           return node.complete && node.naturalHeight !== 0
 - 
 -         return true
 -       }
 - 
 -       let dataUrl
 -       switch (type) {
 -         case 'png':
 -           dataUrl = await toPng(flowElement, { filter })
 -           break
 -         case 'jpeg':
 -           dataUrl = await toJpeg(flowElement, { filter })
 -           break
 -         case 'svg':
 -           dataUrl = await toSvg(flowElement, { filter })
 -           break
 -         default:
 -           dataUrl = await toPng(flowElement, { filter })
 -       }
 - 
 -       const link = document.createElement('a')
 -       link.href = dataUrl
 -       link.download = `${appDetail.name}.${type}`
 -       document.body.appendChild(link)
 -       link.click()
 -       document.body.removeChild(link)
 -     }
 -     catch (error) {
 -       console.error('Export image failed:', error)
 -     }
 -   }, [getNodesReadOnly, appDetail])
 - 
 -   const handleTrigger = useCallback(() => {
 -     if (getNodesReadOnly())
 -       return
 - 
 -     setOpen(v => !v)
 -   }, [getNodesReadOnly])
 - 
 -   return (
 -     <PortalToFollowElem
 -       open={open}
 -       onOpenChange={setOpen}
 -       placement="top-start"
 -       offset={{
 -         mainAxis: 4,
 -         crossAxis: -8,
 -       }}
 -     >
 -       <PortalToFollowElemTrigger>
 -         <TipPopup title={t('workflow.common.exportImage')}>
 -           <div
 -             className={cn(
 -               'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover hover:text-text-secondary',
 -               `${getNodesReadOnly() && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
 -             )}
 -             onClick={handleTrigger}
 -           >
 -             <RiExportLine className='h-4 w-4' />
 -           </div>
 -         </TipPopup>
 -       </PortalToFollowElemTrigger>
 -       <PortalToFollowElemContent className='z-10'>
 -         <div className='min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg'>
 -           <div className='p-1'>
 -             <div
 -               className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'
 -               onClick={() => handleExportImage('png')}
 -             >
 -               {t('workflow.common.exportPNG')}
 -             </div>
 -             <div
 -               className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'
 -               onClick={() => handleExportImage('jpeg')}
 -             >
 -               {t('workflow.common.exportJPEG')}
 -             </div>
 -             <div
 -               className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'
 -               onClick={() => handleExportImage('svg')}
 -             >
 -               {t('workflow.common.exportSVG')}
 -             </div>
 -           </div>
 -         </div>
 -       </PortalToFollowElemContent>
 -     </PortalToFollowElem>
 -   )
 - }
 - 
 - export default memo(ExportImage)
 
 
  |