diff --git a/src/api/fetchBase64Image.ts b/src/api/fetchBase64Image.ts index 7226f668..9de8c954 100644 --- a/src/api/fetchBase64Image.ts +++ b/src/api/fetchBase64Image.ts @@ -10,7 +10,7 @@ export const buildBase64URL = (contentType: string, base64: string): string => export const fetchImgixBase64Image = (cache: GatsbyCache) => ( url: string, ): TE.TaskEither => - withCache(`gatsby-plugin-imgix-base64-url-${url}`, cache, () => + withCache(`imgix-gatsby-base64-url-${url}`, cache, () => pipe( url, fetch, @@ -27,3 +27,38 @@ export const fetchImgixBase64Image = (cache: GatsbyCache) => ( ), ), ); + +export type HexString = string; +type ImgixPaletteResponse = { + dominant_colors: { + vibrant: { + /** + * In format '#xxxxxx' + */ + hex: string; + }; + }; +}; + +export const fetchImgixDominantColor = (cache: GatsbyCache) => ( + buildURL: (params: Record) => string, +): TE.TaskEither => + pipe(buildURL({ palette: 'json' }), (url) => + withCache(`imgix-gatsby-dominant-color-${url}`, cache, () => + pipe( + url, + fetch, + TE.chain((res) => + TE.tryCatch( + () => res.json(), + (err) => + new Error( + 'Something went wrong while decoding the dominant color for the placeholder image: ' + + String(err), + ), + ), + ), + TE.map((data) => data.dominant_colors.vibrant.hex), + ), + ), + ); diff --git a/src/modules/gatsby-source-url/createImgixGatsbyImageDataFieldConfig.ts b/src/modules/gatsby-source-url/createImgixGatsbyImageDataFieldConfig.ts index 9821a7e7..bcbe5a0c 100644 --- a/src/modules/gatsby-source-url/createImgixGatsbyImageDataFieldConfig.ts +++ b/src/modules/gatsby-source-url/createImgixGatsbyImageDataFieldConfig.ts @@ -7,8 +7,10 @@ import { GatsbyCache } from 'gatsby'; import { FluidObject } from 'gatsby-image'; import { generateImageData, + getLowResolutionImageURL, IGatsbyImageData, IGatsbyImageHelperArgs, + ImageFormat, } from 'gatsby-plugin-image'; import { getGatsbyImageFieldConfig } from 'gatsby-plugin-image/graphql-utils'; import { @@ -20,6 +22,10 @@ import { } from 'gatsby/graphql'; import ImgixClient from 'imgix-core-js'; import R from 'ramda'; +import { + fetchImgixBase64Image, + fetchImgixDominantColor, +} from '../../api/fetchBase64Image'; import { TaskOptionFromTE } from '../../common/fpTsUtils'; import { ImgixSourceDataResolver, @@ -27,18 +33,9 @@ import { taskEitherFromSourceDataResolver, } from '../../common/utils'; import { IImgixParams } from '../../publicTypes'; -import { ImgixParamsInputType } from './graphqlTypes'; +import { ImgixParamsInputType, ImgixPlaceholderType } from './graphqlTypes'; import { resolveDimensions } from './resolveDimensions'; -/* const fitMap = { - cover: , - contain: , - fill: , - inside: , - outside: , - -} */ - const generateImageSource = ( client: ImgixClient, ): IGatsbyImageHelperArgs['generateImageSource'] => ( @@ -99,27 +96,58 @@ const resolveGatsbyImageData = ({ client: imgixClient, }), ) - .return(({ url, dimensions: { width, height } }) => - generateImageData({ - ...args, - pluginName: `@imgix/gatsby`, - filename: url, - sourceMetadata: { width, height, format: 'auto' }, - // placeholderURL: await getBase64Image({ baseUrl }), - // TODO: not a public API, need to replace with public API when implemented - breakpoints: - args.breakpoints ?? - (imgixClient as any)._generateTargetWidths( - args.widthTolerance, - args.srcSetMinWidth, - args.srcSetMaxWidth, + .letL( + 'baseImageDataArgs', + ({ url, dimensions: { width, height } }) => + ({ + ...args, + pluginName: `@imgix/gatsby`, + filename: url, + sourceMetadata: { width, height, format: 'auto' as ImageFormat }, + // TODO: not a public API, need to replace with public API when implemented + breakpoints: + args.breakpoints ?? + (imgixClient as any)._generateTargetWidths( + args.widthTolerance, + args.srcSetMinWidth, + args.srcSetMaxWidth, + ), + formats: ['auto'] as ImageFormat[], + generateImageSource: generateImageSource(imgixClient), + // TODO: check if this should be set to imgixParams + options: args, + } as const), + ) + .bindL('placeholderData', ({ url, baseImageDataArgs }) => { + console.log('args.placeholder', args.placeholder); + if (args.placeholder === 'blurred') { + return pipe( + getLowResolutionImageURL(baseImageDataArgs), + fetchImgixBase64Image(cache), + TE.map((base64Data) => ({ + placeholder: { fallback: base64Data }, + })), + ); + } + if (args.placeholder === 'dominantColor') { + return pipe( + // TODO: add imgix params + fetchImgixDominantColor(cache)((params) => + imgixClient.buildURL(url, params), ), - // breakpoints - formats: ['auto'], - generateImageSource: generateImageSource(imgixClient), - options: args, + TE.map((dominantColor) => ({ + backgroundColor: dominantColor, + })), + ); + } + return TE.right({}); + }) + .return(({ baseImageDataArgs, placeholderData }) => ({ + ...generateImageData({ + ...baseImageDataArgs, }), - ), + ...placeholderData, + })), TE.getOrElseW(() => T.of(undefined)), )(); }; @@ -167,6 +195,14 @@ export const createImgixGatsbyImageFieldConfig = ({ All of imgix's parameters can be found here: https://docs.imgix.com/apis/rendering `, }, + placeholder: { + type: ImgixPlaceholderType, + description: stripIndent` + Format of generated placeholder image, displayed while the main image loads. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. + NONE: no placeholder. Set "backgroundColor" to use a fixed background color.`, + }, widthTolerance: { type: GraphQLFloat, description: stripIndent` @@ -189,7 +225,6 @@ export const createImgixGatsbyImageFieldConfig = ({ `, defaultValue: 8192, }, - // TODO: add placeholder }, }; diff --git a/src/modules/gatsby-source-url/graphqlTypes.ts b/src/modules/gatsby-source-url/graphqlTypes.ts index ea4e8c02..021f9988 100644 --- a/src/modules/gatsby-source-url/graphqlTypes.ts +++ b/src/modules/gatsby-source-url/graphqlTypes.ts @@ -153,3 +153,12 @@ export const unTransformParams = ( export type IGatsbySourceImgixUrlField = string; export const gatsbySourceImgixUrlFieldType = GraphQLString; + +export const ImgixPlaceholderType = new GraphQLEnumType({ + name: `ImgixPlaceholder`, + values: { + DOMINANT_COLOR: { value: `dominantColor` }, + BLURRED: { value: `blurred` }, + NONE: { value: `none` }, + }, +});