123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- 'use client'
- import type { MouseEventHandler } from 'react'
- 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 Uploader from './uploader'
- import Button from '@/app/components/base/button'
- import Input from '@/app/components/base/input'
- import Modal from '@/app/components/base/modal'
- import { ToastContext } from '@/app/components/base/toast'
- import {
- importDSL,
- importDSLConfirm,
- } from '@/service/apps'
- import {
- DSLImportMode,
- DSLImportStatus,
- } from '@/models/app'
- import { useAppContext } from '@/context/app-context'
- import { useProviderContext } from '@/context/provider-context'
- import AppsFull from '@/app/components/billing/apps-full-in-dialog'
- import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
- import { getRedirection } from '@/utils/app-redirection'
- import cn from '@/utils/classnames'
- import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
- type CreateFromDSLModalProps = {
- show: boolean
- onSuccess?: () => void
- onClose: () => void
- activeTab?: string
- dslUrl?: string
- }
- export enum CreateFromDSLModalTab {
- FROM_FILE = 'from-file',
- FROM_URL = 'from-url',
- }
- const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDSLModalTab.FROM_FILE, dslUrl = '' }: CreateFromDSLModalProps) => {
- const { push } = useRouter()
- const { t } = useTranslation()
- const { notify } = useContext(ToastContext)
- const [currentFile, setDSLFile] = useState<File>()
- const [fileContent, setFileContent] = useState<string>()
- const [currentTab, setCurrentTab] = useState(activeTab)
- const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
- const [showErrorModal, setShowErrorModal] = useState(false)
- const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
- const [importId, setImportId] = useState<string>()
- const { handleCheckPluginDependencies } = usePluginDependencies()
- const readFile = (file: File) => {
- const reader = new FileReader()
- reader.onload = function (event) {
- const content = event.target?.result
- setFileContent(content as string)
- }
- reader.readAsText(file)
- }
- const handleFile = (file?: File) => {
- setDSLFile(file)
- if (file)
- readFile(file)
- if (!file)
- setFileContent('')
- }
- const { isCurrentWorkspaceEditor } = useAppContext()
- const { plan, enableBilling } = useProviderContext()
- const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
- const isCreatingRef = useRef(false)
- const onCreate: MouseEventHandler = async () => {
- if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
- return
- if (currentTab === CreateFromDSLModalTab.FROM_URL && !dslUrlValue)
- return
- if (isCreatingRef.current)
- return
- isCreatingRef.current = true
- try {
- let response
- if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
- response = await importDSL({
- mode: DSLImportMode.YAML_CONTENT,
- yaml_content: fileContent || '',
- })
- }
- if (currentTab === CreateFromDSLModalTab.FROM_URL) {
- response = await importDSL({
- mode: DSLImportMode.YAML_URL,
- yaml_url: dslUrlValue || '',
- })
- }
- if (!response)
- return
- const { id, status, app_id, app_mode, imported_dsl_version, current_dsl_version } = response
- if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
- if (onSuccess)
- onSuccess()
- if (onClose)
- onClose()
- notify({
- type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning',
- message: t(status === DSLImportStatus.COMPLETED ? 'app.newApp.appCreated' : 'app.newApp.caution'),
- children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'),
- })
- localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
- if (app_id)
- await handleCheckPluginDependencies(app_id)
- getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push)
- }
- else if (status === DSLImportStatus.PENDING) {
- setVersions({
- importedVersion: imported_dsl_version ?? '',
- systemVersion: current_dsl_version ?? '',
- })
- if (onClose)
- onClose()
- setTimeout(() => {
- setShowErrorModal(true)
- }, 300)
- setImportId(id)
- }
- else {
- notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
- }
- }
- // eslint-disable-next-line unused-imports/no-unused-vars
- catch (e) {
- notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
- }
- isCreatingRef.current = false
- }
- const onDSLConfirm: MouseEventHandler = async () => {
- try {
- if (!importId)
- return
- const response = await importDSLConfirm({
- import_id: importId,
- })
- const { status, app_id, app_mode } = response
- if (status === DSLImportStatus.COMPLETED) {
- if (onSuccess)
- onSuccess()
- if (onClose)
- onClose()
- notify({
- type: 'success',
- message: t('app.newApp.appCreated'),
- })
- if (app_id)
- await handleCheckPluginDependencies(app_id)
- localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
- getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push)
- }
- else if (status === DSLImportStatus.FAILED) {
- notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
- }
- }
- // eslint-disable-next-line unused-imports/no-unused-vars
- catch (e) {
- notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
- }
- }
- const tabs = [
- {
- key: CreateFromDSLModalTab.FROM_FILE,
- label: t('app.importFromDSLFile'),
- },
- {
- key: CreateFromDSLModalTab.FROM_URL,
- label: t('app.importFromDSLUrl'),
- },
- ]
- const buttonDisabled = useMemo(() => {
- if (isAppsFull)
- return true
- if (currentTab === CreateFromDSLModalTab.FROM_FILE)
- return !currentFile
- if (currentTab === CreateFromDSLModalTab.FROM_URL)
- return !dslUrlValue
- return false
- }, [isAppsFull, currentTab, currentFile, dslUrlValue])
- return (
- <>
- <Modal
- className='w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl'
- isShow={show}
- onClose={() => { }}
- >
- <div className='title-2xl-semi-bold flex items-center justify-between pb-3 pl-6 pr-5 pt-6 text-text-primary'>
- {t('app.importFromDSL')}
- <div
- className='flex h-8 w-8 cursor-pointer items-center'
- onClick={() => onClose()}
- >
- <RiCloseLine className='h-5 w-5 text-text-tertiary' />
- </div>
- </div>
- <div className='system-md-semibold flex h-9 items-center space-x-6 border-b border-divider-subtle px-6 text-text-tertiary'>
- {
- tabs.map(tab => (
- <div
- key={tab.key}
- className={cn(
- 'relative flex h-full cursor-pointer items-center',
- currentTab === tab.key && 'text-text-primary',
- )}
- onClick={() => setCurrentTab(tab.key)}
- >
- {tab.label}
- {
- currentTab === tab.key && (
- <div className='absolute bottom-0 h-[2px] w-full bg-util-colors-blue-brand-blue-brand-600'></div>
- )
- }
- </div>
- ))
- }
- </div>
- <div className='px-6 py-4'>
- {
- currentTab === CreateFromDSLModalTab.FROM_FILE && (
- <Uploader
- className='mt-0'
- file={currentFile}
- updateFile={handleFile}
- />
- )
- }
- {
- currentTab === CreateFromDSLModalTab.FROM_URL && (
- <div>
- <div className='system-md-semibold leading6 mb-1'>DSL URL</div>
- <Input
- placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
- value={dslUrlValue}
- onChange={e => setDslUrlValue(e.target.value)}
- />
- </div>
- )
- }
- </div>
- {isAppsFull && (
- <div className='px-6'>
- <AppsFull className='mt-0' loc='app-create-dsl' />
- </div>
- )}
- <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>
- </div>
- </Modal>
- <Modal
- isShow={showErrorModal}
- onClose={() => setShowErrorModal(false)}
- className='w-[480px]'
- >
- <div className='flex flex-col items-start gap-2 self-stretch pb-4'>
- <div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
- <div className='system-md-regular flex grow flex-col text-text-secondary'>
- <div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
- <div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
- <br />
- <div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div>
- <div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
- </div>
- </div>
- <div className='flex items-start justify-end gap-2 self-stretch pt-6'>
- <Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
- <Button variant='primary' destructive onClick={onDSLConfirm}>{t('app.newApp.Confirm')}</Button>
- </div>
- </Modal>
- </>
- )
- }
- export default CreateFromDSLModal
|