element-fingerprint.ts 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import type { PPTElement } from '@/lib/types/slides';
  2. /**
  3. * Extract the semantic payload for each element type.
  4. * Used by elementFingerprint to detect content-only changes
  5. * (same id/position but different text, chart data, media src, etc.).
  6. */
  7. function semanticPart(e: PPTElement): unknown {
  8. switch (e.type) {
  9. case 'text':
  10. return { content: e.content };
  11. case 'image':
  12. return { src: e.src };
  13. case 'shape':
  14. return {
  15. path: e.path,
  16. fill: e.fill,
  17. text: e.text?.content ?? '',
  18. gradient: e.gradient ?? null,
  19. pattern: e.pattern ?? null,
  20. };
  21. case 'line':
  22. return {
  23. start: e.start,
  24. end: e.end,
  25. color: e.color,
  26. style: e.style,
  27. points: e.points,
  28. };
  29. case 'chart':
  30. return {
  31. chartType: e.chartType,
  32. data: e.data,
  33. themeColors: e.themeColors,
  34. };
  35. case 'table':
  36. return {
  37. data: e.data.map((row) => row.map((c) => c.text)),
  38. colWidths: e.colWidths,
  39. theme: e.theme ?? null,
  40. };
  41. case 'latex':
  42. return { latex: e.latex };
  43. case 'video':
  44. return { src: e.src, poster: e.poster ?? '' };
  45. case 'audio':
  46. return { src: e.src };
  47. default: {
  48. const exhaustiveCheck: never = e;
  49. return exhaustiveCheck;
  50. }
  51. }
  52. }
  53. /**
  54. * Generate a fingerprint string for a list of whiteboard elements.
  55. * Used for change detection and deduplication in history snapshots.
  56. *
  57. * Covers both geometry (id, position, size) AND semantic content
  58. * via structured JSON.stringify — avoids delimiter-collision issues
  59. * that hand-concatenated strings would have with rich-text HTML content.
  60. */
  61. export function elementFingerprint(els: PPTElement[]): string {
  62. return JSON.stringify(
  63. els.map((e) => ({
  64. id: e.id,
  65. left: e.left ?? 0,
  66. top: e.top ?? 0,
  67. width: 'width' in e ? e.width : 0,
  68. height: 'height' in e && e.height != null ? e.height : 0,
  69. sem: semanticPart(e),
  70. })),
  71. );
  72. }