mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-10 17:00:11 +01:00
implement search for document picker
This commit is contained in:
parent
c24b79c9d1
commit
fad1129b33
@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { X } from "@phosphor-icons/react";
|
||||||
|
import Document from "@/models/document";
|
||||||
|
|
||||||
|
export default function NewFolderModal({ closeModal, files, setFiles }) {
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [folderName, setFolderName] = useState("");
|
||||||
|
|
||||||
|
const handleCreate = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
if (folderName.trim() !== "") {
|
||||||
|
const newFolder = {
|
||||||
|
name: folderName,
|
||||||
|
type: "folder",
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
const { success } = await Document.createFolder(folderName);
|
||||||
|
if (success) {
|
||||||
|
setFiles({
|
||||||
|
...files,
|
||||||
|
items: [...files.items, newFolder],
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
} else {
|
||||||
|
setError("Failed to create folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full max-w-2xl max-h-full">
|
||||||
|
<div className="relative bg-main-gradient rounded-lg shadow">
|
||||||
|
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
|
||||||
|
<h3 className="text-xl font-semibold text-white">
|
||||||
|
Create New Folder
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
type="button"
|
||||||
|
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||||
|
data-modal-hide="staticModal"
|
||||||
|
>
|
||||||
|
<X className="text-gray-300 text-lg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleCreate}>
|
||||||
|
<div className="p-6 space-y-6 flex h-full w-full">
|
||||||
|
<div className="w-full flex flex-col gap-y-4">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="folderName"
|
||||||
|
className="block mb-2 text-sm font-medium text-white"
|
||||||
|
>
|
||||||
|
Folder Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="folderName"
|
||||||
|
type="text"
|
||||||
|
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||||
|
placeholder="Enter folder name"
|
||||||
|
required={true}
|
||||||
|
autoComplete="off"
|
||||||
|
value={folderName}
|
||||||
|
onChange={(e) => setFolderName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
type="button"
|
||||||
|
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
|
||||||
|
>
|
||||||
|
Create Folder
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -3,11 +3,15 @@ import PreLoader from "@/components/Preloader";
|
|||||||
import { memo, useEffect, useState } from "react";
|
import { memo, useEffect, useState } from "react";
|
||||||
import FolderRow from "./FolderRow";
|
import FolderRow from "./FolderRow";
|
||||||
import System from "@/models/system";
|
import System from "@/models/system";
|
||||||
import { Plus, Trash } from "@phosphor-icons/react";
|
import { MagnifyingGlass, Plus, Trash } from "@phosphor-icons/react";
|
||||||
import Document from "@/models/document";
|
import Document from "@/models/document";
|
||||||
import showToast from "@/utils/toast";
|
import showToast from "@/utils/toast";
|
||||||
import FolderSelectionPopup from "./FolderSelectionPopup";
|
import FolderSelectionPopup from "./FolderSelectionPopup";
|
||||||
import MoveToFolderIcon from "./MoveToFolderIcon";
|
import MoveToFolderIcon from "./MoveToFolderIcon";
|
||||||
|
import { useModal } from "@/hooks/useModal";
|
||||||
|
import ModalWrapper from "@/components/ModalWrapper";
|
||||||
|
import NewFolderModal from "./NewFolderModal";
|
||||||
|
import debounce from "lodash.debounce";
|
||||||
|
|
||||||
function Directory({
|
function Directory({
|
||||||
files,
|
files,
|
||||||
@ -24,9 +28,14 @@ function Directory({
|
|||||||
loadingMessage,
|
loadingMessage,
|
||||||
}) {
|
}) {
|
||||||
const [amountSelected, setAmountSelected] = useState(0);
|
const [amountSelected, setAmountSelected] = useState(0);
|
||||||
const [newFolderName, setNewFolderName] = useState("");
|
|
||||||
const [showNewFolderInput, setShowNewFolderInput] = useState(false);
|
|
||||||
const [showFolderSelection, setShowFolderSelection] = useState(false);
|
const [showFolderSelection, setShowFolderSelection] = useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
|
const {
|
||||||
|
isOpen: isFolderModalOpen,
|
||||||
|
openModal: openFolderModal,
|
||||||
|
closeModal: closeFolderModal,
|
||||||
|
} = useModal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAmountSelected(Object.keys(selectedItems).length);
|
setAmountSelected(Object.keys(selectedItems).length);
|
||||||
@ -121,32 +130,6 @@ function Directory({
|
|||||||
return !!selectedItems[id];
|
return !!selectedItems[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNewFolder = () => {
|
|
||||||
setShowNewFolderInput(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmNewFolder = async () => {
|
|
||||||
if (newFolderName.trim() !== "") {
|
|
||||||
const newFolder = {
|
|
||||||
name: newFolderName,
|
|
||||||
type: "folder",
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// If folder failed to create - silently fail.
|
|
||||||
const { success } = await Document.createFolder(newFolderName);
|
|
||||||
if (success) {
|
|
||||||
setFiles({
|
|
||||||
...files,
|
|
||||||
items: [...files.items, newFolder],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setNewFolderName("");
|
|
||||||
setShowNewFolderInput(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveToFolder = async (folder) => {
|
const moveToFolder = async (folder) => {
|
||||||
const toMove = [];
|
const toMove = [];
|
||||||
for (const itemId of Object.keys(selectedItems)) {
|
for (const itemId of Object.keys(selectedItems)) {
|
||||||
@ -183,40 +166,61 @@ function Directory({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterFiles = (item) => {
|
||||||
|
if (item.type === "folder") {
|
||||||
|
return (
|
||||||
|
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
item.items.some((file) =>
|
||||||
|
file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return item.name.toLowerCase().includes(searchTerm.toLowerCase());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedSearch = debounce((value) => {
|
||||||
|
setSearchTerm(value);
|
||||||
|
setIsSearching(false);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
const handleSearch = (e) => {
|
||||||
|
const searchValue = e.target.value;
|
||||||
|
setSearchTerm(searchValue);
|
||||||
|
setIsSearching(true);
|
||||||
|
debouncedSearch(searchValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredFiles =
|
||||||
|
files && files.items ? files.items.filter((item) => filterFiles(item)) : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-8 pb-8">
|
<div className="px-8 pb-8">
|
||||||
<div className="flex flex-col gap-y-6">
|
<div className="flex flex-col gap-y-6">
|
||||||
<div className="flex items-center justify-between w-[560px] px-5 relative">
|
<div className="flex items-center justify-between w-[560px] px-5 relative">
|
||||||
<h3 className="text-white text-base font-bold">My Documents</h3>
|
<h3 className="text-white text-base font-bold">My Documents</h3>
|
||||||
{showNewFolderInput ? (
|
<div className="relative">
|
||||||
<div className="flex items-center gap-x-2 z-50">
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Folder name"
|
placeholder="Search"
|
||||||
value={newFolderName}
|
onChange={handleSearch}
|
||||||
onChange={(e) => setNewFolderName(e.target.value)}
|
className="bg-zinc-900 text-white placeholder-white/80 text-sm rounded-lg pl-9 pr-2.5 py-2 w-[250px] h-[32px]"
|
||||||
className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-md p-2.5 w-[150px] h-[32px]"
|
/>
|
||||||
|
<MagnifyingGlass
|
||||||
|
size={14}
|
||||||
|
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white"
|
||||||
|
weight="bold"
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<button
|
|
||||||
onClick={confirmNewFolder}
|
|
||||||
className="text-sky-400 rounded-md text-sm font-bold hover:text-sky-500"
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-[#222628]/60"
|
className="flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-[#222628]/60"
|
||||||
onClick={createNewFolder}
|
onClick={openFolderModal}
|
||||||
>
|
>
|
||||||
<Plus size={18} weight="bold" color="#D3D4D4" />
|
<Plus size={18} weight="bold" color="#D3D4D4" />
|
||||||
<div className="text-[#D3D4D4] text-xs font-bold leading-[18px]">
|
<div className="text-[#D3D4D4] text-xs font-bold leading-[18px]">
|
||||||
New Folder
|
New Folder
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative w-[560px] h-[310px] bg-zinc-900 rounded-2xl overflow-hidden">
|
<div className="relative w-[560px] h-[310px] bg-zinc-900 rounded-2xl overflow-hidden">
|
||||||
@ -234,8 +238,12 @@ function Directory({
|
|||||||
{loadingMessage}
|
{loadingMessage}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : files.items ? (
|
) : isSearching && searchTerm !== "" ? (
|
||||||
files.items.map(
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<PreLoader />
|
||||||
|
</div>
|
||||||
|
) : filteredFiles.length > 0 ? (
|
||||||
|
filteredFiles.map(
|
||||||
(item, index) =>
|
(item, index) =>
|
||||||
item.type === "folder" && (
|
item.type === "folder" && (
|
||||||
<FolderRow
|
<FolderRow
|
||||||
@ -302,6 +310,7 @@ function Directory({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UploadFile
|
<UploadFile
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
fetchKeys={fetchKeys}
|
fetchKeys={fetchKeys}
|
||||||
@ -309,6 +318,14 @@ function Directory({
|
|||||||
setLoadingMessage={setLoadingMessage}
|
setLoadingMessage={setLoadingMessage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ModalWrapper isOpen={isFolderModalOpen}>
|
||||||
|
<NewFolderModal
|
||||||
|
closeModal={closeFolderModal}
|
||||||
|
files={files}
|
||||||
|
setFiles={setFiles}
|
||||||
|
/>
|
||||||
|
</ModalWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user