decap-cms icon indicating copy to clipboard operation
decap-cms copied to clipboard

Ability to save image width and height when uploading an image

Open smashercosmo opened this issue 4 years ago • 1 comments

Is your feature request related to a problem? Please describe. Nowadays modern browsers use image 'width' and 'height' attributes to avoid layout shift https://twitter.com/jensimmons/status/1172922185570279425

Also Next.js image component requires 'width' and 'height' attributes to be provided https://nextjs.org/docs/api-reference/next/image#required-props

The problem is that it's pretty annoying to input these values manually. It would be very convenient if these values could be written automatically when image is uploaded.

Describe the solution you'd like In config in could look like

collections:
  - label: Albums
    name: albums
    folder: content/albums
    format: yaml
    create: true
    fields:
      - { label: Title, name: title, widget: string }
      - label: Images
        name: images
        widget: list
        fields:
          - { label: Image, name: image, widget: image }
          - { label: Width, name: width, widget: string, value: '{{entry.images.image.width}}' }
          - { label: Height, name: height, widget: string, value: '{{entry.images.image.height}}' }

Describe alternatives you've considered Currently I'm doing it using CMS preSave event, but it's pretty verbose

async function saveImageDimensions({ entry }: any) {
  if (entry.get('data').get('collection') !== 'albums') {
    return entry.get('data')
  }
  const images = entry.get('data').get('images').toJSON()
  const mediaFiles = entry.get('mediaFiles').toJSON() as ReadonlyArray<{
    path: string
    url: string
  }>
  const mediaFilesObj: { [key: string]: string } = {}
  for (let i = 0, l = mediaFiles.length; i < l; i += 1) {
    const { url, path } = mediaFiles[i]
    mediaFilesObj[path.replace(/^public(.*)/, '$1')] = url
  }
  const dimensions = await Promise.all<{
    path: string
    width: number
    height: number
  }>(
    images.map(({ image }: { image: string }) => {
      return new Promise((resolve) => {
        const img = new Image()
        img.onload = function handleLoad() {
          resolve({
            path: image,
            width: img.naturalWidth,
            height: img.naturalHeight,
          })
        }
        img.src = mediaFilesObj[image]
      })
    }),
  )
  const dimensionsObj: {
    [key: string]: { width: number; height: number }
  } = {}
  for (let i = 0, l = dimensions.length; i < l; i += 1) {
    const { path, width, height } = dimensions[i]
    dimensionsObj[path] = { width, height }
  }
  return entry.get('data').set(
    'images',
    entry
      .get('data')
      .get('images')
      .map((item: any) => {
        return item
          .set('width', dimensionsObj[item.get('image')].width)
          .set('height', dimensionsObj[item.get('image')].height)
      }),
  )
}

cms.registerEventListener({
  name: 'preSave',
  handler: saveImageDimensions,
})

smashercosmo avatar Dec 08 '20 10:12 smashercosmo