mirror of https://github.com/langgenius/dify.git
feat: Implement sidebar toggle functionality with keyboard shortcuts and improve translations
This commit is contained in:
parent
b3431ab0c4
commit
1c85dada53
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { RiLayoutLeft2Line, RiLayoutRight2Line } from '@remixicon/react'
|
||||
import NavLink from './navLink'
|
||||
import type { NavIcon } from './navLink'
|
||||
import AppInfo from './app-info'
|
||||
|
|
@ -11,6 +10,10 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
|||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import cn from '@/utils/classnames'
|
||||
import Divider from '../base/divider'
|
||||
import { useHover, useKeyPress } from 'ahooks'
|
||||
import ToggleButton from './toggle-button'
|
||||
import { getKeyboardKeyCodeBySystem } from '../workflow/utils'
|
||||
|
||||
export type IAppDetailNavProps = {
|
||||
iconType?: 'app' | 'dataset' | 'notion'
|
||||
|
|
@ -33,15 +36,18 @@ const AppDetailNav = ({
|
|||
appSidebarExpand: state.appSidebarExpand,
|
||||
setAppSidebarExpand: state.setAppSidebarExpand,
|
||||
})))
|
||||
const sidebarRef = React.useRef<HTMLDivElement>(null)
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const expand = appSidebarExpand === 'expand'
|
||||
|
||||
const handleToggle = (state: string) => {
|
||||
setAppSidebarExpand(state === 'expand' ? 'collapse' : 'expand')
|
||||
}
|
||||
const handleToggle = useCallback(() => {
|
||||
setAppSidebarExpand(appSidebarExpand === 'expand' ? 'collapse' : 'expand')
|
||||
}, [appSidebarExpand, setAppSidebarExpand])
|
||||
|
||||
// // Check if the current path is a workflow canvas & fullscreen
|
||||
const isHoveringSidebar = useHover(sidebarRef)
|
||||
|
||||
// Check if the current path is a workflow canvas & fullscreen
|
||||
const pathname = usePathname()
|
||||
const inWorkflowCanvas = pathname.endsWith('/workflow')
|
||||
const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true'
|
||||
|
|
@ -60,16 +66,22 @@ const AppDetailNav = ({
|
|||
}
|
||||
}, [appSidebarExpand, setAppSidebarExpand])
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.b`, (e) => {
|
||||
e.preventDefault()
|
||||
handleToggle()
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
if (inWorkflowCanvas && hideHeader) {
|
||||
return (
|
||||
return (
|
||||
<div className='flex w-0 shrink-0'>
|
||||
<AppSidebarDropdown navigation={navigation} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={sidebarRef}
|
||||
className={`
|
||||
flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all
|
||||
${expand ? 'w-[216px]' : 'w-14'}
|
||||
|
|
@ -91,14 +103,30 @@ const AppDetailNav = ({
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='px-4'>
|
||||
<div className={cn('mx-auto mt-1 h-[1px] bg-divider-subtle', !expand && 'w-6')} />
|
||||
<div className='relative px-4 py-2'>
|
||||
<Divider
|
||||
type='horizontal'
|
||||
bgStyle={expand ? 'gradient' : 'solid'}
|
||||
className={cn(
|
||||
'my-0 h-px',
|
||||
expand
|
||||
? 'bg-gradient-to-r from-divider-subtle to-background-gradient-mask-transparent'
|
||||
: 'bg-divider-subtle',
|
||||
)}
|
||||
/>
|
||||
{!isMobile && isHoveringSidebar && (
|
||||
<ToggleButton
|
||||
className='absolute -right-3 top-[-3.5px] z-20'
|
||||
expand={expand}
|
||||
handleToggle={handleToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<nav
|
||||
className={`
|
||||
grow space-y-1
|
||||
${expand ? 'p-4' : 'px-2.5 py-4'}
|
||||
`}
|
||||
className={cn(
|
||||
'flex grow flex-col gap-y-0.5',
|
||||
expand ? 'px-3 py-2' : 'p-3',
|
||||
)}
|
||||
>
|
||||
{navigation.map((item, index) => {
|
||||
return (
|
||||
|
|
@ -113,27 +141,6 @@ const AppDetailNav = ({
|
|||
)
|
||||
})}
|
||||
</nav>
|
||||
{
|
||||
!isMobile && (
|
||||
<div
|
||||
className={`
|
||||
shrink-0 py-3
|
||||
${expand ? 'px-6' : 'px-4'}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
||||
onClick={() => handleToggle(appSidebarExpand)}
|
||||
>
|
||||
{
|
||||
expand
|
||||
? <RiLayoutRight2Line className='h-5 w-5 text-components-menu-item-text' />
|
||||
: <RiLayoutLeft2Line className='h-5 w-5 text-components-menu-item-text' />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,18 +48,18 @@ export default function NavLink({
|
|||
type='button'
|
||||
disabled
|
||||
className={classNames(
|
||||
'opacity-30 text-components-menu-item-text hover:bg-state-base-hover group flex items-center h-9 rounded-md py-2 system-sm-medium cursor-not-allowed',
|
||||
mode === 'expand' ? 'px-3' : 'px-2.5',
|
||||
'system-sm-medium flex h-8 cursor-not-allowed items-center rounded-lg text-components-menu-item-text opacity-30 hover:bg-state-base-hover',
|
||||
mode === 'expand' ? 'pl-3 pr-1' : 'px-1.5',
|
||||
)}
|
||||
title={mode === 'collapse' ? name : ''}
|
||||
aria-disabled
|
||||
>
|
||||
<NavIcon
|
||||
className={classNames(
|
||||
'h-4 w-4 flex-shrink-0',
|
||||
'h-4 w-4 shrink-0',
|
||||
mode === 'expand' ? 'mr-2' : 'mr-0',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
aria-hidden='true'
|
||||
/>
|
||||
{mode === 'expand' && name}
|
||||
</button>
|
||||
|
|
@ -71,18 +71,20 @@ export default function NavLink({
|
|||
key={name}
|
||||
href={href}
|
||||
className={classNames(
|
||||
isActive ? 'bg-state-accent-active text-text-accent font-semibold' : 'text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover',
|
||||
'group flex items-center h-9 rounded-md py-2 system-sm-medium',
|
||||
mode === 'expand' ? 'px-3' : 'px-2.5',
|
||||
isActive
|
||||
? 'system-sm-semibold border-b-[0.25px] border-l-[0.75px] border-r-[0.25px] border-t-[0.75px] border-effects-highlight-lightmode-off bg-state-accent-active text-text-accent-light-mode-only'
|
||||
: 'system-sm-medium text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover',
|
||||
'flex h-8 items-center rounded-lg',
|
||||
mode === 'expand' ? 'pl-3 pr-1' : 'px-1.5',
|
||||
)}
|
||||
title={mode === 'collapse' ? name : ''}
|
||||
>
|
||||
<NavIcon
|
||||
className={classNames(
|
||||
'h-4 w-4 flex-shrink-0',
|
||||
'h-4 w-4 shrink-0',
|
||||
mode === 'expand' ? 'mr-2' : 'mr-0',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
aria-hidden='true'
|
||||
/>
|
||||
{mode === 'expand' && name}
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react'
|
||||
import Button from '../base/button'
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Tooltip from '../base/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getKeyboardKeyNameBySystem } from '../workflow/utils'
|
||||
|
||||
type TooltipContentProps = {
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
const TOGGLE_SHORTCUT = ['ctrl', 'B']
|
||||
|
||||
const TooltipContent = ({
|
||||
expand,
|
||||
}: TooltipContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-1'>
|
||||
<span className='system-xs-medium px-0.5 text-text-secondary'>{expand ? t('layout.sidebar.collapseSidebar') : t('layout.sidebar.expandSidebar')}</span>
|
||||
<div className='flex items-center gap-x-0.5'>
|
||||
{
|
||||
TOGGLE_SHORTCUT.map(key => (
|
||||
<span
|
||||
key={key}
|
||||
className='system-kbd inline-flex items-center justify-center rounded-[4px] bg-components-kbd-bg-gray px-1 text-text-tertiary'
|
||||
>
|
||||
{getKeyboardKeyNameBySystem(key)}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type ToggleButtonProps = {
|
||||
expand: boolean
|
||||
handleToggle: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const ToggleButton = ({
|
||||
expand,
|
||||
handleToggle,
|
||||
className,
|
||||
}: ToggleButtonProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={<TooltipContent expand={expand} />}
|
||||
popupClassName='p-1.5 rounded-lg'
|
||||
position='right'
|
||||
>
|
||||
<Button
|
||||
size='small'
|
||||
onClick={handleToggle}
|
||||
className={cn('rounded-full px-1', className)}
|
||||
>
|
||||
{
|
||||
expand
|
||||
? <RiArrowLeftSLine className='size-4' />
|
||||
: <RiArrowRightSLine className='size-4' />
|
||||
}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ToggleButton)
|
||||
|
|
@ -19,7 +19,7 @@ import {
|
|||
RiVerifiedBadgeLine,
|
||||
} from '@remixicon/react'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import { getKeyboardKeyCodeBySystem } from '../../workflow/utils'
|
||||
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../../workflow/utils'
|
||||
import Toast from '../../base/toast'
|
||||
import type { ModelAndParameter } from '../configuration/debug/types'
|
||||
import Divider from '../../base/divider'
|
||||
|
|
@ -67,7 +67,7 @@ export type AppPublisherProps = {
|
|||
onRefreshData?: () => void
|
||||
}
|
||||
|
||||
const PUBLISH_SHORTCUT = ['⌘', '⇧', 'P']
|
||||
const PUBLISH_SHORTCUT = ['ctrl', '⇧', 'P']
|
||||
|
||||
const AppPublisher = ({
|
||||
disabled = false,
|
||||
|
|
@ -255,7 +255,7 @@ const AppPublisher = ({
|
|||
<div className='flex gap-0.5'>
|
||||
{PUBLISH_SHORTCUT.map(key => (
|
||||
<span key={key} className='system-kbd h-4 w-4 rounded-[4px] bg-components-kbd-bg-white text-text-primary-on-surface'>
|
||||
{key}
|
||||
{getKeyboardKeyNameBySystem(key)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
useFormatTimeFromNow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils'
|
||||
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
|
||||
import { usePublishWorkflow } from '@/service/use-workflow'
|
||||
import type { PublishWorkflowParams } from '@/types/workflow'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
|
|
@ -39,7 +39,7 @@ import Confirm from '@/app/components/base/confirm'
|
|||
import PublishAsKnowledgePipelineModal from '../../publish-as-knowledge-pipeline-modal'
|
||||
import type { IconInfo } from '@/models/datasets'
|
||||
|
||||
const PUBLISH_SHORTCUT = ['⌘', '⇧', 'P']
|
||||
const PUBLISH_SHORTCUT = ['ctrl', '⇧', 'P']
|
||||
|
||||
const Popup = () => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -191,7 +191,7 @@ const Popup = () => {
|
|||
<div className='flex gap-0.5'>
|
||||
{PUBLISH_SHORTCUT.map(key => (
|
||||
<span key={key} className='system-kbd h-4 w-4 rounded-[4px] bg-components-kbd-bg-white text-text-primary-on-surface'>
|
||||
{key}
|
||||
{getKeyboardKeyNameBySystem(key)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { useTranslation } from 'react-i18next'
|
|||
import { useKeyPress } from 'ahooks'
|
||||
import Button from '../../base/button'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import { getKeyboardKeyCodeBySystem } from '../utils'
|
||||
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../utils'
|
||||
|
||||
type VersionHistoryButtonProps = {
|
||||
onClick: () => Promise<unknown> | unknown
|
||||
}
|
||||
|
||||
const VERSION_HISTORY_SHORTCUT = ['⌘', '⇧', 'H']
|
||||
const VERSION_HISTORY_SHORTCUT = ['ctrl', '⇧', 'H']
|
||||
|
||||
const PopupContent = React.memo(() => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -25,7 +25,7 @@ const PopupContent = React.memo(() => {
|
|||
key={key}
|
||||
className='system-kbd rounded-[4px] bg-components-kbd-bg-white px-[1px] text-text-tertiary'
|
||||
>
|
||||
{key}
|
||||
{getKeyboardKeyNameBySystem(key)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -45,8 +45,7 @@ const VersionHistoryButton: FC<VersionHistoryButtonProps> = ({
|
|||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.h`, (e) => {
|
||||
e.preventDefault()
|
||||
handleViewVersionHistory()
|
||||
},
|
||||
{ exactMatch: true, useCapture: true })
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
return <Tooltip
|
||||
popupContent={<PopupContent />}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
const translation = {
|
||||
sidebar: {
|
||||
expandSidebar: 'Expand Sidebar',
|
||||
collapseSidebar: 'Collapse Sidebar',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
const translation = {
|
||||
sidebar: {
|
||||
expandSidebar: '展开侧边栏',
|
||||
collapseSidebar: '收起侧边栏',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
Loading…
Reference in New Issue