# Advanced Image Gallery

This example demonstrates a sophisticated image gallery with drag-and-drop upload, file management, and S3 integration using the BuzzyFrameAPI.

## Key Features

* **Drag-and-drop file upload** with visual feedback
* **Multiple file selection** and batch upload
* **Progress tracking** during upload operations
* **Image preview** with hover actions
* **File deletion** with confirmation
* **Automatic URL refresh** for expired S3 links
* **Real-time updates** when images are added or removed
* **Responsive grid layout** using Bulma CSS framework

## Use Cases

Perfect for applications requiring:

* Photo galleries and portfolios
* Document management systems
* Product image management
* User-generated content platforms
* Media libraries and archives

## Implementation Overview

This widget uses the **Child Table Data** pattern, where images are stored as child records linked to a parent row through a file/image field. The implementation showcases:

* Modern React hooks (useState, useEffect, useCallback)
* Proper BuzzyFrameAPI initialization outside React components
* Real-time data synchronization with microapp listeners
* S3 file operations with presigned URLs
* Error handling and user feedback

## Code Example

```html
<!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/react@18.2.0/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/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>
```

## Key Concepts

### BuzzyFrameAPI Integration

* **Initialization Pattern**: API is initialized outside React components for proper async handling
* **Child Data Management**: Uses `getChildItemsByField()` to retrieve file attachments
* **Real-time Updates**: `addMicroappListener()` ensures immediate UI updates when data changes
* **S3 Operations**: Handles presigned URLs for secure file upload and access

### File Management Features

* **Drag-and-Drop**: Native HTML5 drag-and-drop with visual feedback
* **Progress Tracking**: Real-time upload progress with `onProgress` callbacks
* **URL Expiration**: Automatic refresh of expired S3 URLs to maintain access
* **Error Handling**: Comprehensive error handling with user feedback

### React Best Practices

* **Hooks Usage**: Modern React patterns with useState, useEffect, and useCallback
* **Memory Management**: Proper cleanup of event listeners and global functions
* **State Management**: Efficient state updates with minimal re-renders

This example demonstrates how code widgets can create professional-grade file management interfaces that rival dedicated file management applications.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.buzzy.buzz/the-building-blocks/code-widget-custom-code/examples/advanced-image-gallery.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
