mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 10:53:02 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import type { FC } from 'react'
 | |
| import {
 | |
|   Fragment,
 | |
|   memo,
 | |
|   useCallback,
 | |
|   useState,
 | |
| } from 'react'
 | |
| import cn from 'classnames'
 | |
| import { useKeyPress } from 'ahooks'
 | |
| import { useTranslation } from 'react-i18next'
 | |
| import {
 | |
|   useReactFlow,
 | |
|   useViewport,
 | |
| } from 'reactflow'
 | |
| import {
 | |
|   useNodesSyncDraft,
 | |
|   useWorkflowReadOnly,
 | |
| } from '../hooks'
 | |
| import {
 | |
|   getKeyboardKeyCodeBySystem,
 | |
|   getKeyboardKeyNameBySystem,
 | |
|   isEventTargetInputArea,
 | |
| } from '../utils'
 | |
| import ShortcutsName from '../shortcuts-name'
 | |
| import TipPopup from './tip-popup'
 | |
| import {
 | |
|   PortalToFollowElem,
 | |
|   PortalToFollowElemContent,
 | |
|   PortalToFollowElemTrigger,
 | |
| } from '@/app/components/base/portal-to-follow-elem'
 | |
| import {
 | |
|   ZoomIn,
 | |
|   ZoomOut,
 | |
| } from '@/app/components/base/icons/src/vender/line/editor'
 | |
| 
 | |
| enum ZoomType {
 | |
|   zoomIn = 'zoomIn',
 | |
|   zoomOut = 'zoomOut',
 | |
|   zoomToFit = 'zoomToFit',
 | |
|   zoomTo25 = 'zoomTo25',
 | |
|   zoomTo50 = 'zoomTo50',
 | |
|   zoomTo75 = 'zoomTo75',
 | |
|   zoomTo100 = 'zoomTo100',
 | |
|   zoomTo200 = 'zoomTo200',
 | |
| }
 | |
| 
 | |
