mirror of
https://github.com/langgenius/dify.git
synced 2026-05-11 06:37:13 +08:00
305 lines
9.6 KiB
TypeScript
305 lines
9.6 KiB
TypeScript
import type { FC } from 'react'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@langgenius/dify-ui/dropdown-menu'
|
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
|
import {
|
|
Fragment,
|
|
memo,
|
|
useState,
|
|
} from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import {
|
|
useReactFlow,
|
|
useViewport,
|
|
} from 'reactflow'
|
|
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
|
import {
|
|
useNodesSyncDraft,
|
|
useWorkflowReadOnly,
|
|
} from '../hooks'
|
|
import ShortcutsName from '../shortcuts-name'
|
|
import TipPopup from './tip-popup'
|
|
|
|
enum ZoomType {
|
|
zoomToFit = 'zoomToFit',
|
|
zoomTo25 = 'zoomTo25',
|
|
zoomTo50 = 'zoomTo50',
|
|
zoomTo75 = 'zoomTo75',
|
|
zoomTo100 = 'zoomTo100',
|
|
zoomTo200 = 'zoomTo200',
|
|
toggleUserComments = 'toggleUserComments',
|
|
toggleUserCursors = 'toggleUserCursors',
|
|
toggleMiniMap = 'toggleMiniMap',
|
|
}
|
|
|
|
type ZoomInOutProps = {
|
|
showMiniMap?: boolean
|
|
onToggleMiniMap?: () => void
|
|
showUserCursors?: boolean
|
|
onToggleUserCursors?: () => void
|
|
showUserComments?: boolean
|
|
onToggleUserComments?: () => void
|
|
isCommentMode?: boolean
|
|
}
|
|
|
|
const ZoomInOut: FC<ZoomInOutProps> = ({
|
|
showMiniMap = true,
|
|
onToggleMiniMap,
|
|
showUserCursors = true,
|
|
onToggleUserCursors,
|
|
showUserComments = true,
|
|
onToggleUserComments,
|
|
isCommentMode = false,
|
|
}) => {
|
|
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 { data: isCollaborationEnabled } = useSuspenseQuery({
|
|
...systemFeaturesQueryOptions(),
|
|
select: s => s.enable_collaboration_mode,
|
|
})
|
|
|
|
const zoomOptions = [
|
|
[
|
|
{
|
|
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('operator.zoomToFit', { ns: 'workflow' }),
|
|
},
|
|
],
|
|
isCollaborationEnabled
|
|
? [
|
|
{
|
|
key: ZoomType.toggleUserComments,
|
|
text: t('operator.showUserComments', { ns: 'workflow' }),
|
|
},
|
|
{
|
|
key: ZoomType.toggleUserCursors,
|
|
text: t('operator.showUserCursors', { ns: 'workflow' }),
|
|
},
|
|
{
|
|
key: ZoomType.toggleMiniMap,
|
|
text: t('operator.showMiniMap', { ns: 'workflow' }),
|
|
},
|
|
]
|
|
: [
|
|
{
|
|
key: ZoomType.toggleMiniMap,
|
|
text: t('operator.showMiniMap', { ns: 'workflow' }),
|
|
},
|
|
],
|
|
]
|
|
|
|
const handleZoom = (type: string) => {
|
|
if (workflowReadOnly)
|
|
return
|
|
|
|
setOpen(false)
|
|
|
|
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)
|
|
|
|
if (type === ZoomType.toggleUserComments) {
|
|
if (!isCommentMode)
|
|
onToggleUserComments?.()
|
|
|
|
return
|
|
}
|
|
|
|
if (type === ZoomType.toggleUserCursors) {
|
|
onToggleUserCursors?.()
|
|
return
|
|
}
|
|
|
|
if (type === ZoomType.toggleMiniMap) {
|
|
onToggleMiniMap?.()
|
|
return
|
|
}
|
|
|
|
handleSyncWorkflowDraft()
|
|
}
|
|
|
|
return (
|
|
<div className={`
|
|
h-9 cursor-pointer rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg
|
|
p-0.5 text-[13px] shadow-lg backdrop-blur-[5px]
|
|
hover:bg-state-base-hover
|
|
${workflowReadOnly && 'cursor-not-allowed! opacity-50'}
|
|
`}
|
|
>
|
|
<div className="flex h-8 w-[98px] items-center justify-between rounded-lg">
|
|
<TipPopup
|
|
title={t('operator.zoomOut', { ns: 'workflow' })}
|
|
shortcuts={['ctrl', '-']}
|
|
>
|
|
<div
|
|
className={`flex h-8 w-8 items-center justify-center rounded-lg ${zoom <= 0.25 ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-black/5'}`}
|
|
onClick={(e) => {
|
|
if (zoom <= 0.25)
|
|
return
|
|
|
|
e.stopPropagation()
|
|
zoomOut()
|
|
}}
|
|
>
|
|
<span aria-hidden className="i-ri-zoom-out-line h-4 w-4 text-text-tertiary hover:text-text-secondary" />
|
|
</div>
|
|
</TipPopup>
|
|
<DropdownMenu
|
|
open={open}
|
|
onOpenChange={setOpen}
|
|
>
|
|
<DropdownMenuTrigger
|
|
disabled={getWorkflowReadOnly()}
|
|
className={cn(
|
|
'flex h-8 w-[34px] items-center justify-center rounded-lg system-sm-medium text-text-tertiary hover:bg-black/5 hover:text-text-secondary',
|
|
open && 'bg-black/5 text-text-secondary',
|
|
)}
|
|
>
|
|
{Number.parseFloat(`${zoom * 100}`).toFixed(0)}
|
|
%
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
placement="top-start"
|
|
sideOffset={4}
|
|
alignOffset={-2}
|
|
popupClassName="border-0 bg-transparent p-0 shadow-none backdrop-blur-none"
|
|
>
|
|
<div className="w-[192px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]">
|
|
{zoomOptions.map((options, groupIndex) => (
|
|
<Fragment key={options[0]!.key}>
|
|
{groupIndex !== 0 && (
|
|
<DropdownMenuSeparator className="my-0" />
|
|
)}
|
|
<div className="p-1">
|
|
{options.map(option => (
|
|
<DropdownMenuItem
|
|
key={option.key}
|
|
className="justify-between px-3 py-1.5 system-md-regular text-text-secondary"
|
|
disabled={option.key === ZoomType.toggleUserComments && isCommentMode}
|
|
onClick={() => handleZoom(option.key)}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
{option.key === ZoomType.toggleUserComments && showUserComments && (
|
|
<span aria-hidden className="i-ri-check-line h-4 w-4 text-text-accent" />
|
|
)}
|
|
{option.key === ZoomType.toggleUserComments && !showUserComments && (
|
|
<span aria-hidden className="h-4 w-4" />
|
|
)}
|
|
{option.key === ZoomType.toggleUserCursors && showUserCursors && (
|
|
<span aria-hidden className="i-ri-check-line h-4 w-4 text-text-accent" />
|
|
)}
|
|
{option.key === ZoomType.toggleUserCursors && !showUserCursors && (
|
|
<span aria-hidden className="h-4 w-4" />
|
|
)}
|
|
{option.key === ZoomType.toggleMiniMap && showMiniMap && (
|
|
<span aria-hidden className="i-ri-check-line h-4 w-4 text-text-accent" />
|
|
)}
|
|
{option.key === ZoomType.toggleMiniMap && !showMiniMap && (
|
|
<span aria-hidden className="h-4 w-4" />
|
|
)}
|
|
{option.key === ZoomType.zoomToFit && (
|
|
<span aria-hidden className="i-ri-fullscreen-line h-4 w-4 text-text-tertiary" />
|
|
)}
|
|
{option.key !== ZoomType.toggleUserComments
|
|
&& option.key !== ZoomType.toggleUserCursors
|
|
&& option.key !== ZoomType.toggleMiniMap
|
|
&& option.key !== ZoomType.zoomToFit && (
|
|
<span aria-hidden className="h-4 w-4" />
|
|
)}
|
|
<span>{option.text}</span>
|
|
</div>
|
|
<div className="flex items-center space-x-0.5">
|
|
{option.key === ZoomType.zoomToFit && (
|
|
<ShortcutsName keys={['ctrl', '1']} />
|
|
)}
|
|
{option.key === ZoomType.zoomTo50 && (
|
|
<ShortcutsName keys={['shift', '5']} />
|
|
)}
|
|
{option.key === ZoomType.zoomTo100 && (
|
|
<ShortcutsName keys={['shift', '1']} />
|
|
)}
|
|
</div>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</div>
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
<TipPopup
|
|
title={t('operator.zoomIn', { ns: 'workflow' })}
|
|
shortcuts={['ctrl', '+']}
|
|
>
|
|
<div
|
|
className={`flex h-8 w-8 items-center justify-center rounded-lg ${zoom >= 2 ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-black/5'}`}
|
|
onClick={(e) => {
|
|
if (zoom >= 2)
|
|
return
|
|
|
|
e.stopPropagation()
|
|
zoomIn()
|
|
}}
|
|
>
|
|
<span aria-hidden className="i-ri-zoom-in-line h-4 w-4 text-text-tertiary hover:text-text-secondary" />
|
|
</div>
|
|
</TipPopup>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default memo(ZoomInOut)
|