Skip to content

Error when adding new custom components #76

@shriyanss

Description

@shriyanss

I was trying to add a custom component to display a gallery of 6 images fetched from AWS S3 (which contains >= six images at root). For this, I copied the MediaGallerySection and created a file content/pages/backgrounds.md. I've then added mappings for it to src/components/components-registry.tsx, and src/types/generated.ts following the pattern I saw. However, I got an error. In an effort to solve that, I fix a simple mistake, but then got another error.

In the visual editor, it is being rendered as expected. However, I have two issues:-

  • When editing another section anywhere through the visual editor, the preview fails to load and I have to restart the editor
  • Also, i can't edit this element (section) through the visual editor. I have to go to code every time I want to make changes to it

Here's the error I got in the logs (the first one appeared as soon as I added the new component, and the latter after trying to do some workarounds):-

7:51:05 PM: [error] Error converting value in file 'content/pages/backgrounds.md' at field path 'sections.0' of declared type 'model'. No model found with name: WallpaperGallerySection.
7:51:05 PM: [error] Error converting value in file 'content/pages/backgrounds.md' at field path 'sections' of declared type 'list'. One of the list items could not be converted, ignoring the while list.
7:51:20 PM: [error] AnnotationError: Field path sections.0 not found or does not match content schema fieldPath:'sections.0', value:'{"oid":null,"fp":"sections.0","loc":"*[1]","hasOnlyTextNodes":false}', elementXPath:'/html/body[1]/div[1]/data[1]/data[1]/div[1]/div[2]/main[1]/div[1]/data[1]'
7:51:24 PM: [error] AnnotationError: Field path sections.0 not found or does not match content schema fieldPath:'sections.0', value:'{"oid":null,"fp":"sections.0","loc":"*[1]","hasOnlyTextNodes":false}', elementXPath:'/html/body[1]/div[1]/data[1]/data[1]/div[1]/div[2]/main[1]/div[1]/data[1]'

And here's the code for the component:-

import * as React from 'react';
import classNames from 'classnames';

import { mapStylesToClassNames as mapStyles } from '../../../utils/map-styles-to-class-names';
import Section from '../Section';
import ImageBlock from '../../molecules/ImageBlock';

type MediaGallerySectionProps = {
    type: string;
    elementId: string;
    colors?: 'colors-a' | 'colors-b' | 'colors-c' | 'colors-d' | 'colors-e' | 'colors-f';
    title?: string;
    subtitle?: string;
    spacing?: number;
    columns?: number;
    aspectRatio?: string;
    showCaption: boolean;
    enableHover: boolean;
    styles?: any;
};

type MediaGalleryItemProps = {
    image: Image;
    showCaption: boolean;
    enableHover: boolean;
    aspectRatio: string;
};

type Image = {
    url: string;
    altText: string;
    caption: string;
};

export default function WallpaperGallerySection(props: MediaGallerySectionProps) {
    const {
        type,
        elementId,
        colors,
        title,
        subtitle,
        spacing = 16,
        columns = 4,
        aspectRatio = '1:1',
        showCaption,
        enableHover,
        styles = {}
    } = props;
    const [images, setImages] = React.useState<Image[]>([]);

    const fetchImages = async () => {
        const bucketName = '......';
        const region = 'ap-south-1';

        try {
            const response = await fetch(`https://${bucketName}.s3.${region}.amazonaws.com/?list-type=directory`);
            const data = await response.text();

            const parser = new DOMParser();
            const xml = parser.parseFromString(data, 'text/xml');
            const contents = xml.getElementsByTagName('Contents');

            const imageUrls = Array.from(contents).map(item => {
                const imageKey = item.getElementsByTagName('Key')[0].textContent;
                return {
                    url: `https://${bucketName}.s3.${region}.amazonaws.com/${imageKey}`,
                    altText: `Wallpaper ${imageKey}`, // Customize alt text as needed
                    caption: imageKey || '' // You can customize captions if needed
                };
            });

            // Randomly select 6 images
            const selectedImages = selectRandomImages(imageUrls, 6);
            setImages(selectedImages);
        } catch (error) {
            console.error('Error fetching images:', error);
        }
    };

    // Function to select random images from the array
    const selectRandomImages = (imageArray: Image[], count: number): Image[] => {
        const shuffled = [...imageArray].sort(() => 0.5 - Math.random());
        return shuffled.slice(0, count);
    };

    React.useEffect(() => {
        fetchImages();
    }, []);

    return (
        <Section type={type} elementId={elementId} colors={colors} styles={styles.self}>
            {title && <h2 className={classNames(styles.title ? mapStyles(styles.title) : null)}>{title}</h2>}
            {subtitle && (
                <p
                    className={classNames('text-lg', 'sm:text-xl', styles.subtitle ? mapStyles(styles.subtitle) : null, {
                        'mt-6': title
                    })}
                >
                    {subtitle}
                </p>
            )}
            {images.length > 0 && (
                <div
                    className={classNames('grid', 'place-items-center', mapColStyles(columns), {
                        'mt-12': title || subtitle
                    })}
                    style={{
                        gap: spacing ? `${spacing}px` : undefined
                    }}
                >
                    {images.map((image, index) => (
                        <MediaGalleryImage 
                            key={index} 
                            image={image} 
                            showCaption={showCaption} 
                            enableHover={enableHover} 
                            aspectRatio={aspectRatio} 
                        />
                    ))}
                </div>
            )}
        </Section>
    );
}

function MediaGalleryImage(props: MediaGalleryItemProps) {
    const { image, showCaption, enableHover, aspectRatio } = props;
    if (!image) {
        return null;
    }
    return (
        <figure
            className={classNames('overflow-hidden', 'relative', 'w-full', mapAspectRatioStyles(aspectRatio), {
                'h-0': aspectRatio !== 'auto'
            })}
        >
            <ImageBlock
                url={image.url}
                altText={image.altText}
                className={classNames('w-full', {
                    'h-full absolute left-0 top-0 object-cover': aspectRatio !== 'auto',
                    'transition-transform hover:scale-105': enableHover
                })}
            />
            {showCaption && image.caption && (
                <figcaption className="absolute bg-white/50 text-dark left-0 mx-2 bottom-2 p-1.5 text-xs pointer-events-none">{image.caption}</figcaption>
            )}
        </figure>
    );
}

function mapAspectRatioStyles(aspectRatio: string) {
    switch (aspectRatio) {
        case '1:1':
            return 'pt-1/1';
        case '2:3':
            return 'pt-3/2';
        case '3:2':
            return 'pt-2/3';
        case '3:4':
            return 'pt-4/3';
        case '4:3':
            return 'pt-3/4';
        case '16:9':
            return 'pt-9/16';
        default:
            return null;
    }
}

function mapColStyles(columns: number) {
    switch (columns) {
        case 2:
            return 'grid-cols-2';
        case 3:
            return 'grid-cols-2 sm:grid-cols-3';
        case 4:
            return 'grid-cols-2 sm:grid-cols-4';
        case 5:
            return 'grid-cols-2 sm:grid-cols-3 md:grid-cols-5';
        case 6:
            return 'grid-cols-2 sm:grid-cols-4 md:grid-cols-6';
        case 7:
            return 'grid-cols-2 sm:grid-cols-4 md:grid-cols-7';
        default:
            return null;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions