Structured Content 2022: Join our conference to explore fresh perspectives on content and digital experiences →

Vimeo/Youtube video id and thumbnail custom component

By Arjen Scherff-de Water

Get thumbnail and video by pasting vimeo/youtube url

VideoComponent.tsx

import { PatchEvent, set, unset } from "part:@sanity/form-builder/patch-event";
import React, { useState } from "react";
import getVideoId from "get-video-id";
import { withDocument } from "part:@sanity/form-builder";
import { nanoid } from "nanoid";
import { TextInput, Stack, Button } from "@sanity/ui";
import { AddIcon } from "@sanity/icons";

const VideoIdFieldComponent = (props, ref) => {
  const { onChange, value } = props;

  const [newDoc, setNewDoc] = useState<{
    id: string;
    service: string;
    thumbnail: string;
    url: string;
  }>(value || null);

  const createPatchFrom = (value: any) =>
    PatchEvent.from(value === "" ? unset() : set(value));

  async function getThumbnail({ id, service }) {
    if (service === "youtube") {
      return `https://img.youtube.com/vi/${id}/mqdefault.jpg`;
    }

    if (service === "vimeo") {
      const response = await fetch(
        `https://vimeo.com/api/oembed.json?url=https://player.vimeo.com/video/${id}`
      );
      const json = await response.json();
      return json.thumbnail_url.split("_")[0] + "_320x180";
    }

    return null;
  }

  async function onUrlChange(e) {
    const url = e.target.value;

    if (url.trim().length) {
      const { id, service } = getVideoId(url);
      const thumbnail = await getThumbnail({ id, service });
      setNewDoc({ url, id, service, thumbnail });
    } else {
      setNewDoc(null);
    }
  }

  function save() {
    onChange(
      createPatchFrom({
        _key: nanoid(),
        _type: "videoId",
        ...newDoc,
      })
    );
  }

  return (
    <Stack space={3}>
      <TextInput
        type="text"
        onChange={onUrlChange}
        ref={ref}
        value={newDoc?.url}
      />

      {newDoc?.thumbnail && <img src={newDoc.thumbnail} />}

      <Button
        text={`Create`}
        fontSize={2}
        icon={AddIcon}
        padding={3}
        tone="positive"
        onClick={save}
      />
    </Stack>
  );
};

export const VideoIdField = withDocument(
  React.forwardRef(VideoIdFieldComponent)
);

Video id schema

import { VideoIdField } from "../inputs/VideoIdField";
import React from "react";

export default {
  type: "object",
  name: "videoId",
  title: "Video ID",
  inputComponent: VideoIdField,
  fields: [
    {
      type: "string",
      name: "url",
      title: "Video URL",
      description: "A URL to a vimeo or youtube video",
    },
    {
      type: "string",
      name: "id",
      title: "Video ID",
      description: "Auto generated",
      readOnly: true,
    },
    {
      type: "string",
      name: "service",
      title: "Service",
      description: "Auto generated",
      readOnly: true,
    },
    {
      type: "string",
      name: "thumbnail",
      title: "Thumbnail",
      description: "Auto generated",
      readOnly: true,
    },
  ],
  preview: {
    select: {
      id: "id",
      service: "service",
      thumbnail: "thumbnail",
    },
    prepare({ service = "", thumbnail }) {
      return {
        title: `video ${service}`,
        media: () => (thumbnail ? <img src={thumbnail} /> : null),
      };
    },
  },
};

Youtube/Vimeo only for now, can be extended to be used with other services. Needs nanoid and get-video-id from npm.

Contributor

Other schemas by author