Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/apps/ui/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {Outlet} from "react-router-dom"

import Header from "@/components/Header/Header"
import NavBar from "@/components/NavBar"
import Uploader from "@/components/Uploader"
import {
selectCurrentUserError,
selectCurrentUserStatus
} from "@/slices/currentUser"

import Uploader from "@/components/Uploader"
import "./App.css"
import {useUILanguage, useUITheme} from "./hooks"

Expand Down
2 changes: 2 additions & 0 deletions frontend/apps/ui/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import classes from "./Header.module.css"

import {ClearNotificationsButton} from "@/features/notifications/components/ClearButton"
import Search from "@/features/search/components/Search"
import UploadButton from "../UploadButton"
import SidebarToggle from "./SidebarToggle"
import UserMenu from "./UserMenu"

Expand All @@ -25,6 +26,7 @@ function Header() {
<Group>
<SidebarToggle />
<img src={logoURL} width={"30px"} />
<UploadButton />
<ClearNotificationsButton />
</Group>
<Group
Expand Down
167 changes: 167 additions & 0 deletions frontend/apps/ui/src/components/UploadButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {useAppDispatch, useAppSelector} from "@/app/hooks"
import {apiSlice} from "@/features/api/slice"
import {uploadFile} from "@/features/files/filesSlice"
import {generateThumbnail} from "@/features/nodes/thumbnailObjectsSlice"
import type {UploadFileOutput} from "@/features/nodes/types"
import {Button, Menu} from "@mantine/core"
import {useDisclosure} from "@mantine/hooks"
import {IconUpload} from "@tabler/icons-react"

import {
SUPPORTED_EXTENSIONS,
SUPPORTED_MIME_TYPES
} from "@/features/nodes/constants"
import {isSupportedFile} from "@/features/nodes/utils"
import {selectMyPreferences} from "@/features/preferences/storage/preference"
import {selectCurrentNodeID} from "@/features/ui/uiSlice"
import useUploadDestinationFolder from "@/hooks/useUploadDestinationFolder"
import {selectCurrentUser} from "@/slices/currentUser"
import {IconChevronDown, IconFolder} from "@tabler/icons-react"
import {useRef, useState} from "react"
import {useTranslation} from "react-i18next"
import SupportedFilesInfoModal from "../features/nodes/components/Commander/NodesCommander/SupportedFilesInfoModal"

const MIME_TYPES = [...SUPPORTED_EXTENSIONS, ...SUPPORTED_MIME_TYPES].join(",")

export default function UploadButton() {
const {t} = useTranslation()
const dispatch = useAppDispatch()
const [selectedDestinationID, setSelectedDestinationID] = useState<string>()
const fileInputRef = useRef<HTMLInputElement>(null)
const [
supportedFilesInfoOpened,
{open: supportedFilesInfoOpen, close: supportedFilesInfoClose}
] = useDisclosure(false)

const folderID = useAppSelector(s => selectCurrentNodeID(s, "main"))
const currentUser = useAppSelector(selectCurrentUser)
const userPreferences = useAppSelector(selectMyPreferences)

let destinations = useUploadDestinationFolder()

if (
folderID &&
folderID != currentUser.home_folder_id &&
folderID != currentUser.inbox_folder_id
) {
destinations.push({
id: folderID,
label: t("common.current_location", {defaultValue: "Current Location"}),
icon: IconFolder
})
}

const handleFileChange = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
const files = event.target.files
console.log(
"File input changed, files:",
files,
"destination:",
selectedDestinationID
)

if (!files || files.length === 0 || !selectedDestinationID) {
console.error("No files selected or no destination")
return
}

const fileArray = Array.from(files)
const validFiles = fileArray.filter(isSupportedFile)

if (validFiles.length === 0) {
supportedFilesInfoOpen()
// Reset input
if (fileInputRef.current) {
fileInputRef.current.value = ""
}
return
}

for (let i = 0; i < validFiles.length; i++) {
const result = await dispatch(
uploadFile({
file: validFiles[i],
refreshTarget: true,
lang: userPreferences.uploaded_document_lang,
ocr: false,
target_id: selectedDestinationID
})
)
const newlyCreatedNode = result.payload as UploadFileOutput

if (newlyCreatedNode && newlyCreatedNode.source?.id) {
const newNodeID = newlyCreatedNode.source?.id
dispatch(generateThumbnail({node_id: newNodeID, file: validFiles[i]}))
}
}

dispatch(apiSlice.util.invalidateTags(["Node"]))

// Reset the file input and destination
if (fileInputRef.current) {
fileInputRef.current.value = ""
}
setSelectedDestinationID(undefined)
}

const handleMenuItemClick = (destinationID: string) => {
console.log("Menu item clicked, destination:", destinationID)
setSelectedDestinationID(destinationID)
// Trigger file input click
setTimeout(() => {
fileInputRef.current?.click()
}, 100)
}

const MenuItems = destinations.map(dest => {
const Icon = dest.icon
return (
<Menu.Item
key={dest.id}
leftSection={<Icon size={16} />}
onClick={() => handleMenuItemClick(dest.id)}
>
{dest.label}
</Menu.Item>
)
})

return (
<>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
accept={MIME_TYPES}
multiple
onChange={handleFileChange}
style={{display: "none"}}
/>

<Menu shadow="md" width={200}>
<Menu.Target>
<Button rightSection={<IconChevronDown size={16} />}>
<IconUpload size={16} style={{marginRight: 8}} />
{t("common.upload", {defaultValue: "Upload"})}
</Button>
</Menu.Target>

<Menu.Dropdown>
<Menu.Label>
{t("common.upload_to", {defaultValue: "Upload to"})}
</Menu.Label>
{MenuItems}
</Menu.Dropdown>
</Menu>

{supportedFilesInfoOpened && (
<SupportedFilesInfoModal
opened={supportedFilesInfoOpened}
onClose={supportedFilesInfoClose}
/>
)}
</>
)
}
4 changes: 2 additions & 2 deletions frontend/apps/ui/src/components/Uploader/uploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export default function Uploader() {
const files = useSelector(selectFiles)
const dispatch = useDispatch()

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

const onClose = () => {
Expand Down
23 changes: 12 additions & 11 deletions frontend/apps/ui/src/components/Uploader/uploaderItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {useAppSelector} from "@/app/hooks"
import PanelContext from "@/contexts/PanelContext"
import {FileItemType, PanelMode} from "@/types"
import {FileItemType} from "@/types"
import {
Box,
Group,
Expand All @@ -11,25 +9,24 @@ import {
Tooltip,
rem
} from "@mantine/core"
import {IconCircleCheck, IconFolder, IconX} from "@tabler/icons-react"
import {useContext} from "react"
import {useNavigate} from "react-router-dom"
import {IconCircleCheck, IconX} from "@tabler/icons-react"
//import {useNavigate} from "react-router-dom"

import {selectLastPageSize} from "@/features/ui/uiSlice"
//import {selectLastPageSize} from "@/features/ui/uiSlice"
import classes from "./uploaderItem.module.css"

type Args = {
fileItem: FileItemType
}

export default function UploaderItem({fileItem}: Args) {
const mode: PanelMode = useContext(PanelContext)
const navigate = useNavigate()
const lastPageSize = useAppSelector(s => selectLastPageSize(s, mode))
//const mode: PanelMode = useContext(PanelContext)
//const navigate = useNavigate()
//const lastPageSize = useAppSelector(s => selectLastPageSize(s, mode))
let statusComponent

const onTargetClick = () => {
navigate(`/folder/${fileItem.target.id}?page_size=${lastPageSize}`)
//navigate(`/folder/${fileItem.target.id}?page_size=${lastPageSize}`)
}

const onFileClick = () => {
Expand Down Expand Up @@ -57,6 +54,7 @@ export default function UploaderItem({fileItem}: Args) {
<Tooltip label={fileItem.error}>
<List.Item className={classes.uploaderItem} icon={statusComponent}>
<Group justify="space-between">
{/*
<Group
justify="center"
gap="xs"
Expand All @@ -65,6 +63,7 @@ export default function UploaderItem({fileItem}: Args) {
>
<IconFolder /> {fileItem.target.title}
</Group>
*/}
<Box className={classes.uploaderItemFile}>{fileItem.file_name}</Box>
</Group>
</List.Item>
Expand All @@ -75,12 +74,14 @@ export default function UploaderItem({fileItem}: Args) {
return (
<List.Item className={classes.uploaderItem} icon={statusComponent}>
<Group justify="space-between">
{/*
<Group className={classes.uploaderItemTarget} onClick={onTargetClick}>
<IconFolder />
<Text w={150} truncate="end">
{fileItem.target.title}
</Text>
</Group>
*/}
<Box className={classes.uploaderItemFile} onClick={onFileClick}>
<Text w={150} truncate="end">
{fileItem.file_name}
Expand Down
Loading