Skip to content

Building an Annotation List ​

Learn how to build a list component to display and manage annotations using the React context.

Overview ​

An annotation list displays all annotations and allows users to select, filter, and manage them. Use the useAnnotationsContext hook to access annotation data and manage selection.

Basic Annotation List Example ​

tsx
import React from "react";
import { useAnnotationsContext } from "@linkurious/ogma-annotations-react";

function AnnotationList() {
  const { editor } = useAnnotationsContext();
  const [annotations, setAnnotations] = React.useState<any[]>([]);
  const [selected, setSelected] = React.useState<Set<string>>(new Set());

  React.useEffect(() => {
    const updateList = () => {
      const all = editor.getAnnotations();
      setAnnotations(all.features);

      const selectedAnnotations = editor.getSelectedAnnotations();
      setSelected(new Set(selectedAnnotations.features.map((f) => f.id)));
    };

    // Update on any annotation change
    editor.on("add", updateList);
    editor.on("remove", updateList);
    editor.on("update", updateList);
    editor.on("select", updateList);
    editor.on("unselect", updateList);

    updateList();

    return () => {
      editor.off("add", updateList);
      editor.off("remove", updateList);
      editor.off("update", updateList);
      editor.off("select", updateList);
      editor.off("unselect", updateList);
    };
  }, [editor]);

  const handleSelect = React.useCallback(
    (id: string) => {
      editor.select(id);
    },
    [editor]
  );

  const handleDelete = React.useCallback(
    (id: string) => {
      editor.remove(id);
    },
    [editor]
  );

  return (
    <div className="annotation-list">
      <h3>Annotations ({annotations.length})</h3>
      <ul>
        {annotations.map((annotation) => (
          <li
            key={annotation.id}
            className={selected.has(annotation.id) ? "selected" : ""}
            onClick={() => handleSelect(annotation.id)}
          >
            <span className="type">{annotation.properties.type}</span>
            {annotation.properties.type === "text" && (
              <span className="content">
                {annotation.properties.content.substring(0, 30)}
              </span>
            )}
            <button
              onClick={(e) => {
                e.stopPropagation();
                handleDelete(annotation.id);
              }}
            >
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default AnnotationList;

Filterable Annotation List ​

Add filtering by annotation type:

tsx
import React from "react";
import { useAnnotationsContext } from "@linkurious/ogma-annotations-react";
import type { AnnotationType } from "@linkurious/ogma-annotations";

function AnnotationList() {
  const { editor } = useAnnotationsContext();
  const [annotations, setAnnotations] = React.useState<any[]>([]);
  const [filter, setFilter] = React.useState<AnnotationType | "all">("all");

  React.useEffect(() => {
    const updateList = () => {
      const all = editor.getAnnotations();
      setAnnotations(all.features);
    };

    editor.on("add", updateList);
    editor.on("remove", updateList);
    updateList();

    return () => {
      editor.off("add", updateList);
      editor.off("remove", updateList);
    };
  }, [editor]);

  const filteredAnnotations = React.useMemo(() => {
    if (filter === "all") return annotations;
    return annotations.filter((a) => a.properties.type === filter);
  }, [annotations, filter]);

  const counts = React.useMemo(() => {
    return {
      all: annotations.length,
      text: annotations.filter((a) => a.properties.type === "text").length,
      arrow: annotations.filter((a) => a.properties.type === "arrow").length,
      box: annotations.filter((a) => a.properties.type === "box").length,
      polygon: annotations.filter((a) => a.properties.type === "polygon").length,
      comment: annotations.filter((a) => a.properties.type === "comment").length
    };
  }, [annotations]);

  return (
    <div className="annotation-list">
      <div className="filters">
        <button
          className={filter === "all" ? "active" : ""}
          onClick={() => setFilter("all")}
        >
          All ({counts.all})
        </button>
        <button
          className={filter === "text" ? "active" : ""}
          onClick={() => setFilter("text")}
        >
          Text ({counts.text})
        </button>
        <button
          className={filter === "arrow" ? "active" : ""}
          onClick={() => setFilter("arrow")}
        >
          Arrows ({counts.arrow})
        </button>
        <button
          className={filter === "box" ? "active" : ""}
          onClick={() => setFilter("box")}
        >
          Boxes ({counts.box})
        </button>
      </div>

      <ul>
        {filteredAnnotations.map((annotation) => (
          <li key={annotation.id} onClick={() => editor.select(annotation.id)}>
            {annotation.properties.type}: {annotation.id}
          </li>
        ))}
      </ul>
    </div>
  );
}

List with Bulk Actions ​

Add bulk delete and select all functionality:

tsx
import React from "react";
import { useAnnotationsContext } from "@linkurious/ogma-annotations-react";

function AnnotationList() {
  const { editor } = useAnnotationsContext();
  const [annotations, setAnnotations] = React.useState<any[]>([]);

  React.useEffect(() => {
    const updateList = () => {
      const all = editor.getAnnotations();
      setAnnotations(all.features);
    };

    editor.on("add", updateList);
    editor.on("remove", updateList);
    updateList();

    return () => {
      editor.off("add", updateList);
      editor.off("remove", updateList);
    };
  }, [editor]);

  const selectAll = React.useCallback(() => {
    const ids = annotations.map((a) => a.id);
    editor.select(ids);
  }, [editor, annotations]);

  const unselectAll = React.useCallback(() => {
    editor.unselect();
  }, [editor]);

  const deleteAll = React.useCallback(() => {
    if (confirm(`Delete all ${annotations.length} annotations?`)) {
      const all = editor.getAnnotations();
      editor.remove(all);
      editor.clearHistory();
    }
  }, [editor, annotations]);

  const deleteSelected = React.useCallback(() => {
    const selected = editor.getSelectedAnnotations();
    if (selected.features.length > 0) {
      editor.remove(selected);
    }
  }, [editor]);

  return (
    <div className="annotation-list">
      <div className="bulk-actions">
        <button onClick={selectAll}>Select All</button>
        <button onClick={unselectAll}>Unselect All</button>
        <button onClick={deleteSelected}>Delete Selected</button>
        <button onClick={deleteAll} className="danger">
          Delete All
        </button>
      </div>

      <ul>
        {annotations.map((annotation) => (
          <li key={annotation.id}>
            {annotation.properties.type}
          </li>
        ))}
      </ul>
    </div>
  );
}

See Also ​