mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 20:26:02 +08:00
feat: add consistent keyboard shortcut support and visual indicators across all app creation dialogs (#17138)
This commit is contained in:
parent
88cb81d3d6
commit
d0d02be711
@ -1,4 +1,6 @@
|
||||
'use client'
|
||||
import { useCallback } from 'react'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import AppList from './app-list'
|
||||
import FullScreenModal from '@/app/components/base/fullscreen-modal'
|
||||
|
||||
@ -10,6 +12,13 @@ type CreateAppDialogProps = {
|
||||
}
|
||||
|
||||
const CreateAppTemplateDialog = ({ show, onSuccess, onClose, onCreateFromBlank }: CreateAppDialogProps) => {
|
||||
const handleEscKeyPress = useCallback(() => {
|
||||
if (show)
|
||||
onClose()
|
||||
}, [show, onClose])
|
||||
|
||||
useKeyPress('esc', handleEscKeyPress)
|
||||
|
||||
return (
|
||||
<FullScreenModal
|
||||
open={show}
|
||||
|
@ -5,7 +5,8 @@ import { useMemo, useRef, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react'
|
||||
import { useDebounceFn, useKeyPress } from 'ahooks'
|
||||
import Uploader from './uploader'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
@ -143,6 +144,18 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
||||
isCreatingRef.current = false
|
||||
}
|
||||
|
||||
const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 })
|
||||
|
||||
useKeyPress(['meta.enter', 'ctrl.enter'], () => {
|
||||
if (show && !isAppsFull && ((currentTab === CreateFromDSLModalTab.FROM_FILE && currentFile) || (currentTab === CreateFromDSLModalTab.FROM_URL && dslUrlValue)))
|
||||
handleCreateApp()
|
||||
})
|
||||
|
||||
useKeyPress('esc', () => {
|
||||
if (show && !showErrorModal)
|
||||
onClose()
|
||||
})
|
||||
|
||||
const onDSLConfirm: MouseEventHandler = async () => {
|
||||
try {
|
||||
if (!importId)
|
||||
@ -266,7 +279,18 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
||||
)}
|
||||
<div className='flex justify-end px-6 py-5'>
|
||||
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
<Button
|
||||
disabled={buttonDisabled}
|
||||
variant="primary"
|
||||
onClick={handleCreateApp}
|
||||
className="gap-1"
|
||||
>
|
||||
<span>{t('app.newApp.Create')}</span>
|
||||
<div className='flex gap-0.5'>
|
||||
<RiCommandLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
|
||||
<RiCornerDownLeftLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
|
@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react'
|
||||
import { useDebounceFn, useKeyPress } from 'ahooks'
|
||||
import AppIconPicker from '../../base/app-icon-picker'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
@ -66,7 +67,7 @@ const CreateAppModal = ({
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
|
||||
const submit = () => {
|
||||
const submit = useCallback(() => {
|
||||
if (!name.trim()) {
|
||||
Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') })
|
||||
return
|
||||
@ -80,7 +81,19 @@ const CreateAppModal = ({
|
||||
use_icon_as_answer_icon: useIconAsAnswerIcon,
|
||||
})
|
||||
onHide()
|
||||
}
|
||||
}, [name, appIcon, description, useIconAsAnswerIcon, onConfirm, onHide, t])
|
||||
|
||||
const { run: handleSubmit } = useDebounceFn(submit, { wait: 300 })
|
||||
|
||||
useKeyPress(['meta.enter', 'ctrl.enter'], () => {
|
||||
if (show && !(!isEditModal && isAppsFull) && name.trim())
|
||||
handleSubmit()
|
||||
})
|
||||
|
||||
useKeyPress('esc', () => {
|
||||
if (show)
|
||||
onHide()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -146,7 +159,18 @@ const CreateAppModal = ({
|
||||
{!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />}
|
||||
</div>
|
||||
<div className='flex flex-row-reverse'>
|
||||
<Button disabled={!isEditModal && isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button>
|
||||
<Button
|
||||
disabled={(!isEditModal && isAppsFull) || !name.trim()}
|
||||
className='ml-2 w-24 gap-1'
|
||||
variant='primary'
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<span>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</span>
|
||||
<div className='flex gap-0.5'>
|
||||
<RiCommandLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
|
||||
<RiCornerDownLeftLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
|
||||
</div>
|
||||
</Button>
|
||||
<Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
Loading…
x
Reference in New Issue
Block a user