Skip to content

Commit 3daed0f

Browse files
committed
Simplify upload view
1 parent e11f388 commit 3daed0f

File tree

2 files changed

+29
-225
lines changed

2 files changed

+29
-225
lines changed

packages/components/src/ObjectUpload/ObjectUpload.tsx

Lines changed: 6 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,9 @@
11
"use client";
22

3-
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
43
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
5-
import DeleteIcon from "@mui/icons-material/Delete";
6-
import ErrorIcon from "@mui/icons-material/Error";
7-
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
8-
import {
9-
Box,
10-
Button,
11-
Chip,
12-
IconButton,
13-
LinearProgress,
14-
List,
15-
ListItem,
16-
ListItemIcon,
17-
ListItemText,
18-
Paper,
19-
Typography,
20-
} from "@mui/material";
21-
import { formatBytes } from "@pelicanplatform/web-client";
4+
import { Box, Paper, Typography } from "@mui/material";
225
import { DragEvent, Ref, useCallback, useImperativeHandle, useRef, useState } from "react";
236

24-
interface UploadFile {
25-
file: File;
26-
status: "pending" | "uploading" | "success" | "error";
27-
progress: number;
28-
error?: string;
29-
}
30-
317
export interface ObjectUploadRef {
328
dragHandlers: {
339
onDragEnter: (e: DragEvent) => void;
@@ -40,8 +16,8 @@ export interface ObjectUploadRef {
4016
interface ObjectUploadProps {
4117
/** Whether upload functionality is enabled */
4218
disabled?: boolean;
43-
/** Callback when files should be uploaded */
44-
onUpload?: (files: File[]) => Promise<void>;
19+
/** Callback when files are dropped/selected */
20+
onUpload?: (files: File[]) => void;
4521
/** Current object path for context */
4622
currentPath?: string;
4723
/** Ref to expose drag handlers */
@@ -50,8 +26,6 @@ interface ObjectUploadProps {
5026

5127
const ObjectUpload = ({ disabled = false, onUpload, currentPath, refs }: ObjectUploadProps) => {
5228
const [isDragging, setIsDragging] = useState(false);
53-
const [files, setFiles] = useState<UploadFile[]>([]);
54-
const fileInputRef = useRef<HTMLInputElement>(null);
5529
const dragCounterRef = useRef(0);
5630

5731
const handleDragEnter = useCallback((e: DragEvent) => {
@@ -87,16 +61,11 @@ const ObjectUpload = ({ disabled = false, onUpload, currentPath, refs }: ObjectU
8761
if (disabled) return;
8862

8963
const droppedFiles = Array.from(e.dataTransfer.files);
90-
if (droppedFiles.length > 0) {
91-
const newFiles: UploadFile[] = droppedFiles.map((file) => ({
92-
file,
93-
status: "pending",
94-
progress: 0,
95-
}));
96-
setFiles((prev) => [...prev, ...newFiles]);
64+
if (droppedFiles.length > 0 && onUpload) {
65+
onUpload(droppedFiles);
9766
}
9867
},
99-
[disabled]
68+
[disabled, onUpload]
10069
);
10170

10271
// expose drag handlers via ref
@@ -113,97 +82,6 @@ const ObjectUpload = ({ disabled = false, onUpload, currentPath, refs }: ObjectU
11382
[handleDragEnter, handleDragOver, handleDragLeave, handleDrop]
11483
);
11584

116-
const handleFileSelect = useCallback(
117-
(e: React.ChangeEvent<HTMLInputElement>) => {
118-
if (disabled) return;
119-
const selectedFiles = e.target.files ? Array.from(e.target.files) : [];
120-
if (selectedFiles.length > 0) {
121-
const newFiles: UploadFile[] = selectedFiles.map((file) => ({
122-
file,
123-
status: "pending",
124-
progress: 0,
125-
}));
126-
setFiles((prev) => [...prev, ...newFiles]);
127-
}
128-
// reset input value to allow selecting the same file again
129-
if (fileInputRef.current) {
130-
fileInputRef.current.value = "";
131-
}
132-
},
133-
[disabled]
134-
);
135-
136-
const handleRemoveFile = useCallback((index: number) => {
137-
setFiles((prev) => prev.filter((_, i) => i !== index));
138-
}, []);
139-
140-
const handleUploadClick = useCallback(() => {
141-
if (fileInputRef.current) {
142-
fileInputRef.current.click();
143-
}
144-
}, []);
145-
146-
const handleStartUpload = useCallback(async () => {
147-
if (!onUpload) {
148-
console.warn("No upload handler provided");
149-
return;
150-
}
151-
152-
const pendingFiles = files.filter((f) => f.status === "pending");
153-
if (pendingFiles.length === 0) return;
154-
155-
// update all pending files to uploading
156-
setFiles((prev) =>
157-
prev.map((f) => (f.status === "pending" ? { ...f, status: "uploading" as const, progress: 0 } : f))
158-
);
159-
160-
// simulate upload progress (in real implementation, you'd track actual progress)
161-
const uploadPromises = pendingFiles.map(async (uploadFile) => {
162-
try {
163-
// Simulate progress updates
164-
const progressInterval = setInterval(() => {
165-
setFiles((prev) =>
166-
prev.map((f) =>
167-
f.file === uploadFile.file && f.status === "uploading"
168-
? { ...f, progress: Math.min(f.progress + 10, 90) }
169-
: f
170-
)
171-
);
172-
}, 200);
173-
174-
// Actual upload would happen here
175-
await onUpload([uploadFile.file]);
176-
177-
clearInterval(progressInterval);
178-
179-
// Mark as success
180-
setFiles((prev) =>
181-
prev.map((f) =>
182-
f.file === uploadFile.file ? { ...f, status: "success" as const, progress: 100 } : f
183-
)
184-
);
185-
} catch (error) {
186-
// mark as error
187-
setFiles((prev) =>
188-
prev.map((f) =>
189-
f.file === uploadFile.file
190-
? {
191-
...f,
192-
status: "error" as const,
193-
error: error instanceof Error ? error.message : "Upload failed",
194-
}
195-
: f
196-
)
197-
);
198-
}
199-
});
200-
201-
await Promise.all(uploadPromises);
202-
}, [files, onUpload]);
203-
204-
const hasPendingFiles = files.some((f) => f.status === "pending");
205-
const hasUploadingFiles = files.some((f) => f.status === "uploading");
206-
20785
return (
20886
<>
20987
{/* Drag overlay - renders at top level */}
@@ -240,89 +118,6 @@ const ObjectUpload = ({ disabled = false, onUpload, currentPath, refs }: ObjectU
240118
</Box>
241119
</Paper>
242120
)}
243-
244-
{/* Upload controls */}
245-
<Box sx={{ mb: 2 }}>
246-
<input
247-
ref={fileInputRef}
248-
type="file"
249-
multiple
250-
style={{ display: "none" }}
251-
onChange={handleFileSelect}
252-
disabled={disabled}
253-
/>
254-
<Box sx={{ display: "flex", gap: 2, alignItems: "center" }}>
255-
<Button
256-
variant="contained"
257-
startIcon={<CloudUploadIcon />}
258-
onClick={handleUploadClick}
259-
disabled={disabled}
260-
>
261-
Select Files
262-
</Button>
263-
{hasPendingFiles && (
264-
<Button
265-
variant="contained"
266-
color="success"
267-
onClick={handleStartUpload}
268-
disabled={disabled || hasUploadingFiles}
269-
>
270-
Upload {files.filter((f) => f.status === "pending").length} File(s)
271-
</Button>
272-
)}
273-
{disabled && <Chip label="Read-only" color="warning" size="small" />}
274-
</Box>
275-
</Box>
276-
277-
{/* File list */}
278-
{files.length > 0 && (
279-
<Paper variant="outlined" sx={{ maxHeight: 400, overflow: "auto" }}>
280-
<List>
281-
{files.map((uploadFile, index) => (
282-
<ListItem
283-
key={index}
284-
secondaryAction={
285-
uploadFile.status === "pending" || uploadFile.status === "error" ? (
286-
<IconButton edge="end" onClick={() => handleRemoveFile(index)}>
287-
<DeleteIcon />
288-
</IconButton>
289-
) : null
290-
}
291-
>
292-
<ListItemIcon>
293-
{uploadFile.status === "success" ? (
294-
<CheckCircleIcon color="success" />
295-
) : uploadFile.status === "error" ? (
296-
<ErrorIcon color="error" />
297-
) : (
298-
<InsertDriveFileIcon />
299-
)}
300-
</ListItemIcon>
301-
<ListItemText
302-
primary={uploadFile.file.name}
303-
secondary={
304-
<Box>
305-
<Typography variant="caption" component="span">
306-
{formatBytes(uploadFile.file.size)}
307-
</Typography>
308-
{uploadFile.status === "error" && uploadFile.error && (
309-
<Typography variant="caption" color="error" component="div">
310-
{uploadFile.error}
311-
</Typography>
312-
)}
313-
{uploadFile.status === "uploading" && (
314-
<Box sx={{ width: "100%", mt: 1 }}>
315-
<LinearProgress variant="determinate" value={uploadFile.progress} />
316-
</Box>
317-
)}
318-
</Box>
319-
}
320-
/>
321-
</ListItem>
322-
))}
323-
</List>
324-
</Paper>
325-
)}
326121
</>
327122
);
328123
};

website/src/app/page.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,25 @@
33
import { Box } from "@mui/material";
44

55
import { useSearchParams } from "next/navigation";
6-
import { useState } from "react";
6+
import { Suspense, useState } from "react";
77

88
import Client from "../../../packages/components/src/Client";
99

1010
function parseStateParam(state: string | null): Record<string, string> {
1111
if (!state) return {};
1212

13-
return state.split(";").reduce(
14-
(acc, pair) => {
15-
const colonIndex = pair.indexOf(":");
16-
if (colonIndex === -1) return acc;
17-
18-
const key = pair.substring(0, colonIndex);
19-
const value = pair.substring(colonIndex + 1);
20-
acc[key] = value;
21-
return acc;
22-
},
23-
{} as Record<string, string>,
24-
);
13+
return state.split(";").reduce((acc, pair) => {
14+
const colonIndex = pair.indexOf(":");
15+
if (colonIndex === -1) return acc;
16+
17+
const key = pair.substring(0, colonIndex);
18+
const value = pair.substring(colonIndex + 1);
19+
acc[key] = value;
20+
return acc;
21+
}, {} as Record<string, string>);
2522
}
2623

27-
function Page() {
24+
function PageClient() {
2825
const searchParams = useSearchParams();
2926
const state = searchParams.get("state");
3027

@@ -51,4 +48,16 @@ function Page() {
5148
);
5249
}
5350

51+
function Page() {
52+
return (
53+
<Box minHeight={"90vh"} margin={4} width={"1200px"} mx={"auto"}>
54+
{/* Use suspense because of useSearchParams not working within SSR/SSG */}
55+
{/* Although I wish I could just default it to blank instead of overwriting the entire component */}
56+
<Suspense fallback={<div>Loading...</div>}>
57+
<PageClient />
58+
</Suspense>
59+
</Box>
60+
);
61+
}
62+
5463
export default Page;

0 commit comments

Comments
 (0)