-
+
+ {/* Drag overlay - only visible when dragging files over the folder */}
+ {isDragging && selectedFolder && (
+
+
+
+
Drop to Upload
+
+ Files will be added to {selectedFolder === "all" ? "your documents" : `folder "${selectedFolder}"`}
+
+
+
+ )}
+ {/* Hide the main header when viewing a specific folder - it will be merged with the FolderList header */}
+ {selectedFolder === null && (
+
-
{sectionTitle}
+
Folders
Manage your uploaded documents and view their metadata.
- {selectedDocuments.length > 0 && (
-
-
-
{
- console.log("Manual refresh triggered");
- // Show a loading indicator
- showAlert("Refreshing documents and folders...", {
- type: 'info',
- duration: 1500
- });
-
- // First clear folder data to force a clean refresh
- setLoading(true);
- setFolders([]);
-
- // Create a new function to perform a truly fresh fetch
- const performFreshFetch = async () => {
- try {
- // First get fresh folder data from the server
- const folderResponse = await fetch(`${effectiveApiUrl}/folders`, {
- method: 'GET',
- headers: {
- ...(authToken ? { 'Authorization': `Bearer ${authToken}` } : {})
- }
- });
-
- if (!folderResponse.ok) {
- throw new Error(`Failed to fetch folders: ${folderResponse.statusText}`);
- }
-
- // Get the fresh folder data
- const freshFolders = await folderResponse.json();
- console.log(`Refresh: Fetched ${freshFolders.length} folders with fresh data`);
-
- // Update folders state with fresh data
- setFolders(freshFolders);
-
- // Use our helper function to refresh documents with fresh folder data
- await refreshDocuments(freshFolders);
-
- // Show success message
- showAlert("Refresh completed successfully", {
- type: 'success',
- duration: 1500
- });
- } catch (error) {
- console.error("Error during refresh:", error);
- showAlert(`Error refreshing: ${error instanceof Error ? error.message : 'Unknown error'}`, {
- type: 'error',
- duration: 3000
- });
- } finally {
- setLoading(false);
- }
- };
-
- // Execute the fresh fetch
- performFreshFetch();
- }}
- disabled={loading}
- className="flex items-center"
- title="Refresh documents"
- >
-
- Refresh
-
-
-
-
+ )}
-
+ {/* Render the FolderList with header at all times when selectedFolder is not null */}
+ {selectedFolder !== null && (
= ({ apiBaseUrl, authTok
authToken={authToken}
refreshFolders={fetchFolders}
loading={foldersLoading}
+ refreshAction={handleRefresh}
+ selectedDocuments={selectedDocuments}
+ handleDeleteMultipleDocuments={handleDeleteMultipleDocuments}
+ uploadDialogComponent={
+
+ }
/>
-
- {selectedFolder !== null ? (
- documents.length === 0 && !loading ? (
-
-
-
-
No documents found in this folder. Upload a document.
-
+ )}
+
+ {documents.length === 0 && !loading && folders.length === 0 && !foldersLoading ? (
+
+
+
+
No documents found. Upload your first document.
+
+
+ ) : selectedFolder && documents.length === 0 && !loading ? (
+
+
+
+
Drag and drop files here to upload to this folder.
+
Or use the upload button in the top right.
+
+
+ ) : selectedFolder && loading ? (
+
+
+
+
Loading documents...
+
+
+ ) : selectedFolder === null ? (
+
+
+ }
+ />
+
+ ) : (
+
+
+
+
+
+ {selectedDocument && (
+
+ setSelectedDocument(null)}
+ />
- ) : (
-
- )
- ) : null}
-
+ )}
+
+ )}
);
};
diff --git a/ui-component/components/documents/FolderList.tsx b/ui-component/components/documents/FolderList.tsx
index 48fa908..c7a1f32 100644
--- a/ui-component/components/documents/FolderList.tsx
+++ b/ui-component/components/documents/FolderList.tsx
@@ -2,14 +2,14 @@
import React from 'react';
import { Button } from '@/components/ui/button';
-import { PlusCircle, Folder as FolderIcon, File, ArrowLeft } from 'lucide-react';
+import { PlusCircle, ArrowLeft } from 'lucide-react';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
-import { cn } from '@/lib/utils';
-import { Card, CardContent } from '@/components/ui/card';
import { Folder } from '@/components/types';
+import { useRouter, usePathname } from 'next/navigation';
+import Image from 'next/image';
interface FolderListProps {
folders: Folder[];
@@ -19,6 +19,12 @@ interface FolderListProps {
authToken: string | null;
refreshFolders: () => void;
loading: boolean;
+ refreshAction?: () => void;
+ selectedDocuments?: string[];
+ handleDeleteMultipleDocuments?: () => void;
+ showUploadDialog?: boolean;
+ setShowUploadDialog?: (show: boolean) => void;
+ uploadDialogComponent?: React.ReactNode;
}
const FolderList: React.FC
= ({
@@ -28,12 +34,30 @@ const FolderList: React.FC = ({
apiBaseUrl,
authToken,
refreshFolders,
- loading
+ loading,
+ refreshAction,
+ selectedDocuments = [],
+ handleDeleteMultipleDocuments,
+ uploadDialogComponent
}) => {
+ const router = useRouter();
+ const pathname = usePathname();
const [showNewFolderDialog, setShowNewFolderDialog] = React.useState(false);
const [newFolderName, setNewFolderName] = React.useState('');
const [newFolderDescription, setNewFolderDescription] = React.useState('');
const [isCreatingFolder, setIsCreatingFolder] = React.useState(false);
+
+ // Function to update both state and URL
+ const updateSelectedFolder = (folderName: string | null) => {
+ setSelectedFolder(folderName);
+
+ // Update URL to reflect the selected folder
+ if (folderName) {
+ router.push(`${pathname}?folder=${encodeURIComponent(folderName)}`);
+ } else {
+ router.push(pathname);
+ }
+ };
const handleCreateFolder = async () => {
if (!newFolderName.trim()) return;
@@ -73,7 +97,7 @@ const FolderList: React.FC = ({
// Auto-select this newly created folder so user can immediately add files to it
// This ensures we start with a clean empty folder view
- setSelectedFolder(folderData.name);
+ updateSelectedFolder(folderData.name);
} catch (error) {
console.error('Error creating folder:', error);
@@ -86,28 +110,61 @@ const FolderList: React.FC = ({
if (selectedFolder !== null) {
return (
-
-
setSelectedFolder(null)}
- >
-
-
-
- {selectedFolder === "all" ? (
- <>
-
- All Documents
- >
- ) : (
- <>
-
- {selectedFolder}
- >
+
+
+
updateSelectedFolder(null)}
+ >
+
+
+
+ {selectedFolder === "all" ? (
+ 📄
+ ) : (
+
+ )}
+
+ {selectedFolder === "all" ? "All Documents" : selectedFolder}
+
+
+
+ {/* Show delete button if documents are selected */}
+ {selectedDocuments.length > 0 && handleDeleteMultipleDocuments && (
+
+ Delete {selectedDocuments.length} selected
+
)}
-
+
+
+ {/* Action buttons */}
+
+ {refreshAction && (
+
+
+ Refresh
+
+ )}
+
+ {/* Upload dialog component */}
+ {uploadDialogComponent}
+
);
@@ -165,46 +222,44 @@ const FolderList: React.FC
= ({
-
-
setSelectedFolder("all")}
+
+
updateSelectedFolder("all")}
>
-
-
- All Documents
-
-
+
+ 📄
+
+
All Documents
+
{folders.map((folder) => (
-
setSelectedFolder(folder.name)}
+ className="cursor-pointer group flex flex-col items-center"
+ onClick={() => updateSelectedFolder(folder.name)}
>
-
-
- {folder.name}
-
-
+
+
+
+
{folder.name}
+
))}
{folders.length === 0 && !loading && (
-
- No folders yet. Create one to organize your documents.
+
+
+
No folders yet. Create one to organize your documents.
)}
{loading && folders.length === 0 && (
-
- Loading folders...
+
)}
diff --git a/ui-component/components/types.ts b/ui-component/components/types.ts
index 0886483..89c8b33 100644
--- a/ui-component/components/types.ts
+++ b/ui-component/components/types.ts
@@ -7,6 +7,7 @@ export interface MorphikUIProps {
onUriChange?: (uri: string) => void; // Callback when URI is changed
onBackClick?: () => void; // Callback when back button is clicked
appName?: string; // Name of the app to display in UI
+ initialFolder?: string | null; // Initial folder to show
}
export interface Document {
diff --git a/ui-component/components/ui/checkbox.tsx b/ui-component/components/ui/checkbox.tsx
index c6fdd07..b16d41f 100644
--- a/ui-component/components/ui/checkbox.tsx
+++ b/ui-component/components/ui/checkbox.tsx
@@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
-
+
))
diff --git a/ui-component/components/ui/sidebar.tsx b/ui-component/components/ui/sidebar.tsx
index 5bae234..473ac5c 100644
--- a/ui-component/components/ui/sidebar.tsx
+++ b/ui-component/components/ui/sidebar.tsx
@@ -14,6 +14,8 @@ interface SidebarProps extends React.HTMLAttributes
{
connectionUri?: string
isReadOnlyUri?: boolean
onUriChange?: (uri: string) => void
+ isCollapsed?: boolean
+ setIsCollapsed?: (collapsed: boolean) => void
}
export function Sidebar({
@@ -23,12 +25,26 @@ export function Sidebar({
connectionUri,
isReadOnlyUri = false,
onUriChange,
+ isCollapsed: externalIsCollapsed,
+ setIsCollapsed: externalSetIsCollapsed,
...props
}: SidebarProps) {
- const [isCollapsed, setIsCollapsed] = React.useState(false)
+ // Use internal state that syncs with external state if provided
+ const [internalIsCollapsed, setInternalIsCollapsed] = React.useState(false)
const [editableUri, setEditableUri] = React.useState('')
const [isEditingUri, setIsEditingUri] = React.useState(false)
+ // Determine if sidebar is collapsed based on props or internal state
+ const isCollapsed = externalIsCollapsed !== undefined ? externalIsCollapsed : internalIsCollapsed
+
+ // Toggle function that updates both internal and external state if provided
+ const toggleCollapsed = () => {
+ if (externalSetIsCollapsed) {
+ externalSetIsCollapsed(!isCollapsed)
+ }
+ setInternalIsCollapsed(!isCollapsed)
+ }
+
// Initialize from localStorage or props
React.useEffect(() => {
// For development/testing - check if we have a stored URI
@@ -110,7 +126,7 @@ export function Sidebar({
variant="ghost"
size="icon"
className="ml-auto"
- onClick={() => setIsCollapsed(!isCollapsed)}
+ onClick={toggleCollapsed}
>
{isCollapsed ? : }
diff --git a/ui-component/public/icons/folder-icon.png b/ui-component/public/icons/folder-icon.png
new file mode 100644
index 0000000..bdbb779
Binary files /dev/null and b/ui-component/public/icons/folder-icon.png differ