| const ZoomInOut: FC = () => {
 | |
|   const { t } = useTranslation()
 | |
|   const {
 | |
|     zoomIn,
 | |
|     zoomOut,
 | |
|     zoomTo,
 | |
|     fitView,
 | |
|   } = useReactFlow()
 | |
|   const { zoom } = useViewport()
 | |
|   const { handleSyncWorkflowDraft } = useNodesSyncDraft()
 | |
|   const [open, setOpen] = useState(false)
 | |
|   const {
 | |
|     workflowReadOnly,
 | |
|     getWorkflowReadOnly,
 | |
|   } = useWorkflowReadOnly()
 | |
| 
 | |
|   const ZOOM_IN_OUT_OPTIONS = [
 | |
|     [
 | |
|       {
 | |
|         key: ZoomType.zoomTo200,
 | |
|         text: '200%',
 | |
|       },
 | |
|       {
 | |
|         key: ZoomType.zoomTo100,
 | |
|         text: '100%',
 | |
|       },
 | |
|       {
 | |
|         key: ZoomType.zoomTo75,
 | |
|         text: '75%',
 | |
|       },
 | |
|       {
 | |
|         key: ZoomType.zoomTo50,
 | |
|         text: '50%',
 | |
|       },
 | |
|       {
 | |
|         key: ZoomType.zoomTo25,
 | |
|         text: '25%',
 | |
|       },
 | |
|     ],
 | |
|     [
 | |
|       {
 | |
|         key: ZoomType.zoomToFit,
 | |
|         text: t('workflow.operator.zoomToFit'),
 | |
|       },
 | |
|     ],
 | |
|   ]
 | |
| 
 | |
|   const handleZoom = (type: string) => {
 | |
|     if (workflowReadOnly)
 | |
|       return
 | |
| 
 | |
|     if (type === ZoomType.zoomToFit)
 | |
|       fitView()
 | |
| 
 | |
|     if (type === ZoomType.zoomTo25)
 | |
|       zoomTo(0.25)
 | |
| 
 | |
|     if (type === ZoomType.zoomTo50)
 | |
|       zoomTo(0.5)
 | |
| 
 | |
|     if (type === ZoomType.zoomTo75)
 | |
|       zoomTo(0.75)
 | |
| 
 | |
|     if (type === ZoomType.zoomTo100)
 | |
|       zoomTo(1)
 | |
| 
 | |
|     if (type === ZoomType.zoomTo200)
 | |
|       zoomTo(2)
 | |
| 
 | |
|     handleSyncWorkflowDraft()
 | |
|   }
 | |
| 
 | |
|   useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.1`, (e) => {
 | |
|     e.preventDefault()
 | |
|     if (workflowReadOnly)
 | |
|       return
 | |
| 
 | |
|     fitView()
 | |
|     handleSyncWorkflowDraft()
 | |
|   }, {
 | |
|     exactMatch: true,
 | |
|     useCapture: true,
 | |
|   })
 | |
| 
 | |
|   useKeyPress('shift.1', (e) => {
 | |
|     if (workflowReadOnly)
 | |
|       return
 | |
| 
 | |
|     if (isEventTargetInputArea(e.target as HTMLElement))
 | |
|       return
 | |
| 
 | |
|     e.preventDefault()
 | |
|     zoomTo(1)
 | |
|     handleSyncWorkflowDraft()
 | |
|   }, {
 | |
|     exactMatch: true,
 | |
|     useCapture: true,
 | |
|   })
 | |
| 
 | |
|   useKeyPress('shift.2', (e) => {
 | |
|     if (workflowReadOnly)
 | |
|       return
 | |
| 
 | |
|     if (isEventTargetInputArea(e.target as HTMLElement))
 | |
|       return
 | |
| 
 | |
|     e.preventDefault()
 | |
|     zoomTo(2)
 | |
|     handleSyncWorkflowDraft()
 | |
|   }, {
 | |
|     exactMatch: true,
 | |
|     useCapture: true,
 | |
|   })
 | |
| 
 | |
|   useKeyPress('shift.5', (e) => {
 | |
|     if (workflowReadOnly)
 | |
|       return
 | |
| 
 | |
|     if (isEventTargetInputArea(e.target as HTMLElement))
 | |
|       return
 | |
| 
 | |
|     e.preventDefault()
 | |
|     zoomTo(0.5)
 | |
|     handleSyncWorkflowDraft()
 | |
|   }, {
 | |
|     exactMatch: true,
 | |
|     useCapture: true,
 | |
|   })
 | |
| 
 | |
|   useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.dash`, (e) => {
 | |
|     e.preventDefault()
 | |
|     if (workflowReadOnly)
 | |
|       return
 | |
| 
 | |
|     zoomOut()
 | |
|     handleSyncWorkflowDraft()
 | |
|   }, {
 | |
|     exactMatch: true,
 | |
|     useCapture: true,
 | |
|   })
 | |
| 
 | |
|   useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.equalsign`, (e) => {
 | |
|     e.preventDefault()
 | |
|     if (workflowReadOnly)
 | |
|       return
 | |
| 
 | |
|     zoomIn()
 | |
|     handleSyncWorkflowDraft()
 | |
|   }, {
 | |
|     exactMatch: true,
 | |
|     useCapture: true,
 | |
|   })
 | |
| 
 | |
|   const handleTrigger = useCallback(() => {
 | |
|     if (getWorkflowReadOnly())
 | |
|       return
 | |
| 
 | |
|     setOpen(v => !v)
 | |
|   }, [getWorkflowReadOnly])
 | |
| 
 | |
|   return (
 | |
|     <PortalToFollowElem
 | |
|       placement='top-start'
 | |
|       open={open}
 | |
|       onOpenChange={setOpen}
 | |
|       offset={{
 | |
|         mainAxis: 4,
 | |
|         crossAxis: -2,
 | |
|       }}
 | |
|     >
 | |
|       <PortalToFollowElemTrigger asChild onClick={handleTrigger}>
 | |
|         <div className={`
 | |
