File Manager
This example demonstrates a comprehensive file management system with upload, download, and organization capabilities using the BuzzyFrameAPI.
Key Features
Universal file upload supporting all file types
Drag-and-drop interface with visual feedback
File metadata display including size, type, and filename
Download functionality with direct file access
File deletion with confirmation dialogs
Progress tracking during upload operations
Automatic URL refresh for expired S3 links
Responsive design with mobile-friendly layout
Use Cases
Ideal for applications requiring:
Document management systems
File sharing platforms
Project collaboration tools
Digital asset management
Client portals with file exchange
Administrative dashboards
Implementation Overview
This widget follows the Child Table Data pattern, storing files as child records linked to a parent row. Key implementation features:
Support for any file type (documents, images, videos, etc.)
Efficient file size formatting and display
S3 integration with presigned URLs for security
Real-time synchronization with backend data
Mobile-responsive design using Bulma CSS
Code Example
<!DOCTYPE html>
<html lang="en">
<head>
<title>File Manager</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"
crossorigin
></script>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"
crossorigin
></script>
<script
src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js"
crossorigin
></script>
<style>
.dropzone {
border: 2px dashed #ccc;
cursor: pointer;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #4caf50;
background-color: #f8fff8;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel" data-presets="env,react">
const buzzyFrameAPI = new BuzzyFrameAPI();
let appInitData = null;
let appFiles = [];
async function initApp() {
appInitData = await buzzyFrameAPI.initialise();
await fetchAndUpdateFiles();
buzzyFrameAPI.addMicroappListener({
microAppID: appInitData.rowJSON._id,
listener: async () => {
console.log('Child data updated, refreshing files');
await fetchAndUpdateFiles();
},
});
setInterval(() => {
refreshExpiredUrls();
}, 5 * 60 * 1000);
}
async function fetchAndUpdateFiles() {
try {
const { rowJSON = {} } = appInitData || {};
const children = await buzzyFrameAPI.getChildItemsByField({
appID: rowJSON._id,
fieldID: rowJSON.fieldID,
});
appFiles = children;
if (window.updateFiles) {
window.updateFiles(appFiles);
}
} catch (error) {
console.error('Error fetching files:', error);
if (window.updateFiles) {
window.updateFiles([]);
}
}
}
function App() {
const { useEffect, useState, useCallback } = React;
const [files, setFiles] = useState([]);
const [loading, setLoading] = useState(true);
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [dragActive, setDragActive] = useState(false);
useEffect(() => {
window.updateFiles = newFiles => {
setFiles(newFiles);
setLoading(false);
};
if (appFiles.length > 0) {
setFiles(appFiles);
setLoading(false);
}
return () => {
window.updateFiles = null;
};
}, []);
const refreshExpiredUrls = async () => {
const updatedFiles = await Promise.all(
appFiles.map(async file => {
if (
file.content.expiredAt &&
Date.now() > file.content.expiredAt
) {
try {
const { presignedReadUrl } =
await buzzyFrameAPI.createPresignedPutAndReadUrls(
appInitData.rowJSON._id,
file.content.filename,
);
await buzzyFrameAPI.updateMicroappChild({
childID: file._id,
content: {
...file.content,
url: presignedReadUrl,
expiredAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
},
});
return {
...file,
content: {
...file.content,
url: presignedReadUrl,
},
};
} catch (error) {
console.error('Error refreshing URL:', error);
return file;
}
}
return file;
}),
);
appFiles = updatedFiles;
if (window.updateFiles) {
window.updateFiles(appFiles);
}
};
const handleDrop = useCallback(async e => {
e.preventDefault();
setDragActive(false);
const files = e.dataTransfer?.files;
if (!files?.length) return;
setUploading(true);
setProgress(0);
try {
const { rowJSON } = appInitData;
for (let i = 0; i < files.length; i++) {
const file = files[i];
setProgress((i / files.length) * 100);
const { presignedPutUrl, presignedReadUrl } =
await buzzyFrameAPI.createPresignedPutAndReadUrls(
rowJSON._id,
file.name,
);
await buzzyFrameAPI.uploadFileToS3({
presignedPutUrl,
file,
onProgress: percent =>
setProgress(((i + percent / 100) / files.length) * 100),
});
await buzzyFrameAPI.createMicroappChild({
microAppResourceID: rowJSON.parentResourceID,
appID: rowJSON._id,
fieldID: rowJSON.fieldID,
content: {
url: presignedReadUrl,
filename: file.name,
size: file.size,
type: file.type,
expiredAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
},
});
}
} catch (error) {
console.error('Error uploading files:', error);
} finally {
setUploading(false);
setProgress(0);
}
}, []);
const handleDeleteFile = async childID => {
if (!confirm('Are you sure you want to delete this file?')) return;
try {
await buzzyFrameAPI.removeMicroappChild({ childID });
} catch (error) {
console.error('Error deleting file:', error);
}
};
const formatFileSize = bytes => {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return size.toFixed(1) + ' ' + units[unitIndex];
};
if (loading)
return <div className="has-text-centered p-4">Loading...</div>;
return (
<div className="container">
<div
className={`dropzone box has-text-centered p-5 mb-4 ${
dragActive ? 'active' : ''
}`}
onDragOver={e => {
e.preventDefault();
setDragActive(true);
}}
onDragLeave={() => setDragActive(false)}
onDrop={handleDrop}>
<p className="mb-3">
Drag and drop files here or click to browse
</p>
<input
type="file"
multiple
onChange={e =>
handleDrop({ dataTransfer: { files: e.target.files } })
}
style={{ display: 'none' }}
id="file-input"
/>
<button
className="button is-primary"
onClick={() => document.getElementById('file-input').click()}>
Browse Files
</button>
</div>
{uploading && (
<div className="mb-4">
<progress
className="progress is-primary"
value={progress}
max="100">
{progress}%
</progress>
</div>
)}
<div className="content">
{files.map(file => (
<div key={file._id} className="box">
<div className="columns is-mobile is-vcentered">
<div className="column">
<div>
<strong>{file.content.filename}</strong>
</div>
<div className="has-text-grey is-size-7">
{formatFileSize(file.content.size)} •{' '}
{file.content.type}
</div>
</div>
<div className="column is-narrow">
<div className="buttons has-addons">
<a
href={file.content.url}
target="_blank"
rel="noopener noreferrer"
className="button is-primary is-small">
<span className="icon is-small">
<i className="fas fa-download"></i>
</span>
<span>Download</span>
</a>
<button
className="button is-danger is-small"
onClick={() => handleDeleteFile(file._id)}>
<span className="icon is-small">
<i className="fas fa-trash"></i>
</span>
<span>Delete</span>
</button>
</div>
</div>
</div>
</div>
))}
</div>
</div>
);
}
initApp().then(() => {
ReactDOM.render(<App />, document.getElementById('root'));
});
</script>
</body>
</html>
Key Concepts
Universal File Support
All File Types: Supports documents, images, videos, archives, and any other file format
File Metadata: Displays filename, size, and MIME type for easy identification
Size Formatting: Human-readable file size display (B, KB, MB, GB)
File Operations
Upload: Drag-and-drop or click-to-browse file selection
Download: Direct file access through presigned URLs
Delete: Secure file removal with confirmation
Progress Tracking: Real-time upload progress feedback
S3 Integration
Presigned URLs: Secure file upload and access without exposing credentials
URL Expiration: Automatic refresh of expired URLs to maintain access
Storage Management: Efficient file storage with proper metadata tracking
User Experience
Responsive Design: Works seamlessly on desktop and mobile devices
Visual Feedback: Clear indicators for drag-and-drop states and upload progress
Error Handling: Graceful error handling with user-friendly messages
This file manager demonstrates how code widgets can provide enterprise-grade file management capabilities within Buzzy applications, suitable for any use case requiring file storage and organization.
Last updated