diff --git a/web/app/(commonLayout)/apps/NewAppCard.tsx b/web/app/(commonLayout)/apps/NewAppCard.tsx index 12d7e2636e..b1b9aeb3cf 100644 --- a/web/app/(commonLayout)/apps/NewAppCard.tsx +++ b/web/app/(commonLayout)/apps/NewAppCard.tsx @@ -3,6 +3,7 @@ import { forwardRef, useState } from 'react' import { useTranslation } from 'react-i18next' import NewAppDialog from './NewAppDialog' +import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal' import { useProviderContext } from '@/context/provider-context' import { Plus } from '@/app/components/base/icons/src/vender/line/general' import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' @@ -17,13 +18,13 @@ const CreateAppCard = forwardRef(({ onSuc const { onPlanInfoChanged } = useProviderContext() const [showNewAppDialog, setShowNewAppDialog] = useState(false) + const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false) return ( setShowNewAppDialog(true)} className='relative col-span-1 flex flex-col justify-between min-h-[160px] bg-gray-200 rounded-xl cursor-pointer duration-200 ease-in-out hover:bg-gray-50 hover:shadow-lg transition-colors' > -
+
setShowNewAppDialog(true)}>
@@ -33,10 +34,22 @@ const CreateAppCard = forwardRef(({ onSuc
-
+
setShowCreateFromDSLModal(true)} + > {t('app.createFromConfigFile')}
+ setShowCreateFromDSLModal(false)} + onSuccess={() => { + onPlanInfoChanged() + if (onSuccess) + onSuccess() + }} + /> { onPlanInfoChanged() diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx new file mode 100644 index 0000000000..9ac83f794a --- /dev/null +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -0,0 +1,198 @@ +'use client' + +import type { MouseEventHandler } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' +// import useSWR from 'swr' +import cn from 'classnames' +import { useRouter } from 'next/navigation' +import { useContext } from 'use-context-selector' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +// import Dialog from '@/app/components/base/dialog' +import Modal from '@/app/components/base/modal' +// import Input from '@/app/components/base/input' +// import type { AppMode } from '@/types/app' +import { ToastContext } from '@/app/components/base/toast' +// import { createApp, fetchAppTemplates } from '@/service/apps' +import { useAppContext } from '@/context/app-context' +import { useProviderContext } from '@/context/provider-context' +import AppsFull from '@/app/components/billing/apps-full-in-dialog' +import { Trash03, UploadCloud01, XClose } from '@/app/components/base/icons/src/vender/line/general' + +type CreateFromDSLModalProps = { + show: boolean + onSuccess?: () => void + onClose: () => void +} + +const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProps) => { + const router = useRouter() + const { t } = useTranslation() + const { notify } = useContext(ToastContext) + const [currentFile, setDSLFile] = useState() + const [dragging, setDragging] = useState(false) + const dropRef = useRef(null) + const dragRef = useRef(null) + const fileUploader = useRef(null) + + const handleDragEnter = (e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + e.target !== dragRef.current && setDragging(true) + } + const handleDragOver = (e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + } + const handleDragLeave = (e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + e.target === dragRef.current && setDragging(false) + } + + const handleDrop = useCallback((e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + setDragging(false) + if (!e.dataTransfer) + return + + const files = [...e.dataTransfer.files] as File[] + setDSLFile(files[0]) + }, [setDSLFile]) + + const selectHandle = () => { + if (fileUploader.current) + fileUploader.current.click() + } + + const fileChangeHandle = (e: React.ChangeEvent) => { + const files = [...(e.target.files ?? [])] as File[] + console.log(files[0]) + setDSLFile(files[0]) + } + + const removeFile = () => { + if (fileUploader.current) + fileUploader.current.value = '' + setDSLFile(null) + } + + // utils + // const getFileType = (currentFile: File) => { + // if (!currentFile) + // return '' + + // const arr = currentFile.name.split('.') + // return arr[arr.length - 1] + // } + + const getFileSize = (size: number) => { + if (size / 1024 < 10) + return `${(size / 1024).toFixed(2)}KB` + + return `${(size / 1024 / 1024).toFixed(2)}MB` + } + + useEffect(() => { + dropRef.current?.addEventListener('dragenter', handleDragEnter) + dropRef.current?.addEventListener('dragover', handleDragOver) + dropRef.current?.addEventListener('dragleave', handleDragLeave) + dropRef.current?.addEventListener('drop', handleDrop) + return () => { + dropRef.current?.removeEventListener('dragenter', handleDragEnter) + dropRef.current?.removeEventListener('dragover', handleDragOver) + dropRef.current?.removeEventListener('dragleave', handleDragLeave) + dropRef.current?.removeEventListener('drop', handleDrop) + } + }, [handleDrop]) + + const { isCurrentWorkspaceManager } = useAppContext() + const { plan, enableBilling } = useProviderContext() + const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) + + const isCreatingRef = useRef(false) + // TODO + const onCreate: MouseEventHandler = async () => { + if (isCreatingRef.current) + return + isCreatingRef.current = true + try { + // const app = await createApp() + if (onSuccess) + onSuccess() + if (onClose) + onClose() + notify({ type: 'success', message: t('app.newApp.appCreated') }) + // router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`) + } + catch (e) { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + } + isCreatingRef.current = false + } + + return ( + {}} + > +
Create from DSL file
+
+ +
+
+ +
+ {!currentFile && ( +
+ +
+ + + {t('datasetCreation.stepOne.uploader.button')} + + +
+
+ )} + {dragging &&
} +
+ {currentFile && ( +
+ {/* TODO type icon */} +
+
+
{currentFile.name}
+
{getFileSize(currentFile.size)}
+
+
+ { + e.stopPropagation() + removeFile() + }} /> +
+
+ )} +
+ {isAppsFull && } +
+ + +
+ + ) +} + +export default CreateFromDSLModal diff --git a/web/app/components/base/icons/assets/vender/line/general/upload-cloud-01.svg b/web/app/components/base/icons/assets/vender/line/general/upload-cloud-01.svg new file mode 100644 index 0000000000..c85fd5e35d --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/upload-cloud-01.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/app/components/base/icons/src/vender/line/general/UploadCloud01.json b/web/app/components/base/icons/src/vender/line/general/UploadCloud01.json new file mode 100644 index 0000000000..03e448d7ad --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/UploadCloud01.json @@ -0,0 +1,42 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "opacity": "0.4", + "d": "M4 16.2422C2.79401 15.435 2 14.0602 2 12.5C2 10.1564 3.79151 8.23129 6.07974 8.01937C6.54781 5.17213 9.02024 3 12 3C14.9798 3 17.4522 5.17213 17.9203 8.01937C20.2085 8.23129 22 10.1564 22 12.5C22 14.0602 21.206 15.435 20 16.2422", + "stroke": "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M8 16L12 12M12 12L16 16M12 12L12 21", + "stroke": "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + "name": "UploadCloud01" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/UploadCloud01.tsx b/web/app/components/base/icons/src/vender/line/general/UploadCloud01.tsx new file mode 100644 index 0000000000..1bfb145e40 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/UploadCloud01.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './UploadCloud01.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'UploadCloud01' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/general/index.ts b/web/app/components/base/icons/src/vender/line/general/index.ts index 0616292a11..f809461b6e 100644 --- a/web/app/components/base/icons/src/vender/line/general/index.ts +++ b/web/app/components/base/icons/src/vender/line/general/index.ts @@ -28,5 +28,6 @@ export { default as Settings04 } from './Settings04' export { default as Target04 } from './Target04' export { default as Trash03 } from './Trash03' export { default as Upload03 } from './Upload03' +export { default as UploadCloud01 } from './UploadCloud01' export { default as XClose } from './XClose' export { default as X } from './X'