| @@ -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> | |||