-
-
-
+
+
Yellow mountain range.jpg
@@ -19,9 +22,14 @@ const FileInAttachmentItem = () => {
21.5 MB
-
-
-
+
)
}
diff --git a/web/app/components/base/file-uploader/file-uploader-in-attachment/index.tsx b/web/app/components/base/file-uploader/file-uploader-in-attachment/index.tsx
index 5715231fc1..f2274fa822 100644
--- a/web/app/components/base/file-uploader/file-uploader-in-attachment/index.tsx
+++ b/web/app/components/base/file-uploader/file-uploader-in-attachment/index.tsx
@@ -1,11 +1,21 @@
-import { memo } from 'react'
+import {
+ memo,
+ useCallback,
+} from 'react'
import {
RiLink,
RiUploadCloud2Line,
} from '@remixicon/react'
+import FileFromLinkOrLocal from '../file-from-link-or-local'
import FileInAttachmentItem from './file-in-attachment-item'
import Button from '@/app/components/base/button'
+import cn from '@/utils/classnames'
+type Option = {
+ value: string
+ label: string
+ icon: JSX.Element
+}
const FileUploaderInAttachment = () => {
const options = [
{
@@ -20,21 +30,39 @@ const FileUploaderInAttachment = () => {
},
]
+ const renderButton = useCallback((option: Option, open?: boolean) => {
+ return (
+
+ )
+ }, [])
+ const renderTrigger = useCallback((option: Option) => {
+ return (open: boolean) => renderButton(option, open)
+ }, [renderButton])
+ const renderOption = useCallback((option: Option) => {
+ if (option.value === 'local')
+ return renderButton(option)
+
+ if (option.value === 'link') {
+ return (
+
+ )
+ }
+ }, [renderButton, renderTrigger])
+
return (
- {
- options.map(option => (
-
- ))
- }
+ {options.map(renderOption)}
diff --git a/web/app/components/base/file-uploader/file-uploader-in-chat-input/index.tsx b/web/app/components/base/file-uploader/file-uploader-in-chat-input/index.tsx
index d645d2b68f..0380b60d87 100644
--- a/web/app/components/base/file-uploader/file-uploader-in-chat-input/index.tsx
+++ b/web/app/components/base/file-uploader/file-uploader-in-chat-input/index.tsx
@@ -1,50 +1,30 @@
import {
memo,
- useState,
+ useCallback,
} from 'react'
import {
RiAttachmentLine,
} from '@remixicon/react'
-import {
- PortalToFollowElem,
- PortalToFollowElemContent,
- PortalToFollowElemTrigger,
-} from '@/app/components/base/portal-to-follow-elem'
+import FileFromLinkOrLocal from '../file-from-link-or-local'
import ActionButton from '@/app/components/base/action-button'
-import Button from '@/app/components/base/button'
+import cn from '@/utils/classnames'
const FileUploaderInChatInput = () => {
- const [open, setOpen] = useState(false)
+ const renderTrigger = useCallback((open: boolean) => {
+ return (
+
+
+
+ )
+ }, [])
return (
-
- setOpen(v => !v)}>
-
-
-
-
-
-
-
-
+
)
}
diff --git a/web/app/components/base/file-uploader/hooks.ts b/web/app/components/base/file-uploader/hooks.ts
new file mode 100644
index 0000000000..5f96a0974a
--- /dev/null
+++ b/web/app/components/base/file-uploader/hooks.ts
@@ -0,0 +1,5 @@
+import { useFileStore } from './store'
+
+export const useFile = () => {
+ const fileStore = useFileStore()
+}
diff --git a/web/app/components/base/file-uploader/store.tsx b/web/app/components/base/file-uploader/store.tsx
new file mode 100644
index 0000000000..78ade49fd9
--- /dev/null
+++ b/web/app/components/base/file-uploader/store.tsx
@@ -0,0 +1,52 @@
+import {
+ createContext,
+ useContext,
+ useRef,
+} from 'react'
+import {
+ useStore as useZustandStore,
+} from 'zustand'
+import { createStore } from 'zustand/vanilla'
+
+type Shape = {
+ files: any[]
+ setFiles: (files: any[]) => void
+}
+
+export const createFileStore = () => {
+ return createStore
(set => ({
+ files: [],
+ setFiles: files => set({ files }),
+ }))
+}
+
+type FileStore = ReturnType
+export const FileContext = createContext(null)
+
+export function useStore(selector: (state: Shape) => T): T {
+ const store = useContext(FileContext)
+ if (!store)
+ throw new Error('Missing FileContext.Provider in the tree')
+
+ return useZustandStore(store, selector)
+}
+
+export const useFileStore = () => {
+ return useContext(FileContext)!
+}
+
+type FileProviderProps = {
+ children: React.ReactNode
+}
+export const FileContextProvider = ({ children }: FileProviderProps) => {
+ const storeRef = useRef()
+
+ if (!storeRef.current)
+ storeRef.current = createFileStore()
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/web/app/components/base/file-uploader/utils.ts b/web/app/components/base/file-uploader/utils.ts
new file mode 100644
index 0000000000..a82b7fd541
--- /dev/null
+++ b/web/app/components/base/file-uploader/utils.ts
@@ -0,0 +1,36 @@
+import { upload } from '@/service/base'
+
+type FileUploadParams = {
+ file: File
+ onProgressCallback: (progress: number) => void
+ onSuccessCallback: (res: { id: string }) => void
+ onErrorCallback: () => void
+}
+type FileUpload = (v: FileUploadParams, isPublic?: boolean, url?: string) => void
+export const imageUpload: FileUpload = ({
+ file,
+ onProgressCallback,
+ onSuccessCallback,
+ onErrorCallback,
+}, isPublic, url) => {
+ const formData = new FormData()
+ formData.append('file', file)
+ const onProgress = (e: ProgressEvent) => {
+ if (e.lengthComputable) {
+ const percent = Math.floor(e.loaded / e.total * 100)
+ onProgressCallback(percent)
+ }
+ }
+
+ upload({
+ xhr: new XMLHttpRequest(),
+ data: formData,
+ onprogress: onProgress,
+ }, isPublic, url)
+ .then((res: { id: string }) => {
+ onSuccessCallback(res)
+ })
+ .catch(() => {
+ onErrorCallback()
+ })
+}
diff --git a/web/app/components/base/progress-bar/progress-circle.tsx b/web/app/components/base/progress-bar/progress-circle.tsx
new file mode 100644
index 0000000000..d1812dba37
--- /dev/null
+++ b/web/app/components/base/progress-bar/progress-circle.tsx
@@ -0,0 +1,61 @@
+import { memo } from 'react'
+import cn from '@/utils/classnames'
+
+type ProgressCircleProps = {
+ percentage?: number
+ size?: number
+ circleStrokeWidth?: number
+ circleStrokeColor?: string
+ circleFillColor?: string
+ sectorFillColor?: string
+}
+
+const ProgressCircle: React.FC = ({
+ percentage = 0,
+ size = 12,
+ circleStrokeWidth = 1,
+ circleStrokeColor = 'components-progress-brand-border',
+ circleFillColor = 'components-progress-brand-bg',
+ sectorFillColor = 'components-progress-brand-progress',
+}) => {
+ const radius = size / 2
+ const center = size / 2
+ const angle = (percentage / 100) * 360
+ const radians = (angle * Math.PI) / 180
+ const x = center + radius * Math.cos(radians - Math.PI / 2)
+ const y = center + radius * Math.sin(radians - Math.PI / 2)
+ const largeArcFlag = percentage > 50 ? 1 : 0
+
+ const pathData = `
+ M ${center},${center}
+ L ${center},${center - radius}
+ A ${radius},${radius} 0 ${largeArcFlag} 1 ${x},${y}
+ Z
+ `
+
+ return (
+
+ )
+}
+
+export default memo(ProgressCircle)