All files / memory-monitor/src/components/Snapshots SnapshotList.tsx

36.84% Statements 7/19
20% Branches 4/20
25% Functions 2/8
41.17% Lines 7/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137                                                          1x                                                           1x             1x                       1x                 1x 1x                                                                                             1x  
import React, { useCallback } from "react";
import clsx from "clsx";
import { SnapshotCard } from "./SnapshotCard";
import type { PanelSnapshot } from "../../types";
import styles from "./SnapshotList.module.scss";
 
export interface SnapshotListProps {
  /** List of snapshots */
  snapshots: PanelSnapshot[];
  /** Currently selected snapshot ID (most recently selected = current) */
  selectedId?: string;
  /** Compare snapshot ID (first selected = baseline) */
  compareId?: string;
  /** Callback when a snapshot is selected */
  onSelect?: (snapshot: PanelSnapshot) => void;
  /** Callback when a snapshot is deleted */
  onDelete?: (id: string) => void;
  /** Maximum snapshots allowed */
  maxSnapshots?: number;
  /** Compact display mode */
  compact?: boolean;
  /** Custom class name */
  className?: string;
}
 
/**
 * Camera icon for empty state
 */
function CameraIcon({ className }: { className?: string }) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      className={className}
    >
      <path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" />
      <circle cx="12" cy="13" r="3" />
    </svg>
  );
}
 
/**
 * Snapshot list component displaying all captured snapshots
 */
export function SnapshotList({
  snapshots,
  selectedId,
  compareId,
  onSelect,
  onDelete,
  maxSnapshots = 10,
  compact = false,
  className,
}: SnapshotListProps) {
  const handleSelect = useCallback(
    (snapshot: PanelSnapshot) => {
      onSelect?.(snapshot);
    },
    [onSelect]
  );
 
  const handleDelete = useCallback(
    (id: string) => {
      onDelete?.(id);
    },
    [onDelete]
  );
 
  /**
   * Determine the role of a snapshot in comparison
   * - baseline: the compare snapshot (first selected, older reference)
   * - current: the selected snapshot (second selected, newer to compare against baseline)
   */
  const getSelectionRole = (snapshotId: string): "baseline" | "current" | null => {
    if (selectedId && compareId) {
      if (snapshotId === compareId) return "baseline";
      if (snapshotId === selectedId) return "current";
    }
    return null;
  };
 
  // Empty state
  Eif (snapshots.length === 0) {
    return (
      <div className={clsx(styles.emptyState, className)}>
        <div className={styles.emptyIconWrapper}>
          <CameraIcon className={styles.emptyIcon} />
        </div>
        <h4 className={styles.emptyTitle}>
          No Snapshots Yet
        </h4>
        <p className={styles.emptyDescription}>
          Take a snapshot to capture the current memory state for comparison
        </p>
      </div>
    );
  }
 
  return (
    <div className={clsx(styles.container, className)}>
      {/* Header with count */}
      <div className={styles.header}>
        <span className={styles.count}>
          Snapshots ({snapshots.length}/{maxSnapshots})
        </span>
        {snapshots.length >= maxSnapshots && (
          <span className={styles.maxReached}>
            Max reached
          </span>
        )}
      </div>
 
      {/* Snapshot list */}
      <div className={clsx(styles.list, compact && styles.listCompact)}>
        {snapshots.map((snapshot) => (
          <SnapshotCard
            key={snapshot.id}
            snapshot={snapshot}
            selected={selectedId === snapshot.id || compareId === snapshot.id}
            selectionRole={getSelectionRole(snapshot.id)}
            onClick={() => handleSelect(snapshot)}
            onDelete={onDelete ? () => handleDelete(snapshot.id) : undefined}
            compact={compact}
          />
        ))}
      </div>
    </div>
  );
}
 
SnapshotList.displayName = "SnapshotList";