sanity-plugin-media icon indicating copy to clipboard operation
sanity-plugin-media copied to clipboard

(feat) Option to insert multiple images

Open dennispassway opened this issue 1 year ago โ€ข 3 comments

In my projects I always use the sanity-plugin-media, so thanks for creating it! In many of these projects I also run into that I want to insert multiple images at once in an image array (ie for a carousel or gallery). Searching around, I found that more people have this issue, see the Github issues below or the Sanity Slack:

  • https://github.com/sanity-io/sanity/issues/4483
  • https://github.com/sanity-io/sanity/issues/1804
  • https://github.com/sanity-io/sanity-plugin-media/issues/85

In this pull request, I add the option to insert multiple images at once. By adding the selectionType 'multiple' option, you can determine that the asset source should pass multiple assets. This is not officially supported, but I figured that is where Sanity wishes to go with the selectionType property, since it currently only supports "single".

In this PR I support "multiple", without breaking- or changing any of the current functionality. If you do not pass the selectionType="multiple" it will work the same.

To make this work in a project, you have to create a custom component that creates a AssetSourceComponent with the property selectionType set to multiple. I will pass mine as an example so you can see how this works:

'use client';

import { AssetFromSource, StringInputProps, set, useFormBuilder } from 'sanity';
import { Button, Stack, Text } from '@sanity/ui';
import { FaRegImages } from 'react-icons/fa';
import React, { Fragment, MouseEventHandler, useCallback, useMemo, useState } from 'react';

export const MultipleImagesInputComponent = (properties: StringInputProps) => {
  const { onChange, value, renderDefault } = properties;
  const formBuilder = useFormBuilder();

  const [showAssetSource, setShowAssetSource] = useState(false);

  // I would love for this not to use internal but it is currently the only way I think.
  const AssetSourceComponent = useMemo(
    () => formBuilder?.__internal?.image?.assetSources[0]?.component,
    [formBuilder?.__internal?.image?.assetSources]
  );

  const handleAssetSourceClosed = useCallback(() => {
    setShowAssetSource(false);
  }, []);

  const handleSelectAssetFromSource = useCallback(
    (assetsFromSource: AssetFromSource[]) => {
      try {
        const assetReferences = assetsFromSource.map((asset) => ({
          _key: randomKey(),
          _type: 'image',
          asset: {
            _ref: asset.value,
            _type: 'reference',
          },
        }));

        const newValue = [...(value || []), ...assetReferences];
        onChange(set(newValue));
      } catch (error) {
        console.error(error);
      } finally {
        setShowAssetSource(false);
      }
    },
    [onChange, value]
  );

  const onMultipleImagesClick: MouseEventHandler<HTMLButtonElement> = useCallback(() => {
    setShowAssetSource(true);
  }, []);

  return (
    <Fragment>
      <Stack space={4}>
        {renderDefault(properties)}
        <Text size={2} muted align="center">
          ๐Ÿ’ก To upload multiple images, simply drag them on the list above.
        </Text>
        <Button
          onClick={onMultipleImagesClick}
          mode="ghost"
          icon={FaRegImages}
          text="Add multiple images from media"
        />
      </Stack>

      {showAssetSource && !!AssetSourceComponent ? (
        <AssetSourceComponent
          accept="image/*"
          assetType="image"
          onClose={handleAssetSourceClosed}
          onSelect={handleSelectAssetFromSource}
          selectedAssets={[]}
          selectionType="multiple" // This is where the magic happens
        />
      ) : undefined}
    </Fragment>
  );
};

function randomKey() {
  return Math.random().toString(36).slice(2);
}

I hope you will find this functionality useful. If there is a better way to achieve what I'm trying to do I love to hear that as well. Searching around, I found many people with this wish, but no solution, therefore I created this PR.

dennispassway avatar May 21 '24 07:05 dennispassway

@robinpyon I'm not sure if you are the right person to tag, if not, could you point me in the right direction? I haven't had a response from any of the maintainers on this PR in 2 weeks and would love to hear your thoughts.

dennispassway avatar Jun 03 '24 07:06 dennispassway

@kmelve I haven't heard anything on this PR for a month now... Do you know who I have to connect to this?

dennispassway avatar Jun 17 '24 07:06 dennispassway

Hi @dennispassway โ€“ย thanks for your work and contribution here! We're definitively interested in looking at merging this; hopefully we'll find time to get to it over the next couple of weeks ๐Ÿ™‡

kmelve avatar Jun 27 '24 14:06 kmelve

It would be so awesome to have this! ๐Ÿ™๐Ÿผ It's something I get asked about on every project I build.

alandouglas96 avatar Jan 29 '25 16:01 alandouglas96

Wondering what is blocking this for the last 6 months? It seems there is a lot of interest for such a feature.

theolvq avatar Feb 11 '25 22:02 theolvq

Hi @kmelve ! This has been stale for a while now. I understand you guys have lots to do, but wondering if you could tell if I can do anything to help get this merged?

dennispassway avatar Apr 16 '25 07:04 dennispassway

Would love to see this feature integrated, please!

andrevvm avatar Jun 26 '25 19:06 andrevvm

@kmelve this is not happening anymore is it? ๐Ÿคฃ

For those who really want this, i published my fork on npm and am using that in my projects until this or something similar to this is merged: https://www.npmjs.com/package/@dennispassway/sanity-plugin-media

dennispassway avatar Jul 01 '25 07:07 dennispassway

Thanks @dennispassway , I really need this! Would really appreciate it if @kmelve would take a look and merge ๐Ÿ™๐Ÿผ

CHR-onicles avatar Nov 03 '25 23:11 CHR-onicles