Skip to content

Error when adding new custom components #76

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions