Skip to content

Commit 0654b3b

Browse files
authored
Refactor document upload (#1099)
1 parent 2635299 commit 0654b3b

File tree

17 files changed

+295
-142
lines changed

17 files changed

+295
-142
lines changed

frontend/apps/ui/src/app/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {Outlet} from "react-router-dom"
55

66
import Header from "@/components/Header/Header"
77
import NavBar from "@/components/NavBar"
8+
import Uploader from "@/components/Uploader"
89
import {
910
selectCurrentUserError,
1011
selectCurrentUserStatus
1112
} from "@/slices/currentUser"
1213

13-
import Uploader from "@/components/Uploader"
1414
import "./App.css"
1515
import {useUILanguage, useUITheme} from "./hooks"
1616

frontend/apps/ui/src/components/Header/Header.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import classes from "./Header.module.css"
66

77
import {ClearNotificationsButton} from "@/features/notifications/components/ClearButton"
88
import Search from "@/features/search/components/Search"
9+
import UploadButton from "../UploadButton"
910
import SidebarToggle from "./SidebarToggle"
1011
import UserMenu from "./UserMenu"
1112

@@ -25,6 +26,7 @@ function Header() {
2526
<Group>
2627
<SidebarToggle />
2728
<img src={logoURL} width={"30px"} />
29+
<UploadButton />
2830
<ClearNotificationsButton />
2931
</Group>
3032
<Group
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import {useAppDispatch, useAppSelector} from "@/app/hooks"
2+
import {apiSlice} from "@/features/api/slice"
3+
import {uploadFile} from "@/features/files/filesSlice"
4+
import {generateThumbnail} from "@/features/nodes/thumbnailObjectsSlice"
5+
import type {UploadFileOutput} from "@/features/nodes/types"
6+
import {Button, Menu} from "@mantine/core"
7+
import {useDisclosure} from "@mantine/hooks"
8+
import {IconUpload} from "@tabler/icons-react"
9+
10+
import {
11+
SUPPORTED_EXTENSIONS,
12+
SUPPORTED_MIME_TYPES
13+
} from "@/features/nodes/constants"
14+
import {isSupportedFile} from "@/features/nodes/utils"
15+
import {selectMyPreferences} from "@/features/preferences/storage/preference"
16+
import {selectCurrentNodeID} from "@/features/ui/uiSlice"
17+
import useUploadDestinationFolder from "@/hooks/useUploadDestinationFolder"
18+
import {selectCurrentUser} from "@/slices/currentUser"
19+
import {IconChevronDown, IconFolder} from "@tabler/icons-react"
20+
import {useRef, useState} from "react"
21+
import {useTranslation} from "react-i18next"
22+
import SupportedFilesInfoModal from "../features/nodes/components/Commander/NodesCommander/SupportedFilesInfoModal"
23+
24+
const MIME_TYPES = [...SUPPORTED_EXTENSIONS, ...SUPPORTED_MIME_TYPES].join(",")
25+
26+
export default function UploadButton() {
27+
const {t} = useTranslation()
28+
const dispatch = useAppDispatch()
29+
const [selectedDestinationID, setSelectedDestinationID] = useState<string>()
30+
const fileInputRef = useRef<HTMLInputElement>(null)
31+
const [
32+
supportedFilesInfoOpened,
33+
{open: supportedFilesInfoOpen, close: supportedFilesInfoClose}
34+
] = useDisclosure(false)
35+
36+
const folderID = useAppSelector(s => selectCurrentNodeID(s, "main"))
37+
const currentUser = useAppSelector(selectCurrentUser)
38+
const userPreferences = useAppSelector(selectMyPreferences)
39+
40+
let destinations = useUploadDestinationFolder()
41+
42+
if (
43+
folderID &&
44+
folderID != currentUser.home_folder_id &&
45+
folderID != currentUser.inbox_folder_id
46+
) {
47+
destinations.push({
48+
id: folderID,
49+
label: t("common.current_location", {defaultValue: "Current Location"}),
50+
icon: IconFolder
51+
})
52+
}
53+
54+
const handleFileChange = async (
55+
event: React.ChangeEvent<HTMLInputElement>
56+
) => {
57+
const files = event.target.files
58+
console.log(
59+
"File input changed, files:",
60+
files,
61+
"destination:",
62+
selectedDestinationID
63+
)
64+
65+
if (!files || files.length === 0 || !selectedDestinationID) {
66+
console.error("No files selected or no destination")
67+
return
68+
}
69+
70+
const fileArray = Array.from(files)
71+
const validFiles = fileArray.filter(isSupportedFile)
72+
73+
if (validFiles.length === 0) {
74+
supportedFilesInfoOpen()
75+
// Reset input
76+
if (fileInputRef.current) {
77+
fileInputRef.current.value = ""
78+
}
79+
return
80+
}
81+
82+
for (let i = 0; i < validFiles.length; i++) {
83+
const result = await dispatch(
84+
uploadFile({
85+
file: validFiles[i],
86+
refreshTarget: true,
87+
lang: userPreferences.uploaded_document_lang,
88+
ocr: false,
89+
target_id: selectedDestinationID
90+
})
91+
)
92+
const newlyCreatedNode = result.payload as UploadFileOutput
93+
94+
if (newlyCreatedNode && newlyCreatedNode.source?.id) {
95+
const newNodeID = newlyCreatedNode.source?.id
96+
dispatch(generateThumbnail({node_id: newNodeID, file: validFiles[i]}))
97+
}
98+
}
99+
100+
dispatch(apiSlice.util.invalidateTags(["Node"]))
101+
102+
// Reset the file input and destination
103+
if (fileInputRef.current) {
104+
fileInputRef.current.value = ""
105+
}
106+
setSelectedDestinationID(undefined)
107+
}
108+
109+
const handleMenuItemClick = (destinationID: string) => {
110+
console.log("Menu item clicked, destination:", destinationID)
111+
setSelectedDestinationID(destinationID)
112+
// Trigger file input click
113+
setTimeout(() => {
114+
fileInputRef.current?.click()
115+
}, 100)
116+
}
117+
118+
const MenuItems = destinations.map(dest => {
119+
const Icon = dest.icon
120+
return (
121+
<Menu.Item
122+
key={dest.id}
123+
leftSection={<Icon size={16} />}
124+
onClick={() => handleMenuItemClick(dest.id)}
125+
>
126+
{dest.label}
127+
</Menu.Item>
128+
)
129+
})
130+
131+
return (
132+
<>
133+
{/* Hidden file input */}
134+
<input
135+
ref={fileInputRef}
136+
type="file"
137+
accept={MIME_TYPES}
138+
multiple
139+
onChange={handleFileChange}
140+
style={{display: "none"}}
141+
/>
142+
143+
<Menu shadow="md" width={200}>
144+
<Menu.Target>
145+
<Button rightSection={<IconChevronDown size={16} />}>
146+
<IconUpload size={16} style={{marginRight: 8}} />
147+
{t("common.upload", {defaultValue: "Upload"})}
148+
</Button>
149+
</Menu.Target>
150+
151+
<Menu.Dropdown>
152+
<Menu.Label>
153+
{t("common.upload_to", {defaultValue: "Upload to"})}
154+
</Menu.Label>
155+
{MenuItems}
156+
</Menu.Dropdown>
157+
</Menu>
158+
159+
{supportedFilesInfoOpened && (
160+
<SupportedFilesInfoModal
161+
opened={supportedFilesInfoOpened}
162+
onClose={supportedFilesInfoClose}
163+
/>
164+
)}
165+
</>
166+
)
167+
}

frontend/apps/ui/src/components/Uploader/uploader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export default function Uploader() {
88
const files = useSelector(selectFiles)
99
const dispatch = useDispatch()
1010

11-
const fileItems = files.map(f => (
12-
<UploaderItem key={`${f.target.id}-${f.file_name}`} fileItem={f} />
11+
const fileItems = files.map((item, index) => (
12+
<UploaderItem key={index} fileItem={item} />
1313
))
1414

1515
const onClose = () => {

frontend/apps/ui/src/components/Uploader/uploaderItem.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import {useAppSelector} from "@/app/hooks"
2-
import PanelContext from "@/contexts/PanelContext"
3-
import {FileItemType, PanelMode} from "@/types"
1+
import {FileItemType} from "@/types"
42
import {
53
Box,
64
Group,
@@ -11,25 +9,24 @@ import {
119
Tooltip,
1210
rem
1311
} from "@mantine/core"
14-
import {IconCircleCheck, IconFolder, IconX} from "@tabler/icons-react"
15-
import {useContext} from "react"
16-
import {useNavigate} from "react-router-dom"
12+
import {IconCircleCheck, IconX} from "@tabler/icons-react"
13+
//import {useNavigate} from "react-router-dom"
1714

18-
import {selectLastPageSize} from "@/features/ui/uiSlice"
15+
//import {selectLastPageSize} from "@/features/ui/uiSlice"
1916
import classes from "./uploaderItem.module.css"
2017

2118
type Args = {
2219
fileItem: FileItemType
2320
}
2421

2522
export default function UploaderItem({fileItem}: Args) {
26-
const mode: PanelMode = useContext(PanelContext)
27-
const navigate = useNavigate()
28-
const lastPageSize = useAppSelector(s => selectLastPageSize(s, mode))
23+
//const mode: PanelMode = useContext(PanelContext)
24+
//const navigate = useNavigate()
25+
//const lastPageSize = useAppSelector(s => selectLastPageSize(s, mode))
2926
let statusComponent
3027

3128
const onTargetClick = () => {
32-
navigate(`/folder/${fileItem.target.id}?page_size=${lastPageSize}`)
29+
//navigate(`/folder/${fileItem.target.id}?page_size=${lastPageSize}`)
3330
}
3431

3532
const onFileClick = () => {
@@ -57,6 +54,7 @@ export default function UploaderItem({fileItem}: Args) {
5754
<Tooltip label={fileItem.error}>
5855
<List.Item className={classes.uploaderItem} icon={statusComponent}>
5956
<Group justify="space-between">
57+
{/*
6058
<Group
6159
justify="center"
6260
gap="xs"
@@ -65,6 +63,7 @@ export default function UploaderItem({fileItem}: Args) {
6563
>
6664
<IconFolder /> {fileItem.target.title}
6765
</Group>
66+
*/}
6867
<Box className={classes.uploaderItemFile}>{fileItem.file_name}</Box>
6968
</Group>
7069
</List.Item>
@@ -75,12 +74,14 @@ export default function UploaderItem({fileItem}: Args) {
7574
return (
7675
<List.Item className={classes.uploaderItem} icon={statusComponent}>
7776
<Group justify="space-between">
77+
{/*
7878
<Group className={classes.uploaderItemTarget} onClick={onTargetClick}>
7979
<IconFolder />
8080
<Text w={150} truncate="end">
8181
{fileItem.target.title}
8282
</Text>
8383
</Group>
84+
*/}
8485
<Box className={classes.uploaderItemFile} onClick={onFileClick}>
8586
<Text w={150} truncate="end">
8687
{fileItem.file_name}

0 commit comments

Comments
 (0)