|           p-0.5 h-9 cursor-pointer text-[13px] text-gray-500 font-medium rounded-lg bg-white shadow-lg border-[0.5px] border-gray-100
 | |
|           ${workflowReadOnly && '!cursor-not-allowed opacity-50'}
 | |
|         `}>
 | |
|           <div className={cn(
 | |
|             'flex items-center justify-between w-[98px] h-8 hover:bg-gray-50 rounded-lg',
 | |
|             open && 'bg-gray-50',
 | |
|           )}>
 | |
|             <TipPopup
 | |
|               title={t('workflow.operator.zoomOut')}
 | |
|               shortcuts={['ctrl', '-']}
 | |
|             >
 | |
|               <div
 | |
|                 className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5'
 | |
|                 onClick={(e) => {
 | |
|                   e.stopPropagation()
 | |
|                   zoomOut()
 | |
|                 }}
 | |
|               >
 | |
|                 <ZoomOut className='w-4 h-4' />
 | |
|               </div>
 | |
|             </TipPopup>
 | |
|             <div className='w-[34px]'>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div>
 | |
|             <TipPopup
 | |
|               title={t('workflow.operator.zoomIn')}
 | |
|               shortcuts={['ctrl', '+']}
 | |
|             >
 | |
|               <div
 | |
|                 className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5'
 | |
|                 onClick={(e) => {
 | |
|                   e.stopPropagation()
 | |
|                   zoomIn()
 | |
|                 }}
 | |
|               >
 | |
|                 <ZoomIn className='w-4 h-4' />
 | |
|               </div>
 | |
|             </TipPopup>
 | |
|           </div>
 | |
|         </div>
 | |
|       </PortalToFollowElemTrigger>
 | |
|       <PortalToFollowElemContent className='z-10'>
 | |
|         <div className='w-[145px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'>
 | |
|           {
 | |
|             ZOOM_IN_OUT_OPTIONS.map((options, i) => (
 | |
|               <Fragment key={i}>
 | |
|                 {
 | |
|                   i !== 0 && (
 | |
|                     <div className='h-[1px] bg-gray-100' />
 | |
|                   )
 | |
|                 }
 | |
|                 <div className='p-1'>
 | |
|                   {
 | |
|                     options.map(option => (
 | |
|                       <div
 | |
|                         key={option.key}
 | |
|                         className='flex items-center justify-between px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer text-sm text-gray-700'
 | |
|                         onClick={() => handleZoom(option.key)}
 | |
|                       >
 | |
|                         {option.text}
 | |
|                         {
 | |
|                           option.key === ZoomType.zoomToFit && (
 | |
|                             <ShortcutsName keys={[`${getKeyboardKeyNameBySystem('ctrl')}`, '1']} />
 | |
|                           )
 | |
|                         }
 | |
|                         {
 | |
|                           option.key === ZoomType.zoomTo50 && (
 | |
|                             <ShortcutsName keys={['shift', '5']} />
 | |
|                           )
 | |
|                         }
 | |
|                         {
 | |
|                           option.key === ZoomType.zoomTo100 && (
 | |
|                             <ShortcutsName keys={['shift', '1']} />
 | |
|                           )
 | |
|                         }
 | |
|                         {
 | |
|                           option.key === ZoomType.zoomTo200 && (
 | |
|                             <ShortcutsName keys={['shift', '2']} />
 | |
|                           )
 | |
|                         }
 | |
|                       </div>
 | |
|                     ))
 | |
|                   }
 | |
|                 </div>
 | |
|               </Fragment>
 | |
|             ))
 | |
|           }
 | |
|         </div>
 | |
|       </PortalToFollowElemContent>
 | |
|     </PortalToFollowElem>
 | |
|   )
 | |
| }
 | |
| 
 | |
| export default memo(ZoomInOut)
 | 
