diff --git a/src/templates/Image.tsx b/src/templates/Image.tsx new file mode 100644 index 00000000..f5633922 --- /dev/null +++ b/src/templates/Image.tsx @@ -0,0 +1,50 @@ +import { GatsbyImage, IGatsbyImageData } from 'gatsby-plugin-image' +import * as React from 'react' + +/** + * Shared type to describe what image data looks like coming from Contentful. Note that because Scalars[JSON] + * is defined as `never`, the type of Pick<... 'gatsbyImageData'> is undefined and I need a separate type + */ +export type RawImageData = GatsbyTypes.Maybe< + Pick< + GatsbyTypes.ContentfulAsset, + 'id' | 'title' | 'gatsbyImageData' | 'description' + > +> + +/** + * Internal type to describe what image data looks like coming from Contentful with a transformation applied + * manually. Note that because Scalars[JSON] is defined as `never`, this separate type is necessary. + */ +type ImageData = GatsbyTypes.Maybe< + Pick & { + gatsbyImageData: IGatsbyImageData + } +> + +/** + * Just image data + alt text that we need + */ +type PropTypes = { + image: RawImageData +} + +/** + * A simple image using the gatsby image data passed to it + * @param props An object containing `PropTypes` + * @returns An element for the image and all associated content + */ +const Image = ({ image }: PropTypes): JSX.Element => { + const typedImageData = (image as ImageData)?.gatsbyImageData + if (!image || !typedImageData?.images.sources) { + throw TypeError('Malformed image data') + } + return ( + + ) +} + +export default Image diff --git a/src/templates/Page.tsx b/src/templates/Page.tsx index 6babc9d5..6021e197 100644 --- a/src/templates/Page.tsx +++ b/src/templates/Page.tsx @@ -39,6 +39,12 @@ export const query = graphql` internal { type } + image { + id + gatsbyImageData(placeholder: BLURRED) + description + title + } } fragment Section on ContentfulSection { @@ -59,6 +65,9 @@ export const query = graphql` type thumbnail { id + gatsbyImageData(placeholder: BLURRED) + description + title } internal { type diff --git a/src/templates/Project.tsx b/src/templates/Project.tsx index a7284391..36963eb5 100644 --- a/src/templates/Project.tsx +++ b/src/templates/Project.tsx @@ -1,5 +1,6 @@ import * as React from 'react' -import RichTextParagraph from './RichTextParagraph' +import Image from './Image' +import RichText from './RichText' /** * Top level page has a list of blocks we pass in to start the recursion @@ -14,14 +15,14 @@ type PageProps = { * @returns An element for the project as a wrapper */ const Project = ({ project }: PageProps): JSX.Element => { - if (!project.title || !project.type) { + if (!project.title || !project.type || !project.thumbnail?.gatsbyImageData) { throw TypeError('Badly formatted project') } return ( <> {project.title} | {project.creationDate} | {project.type} |{' '} - {project.thumbnail?.id} - + + ) } diff --git a/src/templates/RichTextParagraph.tsx b/src/templates/RichText.tsx similarity index 66% rename from src/templates/RichTextParagraph.tsx rename to src/templates/RichText.tsx index 3301440f..6d05fb8f 100644 --- a/src/templates/RichTextParagraph.tsx +++ b/src/templates/RichText.tsx @@ -1,8 +1,18 @@ import { documentToReactComponents } from '@contentful/rich-text-react-renderer' import * as React from 'react' +/** + * Shared type for raw text as it comes from Contentful + */ +export type RawRichText = GatsbyTypes.Maybe< + Pick +> + +/** + * All we need is rich text to display this element + */ type PropTypes = { - richText: GatsbyTypes.Maybe> + richText: RawRichText } /** @@ -12,11 +22,11 @@ type PropTypes = { * @param props An object containing `PropTypes` * @returns An element for the rich text, composed of other items inside it */ -const RichTextParagraph = ({ richText }: PropTypes): JSX.Element => { +const RichText = ({ richText }: PropTypes): JSX.Element => { if (!richText || !richText?.raw) { throw TypeError('Malformed rich text') } return <>{documentToReactComponents(JSON.parse(richText.raw))} } -export default RichTextParagraph +export default RichText diff --git a/src/templates/RichTextWithImage.tsx b/src/templates/RichTextWithImage.tsx new file mode 100644 index 00000000..281cf7e4 --- /dev/null +++ b/src/templates/RichTextWithImage.tsx @@ -0,0 +1,67 @@ +import * as React from 'react' +import Image, { RawImageData } from './Image' +import RichText, { RawRichText } from './RichText' + +/** + * Shared type for raw image type without yet being an enum + */ +type ImageTypeData = GatsbyTypes.Maybe + +/** + * Describes the supported values for image placement. If not one of these, either + * we need to support a new value or the existing data is incorrect. + */ +enum ImagePlacement { + None = 'none', + Left = 'left', + Right = 'right', +} + +/** + * Rich text, image data, and information about how to position that image + */ +type PropTypes = { + richText: RawRichText + imageType: ImageTypeData + image: RawImageData +} + +/** + * A rich text paragraph using the default settings to render the elements because it's + * really tough to set up elements that work right for custom elements. + * See https://www.contentful.com/developers/docs/concepts/rich-text/ + * @param props An object containing `PropTypes` + * @returns An element for the rich text, composed of other items inside it + */ +const RichTextWithImage = ({ + richText, + image, + imageType, +}: PropTypes): JSX.Element => { + const placement = imageType as ImagePlacement + if (!richText || !imageType || !placement) { + throw TypeError('Malformed rich text with image data') + } + const textElement = () => + const imageElement = () => + switch (placement) { + case ImagePlacement.None: + return <>{textElement()} + case ImagePlacement.Left: + return ( + <> + {imageElement()} + {textElement()} + + ) + case ImagePlacement.Right: + return ( + <> + {textElement()} + {imageElement()} + + ) + } +} + +export default RichTextWithImage diff --git a/src/templates/Section.tsx b/src/templates/Section.tsx index 1dc6160a..c618ebaa 100644 --- a/src/templates/Section.tsx +++ b/src/templates/Section.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import Link from './Link' import Project from './Project' -import RichTextParagraph from './RichTextParagraph' +import RichTextWithImage from './RichTextWithImage' /** * This is the array of section type that is nested within BlockType @@ -60,7 +60,14 @@ const generateElement = (block: BlockType): JSX.Element => { throw TypeError("Section wasn't defined") } if (isTextFragment(block)) { - return + return ( + + ) } else if (isLinkFragment(block)) { return } else if (isProjectFragment(block)) {