Copy <!DOCTYPE html>
<html lang="en">
<head>
<title>Advanced Image Gallery</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;
}
.image-actions {
position: absolute;
top: 10px;
right: 10px;
opacity: 0;
transition: opacity 0.2s;
}
.image-card:hover .image-actions {
opacity: 1;
}
.image-card img {
width: 100%;
height: 150px;
object-fit: cover;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel" data-presets="env,react">
const buzzyFrameAPI = new BuzzyFrameAPI();
let appInitData = null;
let appImages = [];
async function initApp() {
appInitData = await buzzyFrameAPI.initialise();
await fetchAndUpdateImages();
buzzyFrameAPI.addMicroappListener({
microAppID: appInitData.rowJSON._id,
listener: async () => {
console.log('Child data updated, refreshing images');
await fetchAndUpdateImages();
},
});
setInterval(() => {
refreshExpiredUrls();
}, 5 * 60 * 1000);
}
async function fetchAndUpdateImages() {
try {
const { rowJSON = {} } = appInitData || {};
const children = await buzzyFrameAPI.getChildItemsByField({
appID: rowJSON._id,
fieldID: rowJSON.fieldID,
});
appImages = children;
if (window.updateImages) {
window.updateImages(appImages);
}
} catch (error) {
console.error('Error fetching images:', error);
if (window.updateImages) {
window.updateImages([]);
}
}
}
function App() {
const { useEffect, useState, useCallback } = React;
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(true);
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [dragActive, setDragActive] = useState(false);
useEffect(() => {
window.updateImages = newImages => {
setImages(newImages);
setLoading(false);
};
if (appImages.length > 0) {
setImages(appImages);
setLoading(false);
}
return () => {
window.updateImages = null;
};
}, []);
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 handleDeleteImage = async childID => {
if (!confirm('Are you sure you want to delete this image?')) return;
try {
await buzzyFrameAPI.removeMicroappChild({ childID });
} catch (error) {
console.error('Error deleting image:', error);
}
};
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 images here or click to browse
</p>
<input
type="file"
accept="image/*"
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="columns is-multiline">
{images.map(image => (
<div key={image._id} className="column is-3">
<div className="card image-card">
<div className="card-image">
<figure className="image">
<img
src={image.content.url}
alt={image.content.filename}
/>
</figure>
</div>
<div className="card-content">
<p className="is-size-7 has-text-grey">
{image.content.filename}
</p>
</div>
<div className="image-actions">
<div className="buttons has-addons">
<button
className="button is-danger is-small"
onClick={() => handleDeleteImage(image._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>