Link copied to clipboard!

Annotations API

The Annotations API lets you deeply integrate Cord’s annotation functionality into your product, creating a seamless experience.

By supplying the “glue code” between the annotation flow and your UI, you ensure annotations always match what’s on the screen. It’s perfect for complex features with multiple states, like charts, text editors, maps, and more.

annotation-overview.png

The Annotations API is currently in BETA and only supports React apps. The hooks we offer and their arguments / return types might change as we iterate and improve them.

Annotation Location

The main concept supporting the Annotations API is the annotation location: a flat object you associate with a part of your document, that describes, in your own words according to your data model, what is being annotated. Cord remembers this Location object alongside the annotation, and we use it when it’s time to render the annotation pin.

For example, for a chart component (either written entirely by you or using a library like Highcharts, Chart.js, etc), you might describe the point being annotated as:

{
  "page": "dashboard",
  "chart": "widgets-produced",
  "month": "february",
  "year": 2022
}

Or, for a code editor (that uses a library like Monaco, CodeMirror, etc), you might describe an annotation as:

{ "file": "src/app/index.tsx", "line": 42 }

An annotation target is an HTML element in your application’s DOM decorated with a data-cord-annotation-location attribute, with the value being a stable serialization of the location object that best represents that area of the document.

When the user starts the annotation flow from the Cord sidebar and clicks to place the annotation pin somewhere on the page, we check if the click is within an annotation target, by checking its DOM ancestors for any data-cord-annotation-location attribute.

Decorate your DOM with the annotation location

The useCordAnnotationTargetRef hook is a helper hook you use to decorate HTML elements with the data-cord-annotation-location attribute representing the location for annotations captured within that element (and its DOM subtree). This hook handles serializing the location object and attaching it to the element as the data attribute.

import { useCordAnnotationTargetRef } from "@cord-sdk/react";

function VideoPlayerComponent({ videoSrc }) {
  const videoElementRef = useCordAnnotationTargetRef({
    page: "dashboard",
    element: "video",
    src: videoSrc,
  });

  return <video src={videoSrc} ref={videoElementRef} />;
}

With just this addition to your DOM, any time the user places an annotation on the <video> element, we store the annotation location as, for example, { page: "dashboard", element: "video", src: "welcome.mp4" }. When other users see that annotation, it will point to an element in the page that matches that location.

Define custom locations

Optional

If your annotations need to include extra location information that is highly dynamic, dependent on where exactly the user clicked, or otherwise can’t be expressed at render time, you can provide that through the useCordAnnotationCaptureHandler hook.

For example, you might want to remember the timestamp the video annotation was taken at, so that you can skip the video back to that exact timestamp when the user clicks the annotation in Cord.

import {
  useCordAnnotationTargetRef,
  useCordAnnotationCaptureHandler,
} from "@cord-sdk/react";

type VideoPlayerAnnotation = {
  page: string;
  element: string;
  src: string;
  currentTime: number;
};

function VideoPlayerComponent({ videoSrc }) {
  const location = { page: "dashboard", element: "video", src: videoSrc };

  const videoElementRef = useCordAnnotationTargetRef(location);

  useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
    const currentTime = videoElementRef.current?.currentTime ?? 0;
    return {
      extraLocation: { currentTime },
    };
  });

  return <video ref={videoElementRef} src={videoSrc} />;
}

Add location specific labels

Optional

The useCordAnnotationCaptureHandler hook can also be used to define a location-specific label for the annotation, plain text that explains for other people what is being annotated, by returning a label field. If you don’t provide one, we default to “Annotation”.

import {
  useCordAnnotationTargetRef,
  useCordAnnotationCaptureHandler,
} from "@cord-sdk/react";

type VideoPlayerAnnotation = {
  page: string;
  elenent: string;
  src: string;
  currentTime: number;
};

function VideoPlayerComponent({ videoSrc }) {
  const location = { page: "dashboard", element: "video", src: videoSrc };

  const videoElementRef = useCordAnnotationTargetRef(location);

  useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
    const currentTime = videoElementRef.current?.currentTime ?? 0;
    return {
      extraLocation: { currentTime },
      label: "Video: " + videoSrc,
    };
  });

  return <video ref={videoElementRef} src={videoSrc} />;
}

Control where the annotation pin is rendered

Optional

You can control the exact annotation pin position on the page through the useCordAnnotationRenderer hook. The function you provide will be called by Cord code any time the pin positions on the page are refreshed. The function receives the Annotation object as argument, and you can return either absolute document coordinates, or relative to an element.

import {
  useCordAnnotationTargetRef,
  useCordAnnotationCaptureHandler,
  useCordAnnotationRenderer,
} from "@cord-sdk/react";

type VideoPlayerAnnotation = {
  page: string;
  elenent: string;
  src: string;
  currentTime: number;
};

function VideoPlayerComponent({ videoSrc }) {
  const location = { page: "dashboard", element: "video", src: videoSrc };

  const videoElementRef = useCordAnnotationTargetRef(location);

  useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
    const currentTime = videoElementRef.current?.currentTime ?? 0;
    return {
      extraLocation: { currentTime },
      label: "Video: " + videoSrc,
    };
  });

  useCordAnnotationRenderer(location, (annotation) => {
    if (!videoElementRef.current) {
      // if the video element is for some reason not rendered yet,
      // don't show the annotation pin
      return null;
    }

    return {
      coordinates: { x: "90%", y: "50%" },
      element: videoElementRef.current,
    };
  });

  return <video ref={videoElementRef} src={videoSrc} />;
}

Put your app into the right state to render the annotation

Optional

When the user clicks an annotation in the sidebar, you might need to put your app in the right state to show that annotation properly, for example skipping the video to the annotated timestamp, or fetching data for a chart within a specific date interval.

Through the useCordAnnotationClickHandler hook you can provide a callback that will be called with the Annotation object when the user clicks on the annotation pill in the message. You’re not expected to return anything from the callback, it’s just an event.

import {
  useCordAnnotationTargetRef,
  useCordAnnotationCaptureHandler,
  useCordAnnotationRenderer,
  useCordAnnotationClickHandler,
} from "@cord-sdk/react";

type VideoPlayerAnnotation = {
  page: string;
  elenent: string;
  src: string;
  currentTime: number;
};

function VideoPlayerComponent({ videoSrc }) {
  const location = { page: "dashboard", element: "video", src: videoSrc };

  const videoElementRef = useCordAnnotationTargetRef(location);

  useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
    const currentTime = videoElementRef.current?.currentTime ?? 0;
    return {
      extraLocation: { currentTime },
      label: "Video: " + videoSrc,
    };
  });

  useCordAnnotationRenderer(location, (annotation) => {
    if (!videoElementRef.current) {
      // if the video element is for some reason not rendered yet,
      // don't show the annotation pin
      return null;
    }

    return {
      coordinates: { x: "90%", y: "50%" },
      element: videoElementRef.current,
    };
  });

  useCordAnnotationClickHandler<VideoPlayerAnnotation>(
    location,
    (annotation) => {
      if (videoElementRef.current) {
        // skip the video to the annotation timestamp and pause
        videoElementRef.current.currentTime = annotation.location.currentTime;
        videoElementRef.current.pause();
      }
    }
  );

  return <video ref={videoElementRef} src={videoSrc} />;
}

In this section

Sample Code

Reference