File size: 2,122 Bytes
f56a29b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import type { PPTElement } from '@/lib/types/slides';

/**
 * Extract the semantic payload for each element type.
 * Used by elementFingerprint to detect content-only changes
 * (same id/position but different text, chart data, media src, etc.).
 */
function semanticPart(e: PPTElement): unknown {
  switch (e.type) {
    case 'text':
      return { content: e.content };
    case 'image':
      return { src: e.src };
    case 'shape':
      return {
        path: e.path,
        fill: e.fill,
        text: e.text?.content ?? '',
        gradient: e.gradient ?? null,
        pattern: e.pattern ?? null,
      };
    case 'line':
      return {
        start: e.start,
        end: e.end,
        color: e.color,
        style: e.style,
        points: e.points,
      };
    case 'chart':
      return {
        chartType: e.chartType,
        data: e.data,
        themeColors: e.themeColors,
      };
    case 'table':
      return {
        data: e.data.map((row) => row.map((c) => c.text)),
        colWidths: e.colWidths,
        theme: e.theme ?? null,
      };
    case 'latex':
      return { latex: e.latex };
    case 'video':
      return { src: e.src, poster: e.poster ?? '' };
    case 'audio':
      return { src: e.src };
    case 'code':
      return { language: e.language, lines: e.lines, fileName: e.fileName ?? '' };
    default: {
      const exhaustiveCheck: never = e;
      return exhaustiveCheck;
    }
  }
}

/**
 * Generate a fingerprint string for a list of whiteboard elements.
 * Used for change detection and deduplication in history snapshots.
 *
 * Covers both geometry (id, position, size) AND semantic content
 * via structured JSON.stringify — avoids delimiter-collision issues
 * that hand-concatenated strings would have with rich-text HTML content.
 */
export function elementFingerprint(els: PPTElement[]): string {
  return JSON.stringify(
    els.map((e) => ({
      id: e.id,
      left: e.left ?? 0,
      top: e.top ?? 0,
      width: 'width' in e ? e.width : 0,
      height: 'height' in e && e.height != null ? e.height : 0,
      sem: semanticPart(e),
    })),
  );
}