@gemx-dev/heatmap-react 3.5.23 → 3.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/package.json +14 -11
  2. package/dist/esm/components/HeatmapLayout/ContentHeader.d.ts +0 -4
  3. package/dist/esm/components/HeatmapLayout/ContentHeader.d.ts.map +0 -1
  4. package/dist/esm/components/HeatmapLayout/HeatmapLayout.d.ts +0 -7
  5. package/dist/esm/components/HeatmapLayout/HeatmapLayout.d.ts.map +0 -1
  6. package/dist/esm/components/HeatmapLayout/LeftSidebar.d.ts +0 -4
  7. package/dist/esm/components/HeatmapLayout/LeftSidebar.d.ts.map +0 -1
  8. package/dist/esm/components/HeatmapLayout/ReplayControls.d.ts +0 -2
  9. package/dist/esm/components/HeatmapLayout/ReplayControls.d.ts.map +0 -1
  10. package/dist/esm/components/HeatmapLayout/VizDomContainer.d.ts +0 -2
  11. package/dist/esm/components/HeatmapLayout/VizDomContainer.d.ts.map +0 -1
  12. package/dist/esm/components/HeatmapLayout/VizDomRenderer.d.ts +0 -6
  13. package/dist/esm/components/HeatmapLayout/VizDomRenderer.d.ts.map +0 -1
  14. package/dist/esm/components/HeatmapLayout/WrapperLayout.d.ts +0 -7
  15. package/dist/esm/components/HeatmapLayout/WrapperLayout.d.ts.map +0 -1
  16. package/dist/esm/components/HeatmapLayout/WrapperPreview.d.ts +0 -4
  17. package/dist/esm/components/HeatmapLayout/WrapperPreview.d.ts.map +0 -1
  18. package/dist/esm/components/HeatmapLayout/index.d.ts +0 -2
  19. package/dist/esm/components/HeatmapLayout/index.d.ts.map +0 -1
  20. package/dist/esm/components/VizElement/ClarityVisualizer.d.ts +0 -150
  21. package/dist/esm/components/VizElement/ClarityVisualizer.d.ts.map +0 -1
  22. package/dist/esm/components/VizElement/HeatmapElementV2.d.ts +0 -45
  23. package/dist/esm/components/VizElement/HeatmapElementV2.d.ts.map +0 -1
  24. package/dist/esm/components/VizElement/PayloadProcessor.d.ts +0 -65
  25. package/dist/esm/components/VizElement/PayloadProcessor.d.ts.map +0 -1
  26. package/dist/esm/components/VizElement/VizElementRank.d.ts +0 -74
  27. package/dist/esm/components/VizElement/VizElementRank.d.ts.map +0 -1
  28. package/dist/esm/components/VizElement/constants.d.ts +0 -5
  29. package/dist/esm/components/VizElement/constants.d.ts.map +0 -1
  30. package/dist/esm/components/VizElement/helpers.d.ts +0 -20
  31. package/dist/esm/components/VizElement/helpers.d.ts.map +0 -1
  32. package/dist/esm/components/VizElement/types.d.ts +0 -13
  33. package/dist/esm/components/VizElement/types.d.ts.map +0 -1
  34. package/dist/esm/hooks/useHeatmapByMode.d.ts +0 -6
  35. package/dist/esm/hooks/useHeatmapByMode.d.ts.map +0 -1
  36. package/dist/esm/hooks/useHeatmapRender.d.ts +0 -6
  37. package/dist/esm/hooks/useHeatmapRender.d.ts.map +0 -1
  38. package/dist/esm/hooks/useHeatmapScale.d.ts +0 -28
  39. package/dist/esm/hooks/useHeatmapScale.d.ts.map +0 -1
  40. package/dist/esm/hooks/useReplayRender.d.ts +0 -9
  41. package/dist/esm/hooks/useReplayRender.d.ts.map +0 -1
  42. package/dist/esm/types/index.d.ts +0 -3
  43. package/dist/esm/types/index.d.ts.map +0 -1
  44. package/dist/umd/components/VizElement/VizElementRank.d.ts +0 -75
  45. package/dist/umd/components/VizElement/VizElementRank.d.ts.map +0 -1
  46. package/src/components/GraphView.tsx +0 -58
  47. package/src/components/Layout/ContentHeader.tsx +0 -15
  48. package/src/components/Layout/HeatmapLayout.tsx +0 -63
  49. package/src/components/Layout/LeftSidebar.tsx +0 -32
  50. package/src/components/Layout/WrapperLayout.tsx +0 -18
  51. package/src/components/Layout/WrapperPreview.tsx +0 -14
  52. package/src/components/Layout/index.ts +0 -1
  53. package/src/components/Test.tsx +0 -1106
  54. package/src/components/VizDom/ReplayControls.tsx +0 -48
  55. package/src/components/VizDom/VizDomContainer.tsx +0 -25
  56. package/src/components/VizDom/VizDomRenderer.tsx +0 -87
  57. package/src/components/VizDom/index.ts +0 -1
  58. package/src/components/VizElement/ClickedElementOverlay.tsx +0 -65
  59. package/src/components/VizElement/DefaultRankBadges.tsx +0 -33
  60. package/src/components/VizElement/ElementCallout.tsx +0 -337
  61. package/src/components/VizElement/HeatmapElements.tsx +0 -147
  62. package/src/components/VizElement/HoveredElementOverlay.tsx +0 -46
  63. package/src/components/VizElement/MissingElementMessage.tsx +0 -32
  64. package/src/components/VizElement/RankBadge.tsx +0 -25
  65. package/src/components/VizElement/VizElements.tsx +0 -80
  66. package/src/components/VizElement/index.ts +0 -1
  67. package/src/components/VizElement/temp/ClarityVisualizer.ts +0 -764
  68. package/src/components/VizElement/temp/VizElementRank.tsx +0 -579
  69. package/src/components/index.tsx +0 -4
  70. package/src/configs/iframe.ts +0 -15
  71. package/src/configs/index.ts +0 -2
  72. package/src/configs/style.ts +0 -9
  73. package/src/constants/index.ts +0 -4
  74. package/src/dist/components/GraphView.d.ts +0 -9
  75. package/src/dist/components/GraphView.d.ts.map +0 -1
  76. package/src/dist/components/Layout/ContentHeader.d.ts +0 -4
  77. package/src/dist/components/Layout/ContentHeader.d.ts.map +0 -1
  78. package/src/dist/components/Layout/HeatmapLayout.d.ts +0 -11
  79. package/src/dist/components/Layout/HeatmapLayout.d.ts.map +0 -1
  80. package/src/dist/components/Layout/LeftSidebar.d.ts +0 -4
  81. package/src/dist/components/Layout/LeftSidebar.d.ts.map +0 -1
  82. package/src/dist/components/Layout/WrapperLayout.d.ts +0 -8
  83. package/src/dist/components/Layout/WrapperLayout.d.ts.map +0 -1
  84. package/src/dist/components/Layout/WrapperPreview.d.ts +0 -4
  85. package/src/dist/components/Layout/WrapperPreview.d.ts.map +0 -1
  86. package/src/dist/components/Layout/index.d.ts +0 -2
  87. package/src/dist/components/Layout/index.d.ts.map +0 -1
  88. package/src/dist/components/Test.d.ts +0 -121
  89. package/src/dist/components/Test.d.ts.map +0 -1
  90. package/src/dist/components/VizDom/ReplayControls.d.ts +0 -2
  91. package/src/dist/components/VizDom/ReplayControls.d.ts.map +0 -1
  92. package/src/dist/components/VizDom/VizDomContainer.d.ts +0 -2
  93. package/src/dist/components/VizDom/VizDomContainer.d.ts.map +0 -1
  94. package/src/dist/components/VizDom/VizDomRenderer.d.ts +0 -6
  95. package/src/dist/components/VizDom/VizDomRenderer.d.ts.map +0 -1
  96. package/src/dist/components/VizDom/index.d.ts +0 -2
  97. package/src/dist/components/VizDom/index.d.ts.map +0 -1
  98. package/src/dist/components/VizElement/ClickedElementOverlay.d.ts +0 -17
  99. package/src/dist/components/VizElement/ClickedElementOverlay.d.ts.map +0 -1
  100. package/src/dist/components/VizElement/DefaultRankBadges.d.ts +0 -11
  101. package/src/dist/components/VizElement/DefaultRankBadges.d.ts.map +0 -1
  102. package/src/dist/components/VizElement/ElementCallout.d.ts +0 -17
  103. package/src/dist/components/VizElement/ElementCallout.d.ts.map +0 -1
  104. package/src/dist/components/VizElement/HeatmapElements.d.ts +0 -23
  105. package/src/dist/components/VizElement/HeatmapElements.d.ts.map +0 -1
  106. package/src/dist/components/VizElement/HoveredElementOverlay.d.ts +0 -12
  107. package/src/dist/components/VizElement/HoveredElementOverlay.d.ts.map +0 -1
  108. package/src/dist/components/VizElement/MissingElementMessage.d.ts +0 -7
  109. package/src/dist/components/VizElement/MissingElementMessage.d.ts.map +0 -1
  110. package/src/dist/components/VizElement/RankBadge.d.ts +0 -10
  111. package/src/dist/components/VizElement/RankBadge.d.ts.map +0 -1
  112. package/src/dist/components/VizElement/VizElements.d.ts +0 -10
  113. package/src/dist/components/VizElement/VizElements.d.ts.map +0 -1
  114. package/src/dist/components/VizElement/index.d.ts +0 -2
  115. package/src/dist/components/VizElement/index.d.ts.map +0 -1
  116. package/src/dist/components/VizElement/temp/ClarityVisualizer.d.ts +0 -150
  117. package/src/dist/components/VizElement/temp/ClarityVisualizer.d.ts.map +0 -1
  118. package/src/dist/components/VizElement/temp/VizElementRank.d.ts +0 -74
  119. package/src/dist/components/VizElement/temp/VizElementRank.d.ts.map +0 -1
  120. package/src/dist/components/index.d.ts +0 -4
  121. package/src/dist/components/index.d.ts.map +0 -1
  122. package/src/dist/configs/iframe.d.ts +0 -10
  123. package/src/dist/configs/iframe.d.ts.map +0 -1
  124. package/src/dist/configs/index.d.ts +0 -3
  125. package/src/dist/configs/index.d.ts.map +0 -1
  126. package/src/dist/configs/style.d.ts +0 -9
  127. package/src/dist/configs/style.d.ts.map +0 -1
  128. package/src/dist/constants/index.d.ts +0 -5
  129. package/src/dist/constants/index.d.ts.map +0 -1
  130. package/src/dist/helpers/iframe.d.ts +0 -3
  131. package/src/dist/helpers/iframe.d.ts.map +0 -1
  132. package/src/dist/helpers/index.d.ts +0 -2
  133. package/src/dist/helpers/index.d.ts.map +0 -1
  134. package/src/dist/helpers/viz-elements.d.ts +0 -10
  135. package/src/dist/helpers/viz-elements.d.ts.map +0 -1
  136. package/src/dist/hooks/index.d.ts +0 -4
  137. package/src/dist/hooks/index.d.ts.map +0 -1
  138. package/src/dist/hooks/vix-elements/index.d.ts +0 -5
  139. package/src/dist/hooks/vix-elements/index.d.ts.map +0 -1
  140. package/src/dist/hooks/vix-elements/useClickedElement.d.ts +0 -14
  141. package/src/dist/hooks/vix-elements/useClickedElement.d.ts.map +0 -1
  142. package/src/dist/hooks/vix-elements/useHeatmapEffects.d.ts +0 -8
  143. package/src/dist/hooks/vix-elements/useHeatmapEffects.d.ts.map +0 -1
  144. package/src/dist/hooks/vix-elements/useHeatmapElementPosition.d.ts +0 -13
  145. package/src/dist/hooks/vix-elements/useHeatmapElementPosition.d.ts.map +0 -1
  146. package/src/dist/hooks/vix-elements/useHoveredElement.d.ts +0 -17
  147. package/src/dist/hooks/vix-elements/useHoveredElement.d.ts.map +0 -1
  148. package/src/dist/hooks/viz-render/index.d.ts +0 -2
  149. package/src/dist/hooks/viz-render/index.d.ts.map +0 -1
  150. package/src/dist/hooks/viz-render/useHeatmapRender.d.ts +0 -8
  151. package/src/dist/hooks/viz-render/useHeatmapRender.d.ts.map +0 -1
  152. package/src/dist/hooks/viz-render/useHeatmapVizRender.d.ts +0 -8
  153. package/src/dist/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
  154. package/src/dist/hooks/viz-render/useReplayRender.d.ts +0 -11
  155. package/src/dist/hooks/viz-render/useReplayRender.d.ts.map +0 -1
  156. package/src/dist/hooks/viz-scale/index.d.ts +0 -2
  157. package/src/dist/hooks/viz-scale/index.d.ts.map +0 -1
  158. package/src/dist/hooks/viz-scale/useContainerDimensions.d.ts +0 -10
  159. package/src/dist/hooks/viz-scale/useContainerDimensions.d.ts.map +0 -1
  160. package/src/dist/hooks/viz-scale/useContentDimensions.d.ts +0 -11
  161. package/src/dist/hooks/viz-scale/useContentDimensions.d.ts.map +0 -1
  162. package/src/dist/hooks/viz-scale/useHeatmapScale.d.ts +0 -19
  163. package/src/dist/hooks/viz-scale/useHeatmapScale.d.ts.map +0 -1
  164. package/src/dist/hooks/viz-scale/useIframeHeight.d.ts +0 -10
  165. package/src/dist/hooks/viz-scale/useIframeHeight.d.ts.map +0 -1
  166. package/src/dist/hooks/viz-scale/useScaleCalculation.d.ts +0 -10
  167. package/src/dist/hooks/viz-scale/useScaleCalculation.d.ts.map +0 -1
  168. package/src/dist/hooks/viz-scale/useScrollSync.d.ts +0 -10
  169. package/src/dist/hooks/viz-scale/useScrollSync.d.ts.map +0 -1
  170. package/src/dist/index.d.ts +0 -4
  171. package/src/dist/index.d.ts.map +0 -1
  172. package/src/dist/stores/data.d.ts +0 -18
  173. package/src/dist/stores/data.d.ts.map +0 -1
  174. package/src/dist/stores/index.d.ts +0 -2
  175. package/src/dist/stores/index.d.ts.map +0 -1
  176. package/src/dist/ui/BoxStack/BoxStack.d.ts +0 -56
  177. package/src/dist/ui/BoxStack/BoxStack.d.ts.map +0 -1
  178. package/src/dist/ui/BoxStack/index.d.ts +0 -2
  179. package/src/dist/ui/BoxStack/index.d.ts.map +0 -1
  180. package/src/dist/ui/index.d.ts +0 -2
  181. package/src/dist/ui/index.d.ts.map +0 -1
  182. package/src/dist/utils/device.d.ts +0 -2
  183. package/src/dist/utils/device.d.ts.map +0 -1
  184. package/src/dist/utils/retry.d.ts +0 -2
  185. package/src/dist/utils/retry.d.ts.map +0 -1
  186. package/src/dist/utils/sort.d.ts +0 -3
  187. package/src/dist/utils/sort.d.ts.map +0 -1
  188. package/src/global.d.ts +0 -5
  189. package/src/helpers/iframe.ts +0 -33
  190. package/src/helpers/index.ts +0 -1
  191. package/src/helpers/viz-elements.ts +0 -34
  192. package/src/hooks/index.ts +0 -3
  193. package/src/hooks/vix-elements/index.ts +0 -4
  194. package/src/hooks/vix-elements/useClickedElement.ts +0 -62
  195. package/src/hooks/vix-elements/useHeatmapEffects.ts +0 -29
  196. package/src/hooks/vix-elements/useHeatmapElementPosition.ts +0 -66
  197. package/src/hooks/vix-elements/useHoveredElement.ts +0 -135
  198. package/src/hooks/viz-render/index.ts +0 -1
  199. package/src/hooks/viz-render/useHeatmapRender.ts +0 -98
  200. package/src/hooks/viz-render/useHeatmapVizRender.ts +0 -22
  201. package/src/hooks/viz-render/useReplayRender.ts +0 -162
  202. package/src/hooks/viz-scale/index.ts +0 -1
  203. package/src/hooks/viz-scale/useContainerDimensions.ts +0 -46
  204. package/src/hooks/viz-scale/useContentDimensions.ts +0 -29
  205. package/src/hooks/viz-scale/useHeatmapScale.ts +0 -53
  206. package/src/hooks/viz-scale/useIframeHeight.ts +0 -119
  207. package/src/hooks/viz-scale/useScaleCalculation.ts +0 -28
  208. package/src/hooks/viz-scale/useScrollSync.ts +0 -36
  209. package/src/index.ts +0 -5
  210. package/src/stores/data.ts +0 -34
  211. package/src/stores/index.ts +0 -1
  212. package/src/styles/base.css +0 -1
  213. package/src/styles/style.css +0 -30
  214. package/src/types/clarity.d.ts +0 -38
  215. package/src/types/heatmap.d.ts +0 -3
  216. package/src/types/index.d.ts +0 -3
  217. package/src/types/viz-element.d.ts +0 -39
  218. package/src/ui/BoxStack/BoxStack.tsx +0 -120
  219. package/src/ui/BoxStack/index.ts +0 -1
  220. package/src/ui/index.ts +0 -1
  221. package/src/utils/device.ts +0 -7
  222. package/src/utils/retry.ts +0 -20
  223. package/src/utils/sort.ts +0 -5
@@ -1,1106 +0,0 @@
1
- import React, { Component, RefObject, useCallback, useEffect, useRef, useState } from 'react';
2
-
3
- // ===== Type Definitions =====
4
-
5
- interface IHeatmapConfig {
6
- attentionMapInfo?: any[];
7
- height?: number;
8
- width?: number;
9
- }
10
-
11
- interface ImpressionData {
12
- documentHeight?: number;
13
- device?: string;
14
- url?: string;
15
- sessionId?: string;
16
- userId?: string;
17
- pageNum?: string;
18
- timestamp?: number;
19
- duration?: number;
20
- playbackTokenA?: string;
21
- playbackTokenB?: string;
22
- }
23
-
24
- interface TimelineItem {
25
- key: string;
26
- pageNum?: string;
27
- impressionData?: ImpressionData;
28
- isDead?: boolean;
29
- isMissing?: boolean;
30
- failedToFetch?: boolean;
31
- }
32
-
33
- interface MergedPayload {
34
- events: any[];
35
- }
36
-
37
- interface ActiveDuration {
38
- startTime: number;
39
- duration: number;
40
- active: boolean;
41
- }
42
-
43
- enum PanelContent {
44
- Sessions = 'Sessions',
45
- Timeline = 'Timeline',
46
- Area = 'Area',
47
- Click = 'Click',
48
- Scroll = 'Scroll',
49
- Attention = 'Attention',
50
- }
51
-
52
- enum ErrorType {
53
- NoResults = 'NoResults',
54
- NoClicks = 'NoClicks',
55
- NoScroll = 'NoScroll',
56
- ServerError = 'ServerError',
57
- DataError = 'DataError',
58
- }
59
-
60
- interface Visualizer {
61
- merge: (data: any[]) => MergedPayload;
62
- }
63
-
64
- interface HeatmapVisualizer {
65
- attention: (data: any[]) => any;
66
- }
67
-
68
- // ===== Utility Functions =====
69
-
70
- const hideInteractionElements = (iframe: HTMLIFrameElement | null, shouldHide: boolean): void => {
71
- const canvas = iframe?.contentWindow?.document?.getElementById('clarity-interaction-canvas');
72
- const pointer = iframe?.contentWindow?.document?.getElementById('clarity-pointer');
73
- const clicks = iframe?.contentWindow?.document?.getElementsByClassName('clarity-click');
74
-
75
- if (shouldHide) {
76
- if (canvas) canvas.style.display = 'none';
77
- if (pointer) pointer.style.display = 'none';
78
- if (clicks) {
79
- for (let i = 0; i < clicks.length; i++) {
80
- (clicks[i] as HTMLElement).style.display = 'none';
81
- }
82
- }
83
- } else {
84
- if (canvas) canvas.style.display = '';
85
- if (pointer) pointer.style.display = '';
86
- if (clicks) {
87
- for (let i = 0; i < clicks.length; i++) {
88
- (clicks[i] as HTMLElement).style.display = '';
89
- }
90
- }
91
- }
92
- };
93
-
94
- const clearHeatmapCanvas = (iframe: HTMLIFrameElement | null, heatmapVisualizer?: any): void => {
95
- const canvas = iframe?.contentWindow?.document?.getElementById('clarity-heatmap-canvas');
96
- const areaMap = iframe?.contentWindow?.document?.getElementById('clarity-area-map');
97
-
98
- if ((canvas || areaMap) && heatmapVisualizer) {
99
- heatmapVisualizer.clearmap();
100
- }
101
-
102
- if (canvas?.parentNode) {
103
- canvas.parentNode.removeChild(canvas);
104
- }
105
- };
106
-
107
- // ===== Heatmap Renderer Component =====
108
-
109
- interface HeatmapPoint {
110
- x: number;
111
- y: number;
112
- value: number;
113
- timestamp?: number;
114
- }
115
-
116
- interface ClickData {
117
- x: number;
118
- y: number;
119
- count: number;
120
- elementPath?: string;
121
- }
122
-
123
- interface ScrollData {
124
- depth: number;
125
- percentage: number;
126
- users: number;
127
- }
128
-
129
- interface AreaData {
130
- selector: string;
131
- clicks: number;
132
- elementBounds: DOMRect;
133
- }
134
-
135
- interface HeatmapRendererProps {
136
- projectId: string;
137
- isVisible: boolean;
138
- panelContent: PanelContent;
139
- widthScale: number;
140
- height: number;
141
- iframeHeight: number;
142
- heatmapConfig?: IHeatmapConfig;
143
- selectedElement?: any;
144
- heatmapLoading: boolean;
145
- showNoHeatmapModal: boolean;
146
- deviceType?: string;
147
- recordingUrl?: string;
148
- iframeRef: RefObject<HTMLIFrameElement | null>;
149
- heatmapWrapperRef: RefObject<HTMLDivElement | null>;
150
- parentRef: RefObject<HTMLDivElement>;
151
- heatmapSidebarRef?: RefObject<HTMLDivElement>;
152
- selectElement: (element: any) => void;
153
- setIframeHeight: (height: number) => void;
154
- setElementToShow: (element: string) => void;
155
- heatmapVisualizer: HeatmapVisualizer;
156
- areaMapEditKey: string;
157
- attentionAggregatedData?: any;
158
- setPanelContent: (content: PanelContent) => void;
159
- }
160
-
161
- const HeatmapRenderer: React.FC<HeatmapRendererProps> = (props) => {
162
- const canvasRef = useRef<HTMLCanvasElement>(null);
163
- const [heatmapReady, setHeatmapReady] = useState(false);
164
- const [clickPoints, setClickPoints] = useState<ClickData[]>([]);
165
- const [scrollData, setScrollData] = useState<ScrollData[]>([]);
166
- const [areaData, setAreaData] = useState<AreaData[]>([]);
167
-
168
- // Initialize canvas for heatmap rendering
169
- useEffect(() => {
170
- if (!props.isVisible || !canvasRef.current) return;
171
-
172
- const canvas = canvasRef.current;
173
- const ctx = canvas.getContext('2d');
174
-
175
- if (!ctx) return;
176
-
177
- // Set canvas dimensions
178
- canvas.width = props.iframeRef.current?.contentWindow?.innerWidth || 1920;
179
- canvas.height = props.iframeHeight;
180
-
181
- setHeatmapReady(true);
182
- }, [props.isVisible, props.iframeHeight]);
183
-
184
- // Render click heatmap
185
- const renderClickHeatmap = useCallback(() => {
186
- if (!canvasRef.current || !props.heatmapConfig) return;
187
-
188
- const canvas = canvasRef.current;
189
- const ctx = canvas.getContext('2d');
190
- if (!ctx) return;
191
-
192
- // Clear canvas
193
- ctx.clearRect(0, 0, canvas.width, canvas.height);
194
-
195
- // Draw each click point
196
- clickPoints.forEach((click) => {
197
- const gradient = ctx.createRadialGradient(click.x, click.y, 0, click.x, click.y, 40);
198
-
199
- // Color intensity based on click count
200
- const intensity = Math.min(click.count / 10, 1);
201
- gradient.addColorStop(0, `rgba(255, 0, 0, ${intensity * 0.8})`);
202
- gradient.addColorStop(0.5, `rgba(255, 165, 0, ${intensity * 0.4})`);
203
- gradient.addColorStop(1, 'rgba(255, 255, 0, 0)');
204
-
205
- ctx.fillStyle = gradient;
206
- ctx.fillRect(click.x - 40, click.y - 40, 80, 80);
207
- });
208
- }, [clickPoints, props.heatmapConfig]);
209
-
210
- // Render scroll heatmap
211
- const renderScrollHeatmap = useCallback(() => {
212
- if (!canvasRef.current || !props.heatmapConfig) return;
213
-
214
- const canvas = canvasRef.current;
215
- const ctx = canvas.getContext('2d');
216
- if (!ctx) return;
217
-
218
- ctx.clearRect(0, 0, canvas.width, canvas.height);
219
-
220
- // Draw scroll depth gradient
221
- scrollData.forEach((scroll, index) => {
222
- const y = (scroll.depth / 100) * canvas.height;
223
- const nextY =
224
- index < scrollData.length - 1
225
- ? (scrollData[index + 1].depth / 100) * canvas.height
226
- : canvas.height;
227
-
228
- const gradient = ctx.createLinearGradient(0, y, 0, nextY);
229
-
230
- // Color based on engagement
231
- const intensity = scroll.percentage / 100;
232
- const red = Math.floor(255 * (1 - intensity));
233
- const green = Math.floor(255 * intensity);
234
-
235
- gradient.addColorStop(0, `rgba(${red}, ${green}, 0, 0.3)`);
236
- gradient.addColorStop(1, `rgba(${red}, ${green}, 0, 0.3)`);
237
-
238
- ctx.fillStyle = gradient;
239
- ctx.fillRect(0, y, canvas.width, nextY - y);
240
- });
241
- }, [scrollData, props.heatmapConfig]);
242
-
243
- // Render attention heatmap
244
- const renderAttentionHeatmap = useCallback(() => {
245
- if (!canvasRef.current || !props.attentionAggregatedData) return;
246
-
247
- const canvas = canvasRef.current;
248
- const ctx = canvas.getContext('2d');
249
- if (!ctx) return;
250
-
251
- ctx.clearRect(0, 0, canvas.width, canvas.height);
252
-
253
- // Use heatmap.js style rendering
254
- const heatmapPoints = props.attentionAggregatedData.points || [];
255
-
256
- heatmapPoints.forEach((point: HeatmapPoint) => {
257
- const gradient = ctx.createRadialGradient(point.x, point.y, 0, point.x, point.y, 60);
258
-
259
- const intensity = Math.min(point.value / 100, 1);
260
-
261
- // Heat gradient: blue (low) -> green -> yellow -> red (high)
262
- if (intensity < 0.25) {
263
- gradient.addColorStop(0, `rgba(0, 0, 255, ${intensity * 4 * 0.6})`);
264
- gradient.addColorStop(1, 'rgba(0, 0, 255, 0)');
265
- } else if (intensity < 0.5) {
266
- gradient.addColorStop(0, `rgba(0, 255, 0, ${(intensity - 0.25) * 4 * 0.6})`);
267
- gradient.addColorStop(1, 'rgba(0, 255, 0, 0)');
268
- } else if (intensity < 0.75) {
269
- gradient.addColorStop(0, `rgba(255, 255, 0, ${(intensity - 0.5) * 4 * 0.6})`);
270
- gradient.addColorStop(1, 'rgba(255, 255, 0, 0)');
271
- } else {
272
- gradient.addColorStop(0, `rgba(255, 0, 0, ${(intensity - 0.75) * 4 * 0.6})`);
273
- gradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
274
- }
275
-
276
- ctx.fillStyle = gradient;
277
- ctx.fillRect(point.x - 60, point.y - 60, 120, 120);
278
- });
279
- }, [props.attentionAggregatedData]);
280
-
281
- // Render area map (clickable regions)
282
- const renderAreaMap = useCallback(() => {
283
- if (!props.iframeRef.current?.contentDocument) return;
284
-
285
- const doc = props.iframeRef.current.contentDocument;
286
-
287
- // Remove existing area overlays
288
- const existingOverlays = doc.querySelectorAll('.clarity-area-overlay');
289
- existingOverlays.forEach((el) => el.remove());
290
-
291
- // Create overlay for each area
292
- areaData.forEach((area) => {
293
- const overlay = doc.createElement('div');
294
- overlay.className = 'clarity-area-overlay';
295
- overlay.style.cssText = `
296
- position: absolute;
297
- left: ${area.elementBounds.left}px;
298
- top: ${area.elementBounds.top}px;
299
- width: ${area.elementBounds.width}px;
300
- height: ${area.elementBounds.height}px;
301
- background: rgba(0, 123, 255, 0.2);
302
- border: 2px solid rgba(0, 123, 255, 0.6);
303
- pointer-events: auto;
304
- cursor: pointer;
305
- z-index: 10000;
306
- `;
307
-
308
- // Add click count badge
309
- const badge = doc.createElement('div');
310
- badge.className = 'clarity-area-badge';
311
- badge.textContent = area.clicks.toString();
312
- badge.style.cssText = `
313
- position: absolute;
314
- top: -10px;
315
- right: -10px;
316
- background: #007bff;
317
- color: white;
318
- border-radius: 50%;
319
- width: 24px;
320
- height: 24px;
321
- display: flex;
322
- align-items: center;
323
- justify-content: center;
324
- font-size: 12px;
325
- font-weight: bold;
326
- `;
327
- overlay.appendChild(badge);
328
-
329
- overlay.addEventListener('click', () => {
330
- props.selectElement(area);
331
- props.setElementToShow(area.selector);
332
- });
333
-
334
- doc.body.appendChild(overlay);
335
- });
336
- }, [areaData, props]);
337
-
338
- // Process heatmap data based on panel content
339
- useEffect(() => {
340
- if (!props.heatmapConfig || props.heatmapLoading) return;
341
-
342
- switch (props.panelContent) {
343
- case PanelContent.Click:
344
- // Extract click data from heatmapConfig
345
- const clicks = (props.heatmapConfig as any).clicks || [];
346
- setClickPoints(clicks);
347
- break;
348
-
349
- case PanelContent.Scroll:
350
- // Extract scroll data
351
- const scrolls = (props.heatmapConfig as any).scrollDepth || [];
352
- setScrollData(scrolls);
353
- break;
354
-
355
- case PanelContent.Area:
356
- // Extract area data
357
- const areas = (props.heatmapConfig as any).areas || [];
358
- setAreaData(areas);
359
- break;
360
-
361
- case PanelContent.Attention:
362
- // Attention data is already processed via attentionAggregatedData
363
- break;
364
- }
365
- }, [props.heatmapConfig, props.panelContent, props.heatmapLoading]);
366
-
367
- // Render appropriate heatmap based on panel content
368
- useEffect(() => {
369
- if (!heatmapReady || props.heatmapLoading || props.showNoHeatmapModal) return;
370
-
371
- switch (props.panelContent) {
372
- case PanelContent.Click:
373
- renderClickHeatmap();
374
- break;
375
-
376
- case PanelContent.Scroll:
377
- renderScrollHeatmap();
378
- break;
379
-
380
- case PanelContent.Attention:
381
- renderAttentionHeatmap();
382
- break;
383
-
384
- case PanelContent.Area:
385
- renderAreaMap();
386
- break;
387
- }
388
- }, [
389
- heatmapReady,
390
- props.panelContent,
391
- props.heatmapLoading,
392
- props.showNoHeatmapModal,
393
- renderClickHeatmap,
394
- renderScrollHeatmap,
395
- renderAttentionHeatmap,
396
- renderAreaMap,
397
- ]);
398
-
399
- // Handle element selection
400
- const handleCanvasClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
401
- if (!canvasRef.current) return;
402
-
403
- const rect = canvasRef.current.getBoundingClientRect();
404
- const x = e.clientX - rect.left;
405
- const y = e.clientY - rect.top;
406
-
407
- // Find clicked element in heatmap data
408
- const clickedPoint = clickPoints.find(
409
- (point) => Math.sqrt(Math.pow(point.x - x, 2) + Math.pow(point.y - y, 2)) < 40,
410
- );
411
-
412
- if (clickedPoint) {
413
- props.selectElement(clickedPoint);
414
- }
415
- };
416
-
417
- if (!props.isVisible) return null;
418
-
419
- return (
420
- <div className="heatmap-renderer">
421
- {props.heatmapLoading && (
422
- <div className="heatmap-loading">
423
- <div className="spinner">Loading heatmap...</div>
424
- </div>
425
- )}
426
-
427
- {props.showNoHeatmapModal && (
428
- <div className="no-heatmap-modal">
429
- <div className="modal-content">
430
- <h3>No Data Available</h3>
431
- <p>There is no {props.panelContent.toLowerCase()} data for this page.</p>
432
- </div>
433
- </div>
434
- )}
435
-
436
- {props.panelContent !== PanelContent.Area && (
437
- <canvas
438
- ref={canvasRef}
439
- className="clarity-heatmap-canvas"
440
- id="clarity-heatmap-canvas"
441
- onClick={handleCanvasClick}
442
- style={{
443
- position: 'absolute',
444
- top: 0,
445
- left: 0,
446
- width: '100%',
447
- height: '100%',
448
- pointerEvents: 'auto',
449
- zIndex: 9999,
450
- }}
451
- />
452
- )}
453
-
454
- {props.selectedElement && (
455
- <div className="heatmap-element-info">
456
- <h4>Selected Element</h4>
457
- <p>Clicks: {props.selectedElement.count || props.selectedElement.clicks}</p>
458
- {props.selectedElement.elementPath && <p>Path: {props.selectedElement.elementPath}</p>}
459
- </div>
460
- )}
461
- </div>
462
- );
463
- };
464
-
465
- // ===== Snapshot Player Component =====
466
-
467
- interface SnapshotPlayerProps {
468
- isVisible: boolean;
469
- mergedPayload: MergedPayload | null;
470
- time: number;
471
- videoAreaRef: RefObject<HTMLDivElement>;
472
- deviceType?: string;
473
- refOverride: RefObject<HTMLIFrameElement | null>;
474
- impressionHeight?: number;
475
- panelContent: PanelContent;
476
- width: number;
477
- height: number;
478
- iframeStyles: React.CSSProperties;
479
- elementToShow: string;
480
- snapshotStyleClassName?: string;
481
- heatmapConfig?: IHeatmapConfig;
482
- loadingCallback?: (loading: boolean) => void;
483
- snapshotResizeCallback: (width: number, height: number) => void;
484
- visualizer: HeatmapVisualizer;
485
- areaMapEditKey: string;
486
- snapshotLoading?: (loading: boolean) => void;
487
- isDead?: boolean;
488
- }
489
-
490
- const SnapshotPlayer: React.FC<SnapshotPlayerProps> = (props) => {
491
- const [snapshotHtml, setSnapshotHtml] = useState('');
492
- const [isLoading, setIsLoading] = useState(true);
493
-
494
- useEffect(() => {
495
- if (!props.mergedPayload) return;
496
-
497
- // Render snapshot from merged payload
498
- const html = renderSnapshotFromPayload(props.mergedPayload, props.time);
499
- setSnapshotHtml(html);
500
- setIsLoading(false);
501
- props.snapshotLoading?.(false);
502
- }, [props.mergedPayload, props.time]);
503
-
504
- const renderSnapshotFromPayload = (payload: MergedPayload, time: number): string => {
505
- // Find the snapshot at the given time
506
- // This would use the visualizer to render the DOM state
507
- return '<html><body><p>Snapshot rendered here</p></body></html>';
508
- };
509
-
510
- useEffect(() => {
511
- if (!props.refOverride.current) return;
512
-
513
- const iframe = props.refOverride.current;
514
- const doc = iframe.contentDocument;
515
-
516
- if (doc && snapshotHtml) {
517
- doc.open();
518
- doc.write(snapshotHtml);
519
- doc.close();
520
-
521
- // Measure dimensions
522
- setTimeout(() => {
523
- const width = doc.documentElement.scrollWidth;
524
- const height = doc.documentElement.scrollHeight;
525
- props.snapshotResizeCallback(width, height);
526
- }, 100);
527
- }
528
- }, [snapshotHtml, props.refOverride]);
529
-
530
- // Highlight element if needed
531
- useEffect(() => {
532
- if (!props.elementToShow || !props.refOverride.current) return;
533
-
534
- const doc = props.refOverride.current.contentDocument;
535
- if (!doc) return;
536
-
537
- const element = doc.querySelector(props.elementToShow);
538
- if (element) {
539
- element.classList.add('clarity-highlighted');
540
- element.scrollIntoView({ behavior: 'smooth', block: 'center' });
541
- }
542
-
543
- return () => {
544
- if (element) {
545
- element.classList.remove('clarity-highlighted');
546
- }
547
- };
548
- }, [props.elementToShow, props.refOverride]);
549
-
550
- return (
551
- <iframe
552
- ref={props.refOverride}
553
- className={`clarity-snapshot ${props.snapshotStyleClassName || ''}`}
554
- style={{
555
- width: props.width,
556
- height: props.height,
557
- border: 'none',
558
- ...props.iframeStyles,
559
- }}
560
- title="Session Recording"
561
- />
562
- );
563
- };
564
-
565
- // ===== Main Component Props =====
566
-
567
- interface HeatmapWrapperProps {
568
- projectId: string;
569
- isVisible: boolean;
570
- panelContent: PanelContent;
571
- heatmapConfig?: IHeatmapConfig;
572
- impressionHeight?: number;
573
- deviceType?: string;
574
- url?: string;
575
- heatmapLoading?: boolean;
576
- heatmapErrorType?: ErrorType;
577
- videoAreaRef: RefObject<HTMLDivElement>;
578
- parentRef: RefObject<HTMLDivElement>;
579
- heatmapSidebarRef?: RefObject<HTMLDivElement>;
580
- visualizer: Visualizer;
581
- heatmapVisualizer: HeatmapVisualizer;
582
- areaMapEditKey: string;
583
- snapshotStyleClassName?: string;
584
- mergedPayload: MergedPayload | null;
585
- time: number;
586
- loadingCallback?: (loading: boolean) => void;
587
- setPanelContent: (content: PanelContent) => void;
588
- snapshotLoading?: (loading: boolean) => void;
589
- isDead?: boolean;
590
- }
591
-
592
- // ===== Heatmap Wrapper Component =====
593
-
594
- const HeatmapWrapper: React.FC<HeatmapWrapperProps> = (props) => {
595
- const resizeObserverRef = useRef<ResizeObserver>(null);
596
- const wrapperRef = useRef<HTMLDivElement>(null);
597
- const iframeRef = useRef<HTMLIFrameElement>(null);
598
-
599
- // State for dimensions and scaling
600
- const [containerWidth, setContainerWidth] = useState(1);
601
- const [containerHeight, setContainerHeight] = useState(1);
602
- const [scaleRatio, setScaleRatio] = useState(1);
603
- const [widthScale, setWidthScale] = useState(1);
604
- const [heightScale, setHeightScale] = useState(1);
605
- const [finalScale, setFinalScale] = useState(1);
606
-
607
- // State for iframe dimensions
608
- const [iframeWidth, setIframeWidth] = useState(0);
609
- const [iframeHeight, setIframeHeight] = useState(0);
610
- const [contentWidth, setContentWidth] = useState(0);
611
- const [contentHeight, setContentHeight] = useState(0);
612
- const [actualHeight, setActualHeight] = useState(0);
613
-
614
- // Other state
615
- const [selectedElement, setSelectedElement] = useState<any>();
616
- const [elementToShow, setElementToShow] = useState('');
617
- const [showNoHeatmapModal, setShowNoHeatmapModal] = useState(false);
618
- const [attentionData, setAttentionData] = useState<any>();
619
-
620
- const isHeatmapView =
621
- props.panelContent === PanelContent.Attention ||
622
- props.panelContent === PanelContent.Click ||
623
- props.panelContent === PanelContent.Area;
624
-
625
- // Update container dimensions
626
- const updateDimensions = useCallback(() => {
627
- const parent = wrapperRef.current?.parentElement?.parentElement;
628
- if (parent) {
629
- const width = parent.clientWidth;
630
- const height = parent.clientHeight;
631
-
632
- if (iframeWidth !== width) setIframeWidth(width);
633
- if (iframeHeight !== height) setIframeHeight(height);
634
- }
635
- }, [iframeWidth, iframeHeight]);
636
-
637
- // Setup resize listener
638
- useEffect(() => {
639
- updateDimensions();
640
- window.addEventListener('resize', updateDimensions);
641
- return () => window.removeEventListener('resize', updateDimensions);
642
- }, [updateDimensions]);
643
-
644
- // Handle attention data
645
- useEffect(() => {
646
- if (
647
- props.panelContent === PanelContent.Attention &&
648
- props.heatmapConfig?.attentionMapInfo &&
649
- props.heatmapConfig.attentionMapInfo.length > 0
650
- ) {
651
- setAttentionData(props.heatmapVisualizer.attention(props.heatmapConfig.attentionMapInfo));
652
- }
653
- }, [props.heatmapConfig, props.panelContent, props.heatmapVisualizer]);
654
-
655
- // Setup ResizeObserver for iframe content
656
- useEffect(() => {
657
- if (typeof window.ResizeObserver !== 'undefined') {
658
- resizeObserverRef.current = new ResizeObserver((entries) => {
659
- for (const entry of entries) {
660
- if (entry.contentRect) {
661
- // Update height logic here
662
- }
663
- }
664
- });
665
- }
666
-
667
- const iframeBody = iframeRef.current?.contentDocument?.body;
668
- if (resizeObserverRef.current && iframeBody) {
669
- resizeObserverRef.current.observe(iframeBody);
670
- return () => {
671
- if (iframeBody) {
672
- resizeObserverRef.current?.unobserve(iframeBody);
673
- }
674
- };
675
- }
676
- }, [finalScale]);
677
-
678
- // Update actual height from heatmap data
679
- useEffect(() => {
680
- if (props.heatmapConfig?.height) {
681
- setActualHeight(props.heatmapConfig.height);
682
- }
683
- }, [props.heatmapConfig?.width, props.heatmapConfig?.height]);
684
-
685
- // Calculate scales based on dimensions
686
- useEffect(() => {
687
- if (iframeWidth > 0 && contentWidth > 0) {
688
- setWidthScale(Math.min(iframeWidth / contentWidth, 1));
689
- setScaleRatio(Math.min((iframeWidth - 20) / contentWidth, 1));
690
- }
691
- }, [contentWidth, iframeWidth]);
692
-
693
- useEffect(() => {
694
- if (iframeHeight > 0 && contentHeight > 0) {
695
- setHeightScale(Math.min((iframeHeight - 20) / contentHeight, 1));
696
- }
697
- }, [iframeHeight, contentHeight]);
698
-
699
- useEffect(() => {
700
- if (scaleRatio > 0 && heightScale > 0) {
701
- setFinalScale(Math.min(Math.min(scaleRatio, heightScale), 1));
702
- }
703
- }, [scaleRatio, heightScale]);
704
-
705
- // Handle heatmap visibility and error states
706
- useEffect(() => {
707
- if (isHeatmapView) {
708
- if (props.impressionHeight && props.videoAreaRef.current) {
709
- props.videoAreaRef.current.style.height = `${props.impressionHeight * widthScale}px`;
710
- }
711
-
712
- if (props.heatmapLoading || props.heatmapConfig) {
713
- if (props.heatmapErrorType === ErrorType.NoResults) {
714
- setShowNoHeatmapModal(true);
715
- } else if (
716
- props.panelContent === PanelContent.Click ||
717
- props.panelContent === PanelContent.Area
718
- ) {
719
- if (props.heatmapErrorType === ErrorType.NoClicks) {
720
- setShowNoHeatmapModal(true);
721
- } else {
722
- setShowNoHeatmapModal(false);
723
- if (props.parentRef.current) {
724
- props.parentRef.current.style.overflow = '';
725
- }
726
- }
727
- } else if (props.panelContent === PanelContent.Scroll) {
728
- if (props.heatmapErrorType === ErrorType.NoScroll) {
729
- setShowNoHeatmapModal(true);
730
- } else {
731
- setShowNoHeatmapModal(false);
732
- if (props.parentRef.current) {
733
- props.parentRef.current.style.overflow = '';
734
- }
735
- }
736
- }
737
- } else {
738
- if (props.parentRef.current) {
739
- props.parentRef.current.style.overflow = 'hidden';
740
- }
741
- if (props.heatmapErrorType === ErrorType.NoResults) {
742
- setShowNoHeatmapModal(true);
743
- }
744
- }
745
- } else {
746
- setShowNoHeatmapModal(false);
747
- if (props.videoAreaRef.current?.style) {
748
- props.videoAreaRef.current.style.height = '';
749
- }
750
- }
751
- }, [
752
- isHeatmapView,
753
- widthScale,
754
- props.impressionHeight,
755
- props.heatmapConfig,
756
- props.heatmapErrorType,
757
- props.panelContent,
758
- props.heatmapLoading,
759
- ]);
760
-
761
- const handleSnapshotResize = useCallback(
762
- (width: number, height: number) => {
763
- if (width !== contentWidth) setContentWidth(width);
764
- if (height !== contentHeight) setContentHeight(height);
765
- },
766
- [contentWidth, contentHeight],
767
- );
768
-
769
- return (
770
- <div
771
- className={isHeatmapView ? 'heatmap-wrapper' : 'session-wrapper'}
772
- style={
773
- isHeatmapView
774
- ? {
775
- width: contentWidth,
776
- height: props.impressionHeight,
777
- transform: `scale(${widthScale})`,
778
- transformOrigin: 'center',
779
- }
780
- : {
781
- height: contentHeight * finalScale,
782
- maxWidth: contentWidth * finalScale,
783
- }
784
- }
785
- ref={wrapperRef}
786
- >
787
- {/* Cover overlay for certain views */}
788
- {(props.panelContent === PanelContent.Sessions ||
789
- props.panelContent === PanelContent.Timeline) && <div className="cover" />}
790
-
791
- <HeatmapRenderer
792
- projectId={props.projectId}
793
- isVisible={isHeatmapView}
794
- panelContent={props.panelContent}
795
- widthScale={widthScale}
796
- height={iframeHeight / widthScale}
797
- iframeHeight={actualHeight}
798
- heatmapConfig={props.heatmapConfig}
799
- selectedElement={selectedElement}
800
- heatmapLoading={!!props.heatmapLoading}
801
- showNoHeatmapModal={showNoHeatmapModal}
802
- deviceType={props.deviceType}
803
- recordingUrl={props.url}
804
- iframeRef={iframeRef}
805
- heatmapWrapperRef={wrapperRef}
806
- parentRef={props.parentRef}
807
- heatmapSidebarRef={props.heatmapSidebarRef}
808
- selectElement={setSelectedElement}
809
- setIframeHeight={setActualHeight}
810
- setElementToShow={setElementToShow}
811
- heatmapVisualizer={props.heatmapVisualizer}
812
- areaMapEditKey={props.areaMapEditKey}
813
- attentionAggregatedData={attentionData}
814
- setPanelContent={props.setPanelContent}
815
- />
816
-
817
- <SnapshotPlayer
818
- isVisible={props.isVisible}
819
- mergedPayload={props.mergedPayload}
820
- time={props.time}
821
- videoAreaRef={props.videoAreaRef}
822
- deviceType={props.deviceType}
823
- refOverride={iframeRef}
824
- impressionHeight={props.impressionHeight}
825
- panelContent={props.panelContent}
826
- width={contentWidth}
827
- height={isHeatmapView ? iframeHeight / widthScale : contentHeight}
828
- iframeStyles={
829
- isHeatmapView
830
- ? {}
831
- : {
832
- transform: `scale(${finalScale})`,
833
- transformOrigin: '0 0',
834
- }
835
- }
836
- elementToShow={elementToShow}
837
- snapshotStyleClassName={
838
- showNoHeatmapModal ? 'deadSessionSnapshot' : props.snapshotStyleClassName
839
- }
840
- heatmapConfig={props.heatmapConfig}
841
- loadingCallback={props.loadingCallback}
842
- snapshotResizeCallback={handleSnapshotResize}
843
- visualizer={props.heatmapVisualizer}
844
- areaMapEditKey={props.areaMapEditKey}
845
- snapshotLoading={props.snapshotLoading}
846
- isDead={props.isDead}
847
- />
848
- </div>
849
- );
850
- };
851
-
852
- // ===== Impression Player Component (Class Component) =====
853
-
854
- interface ImpressionPlayerProps {
855
- loading: boolean;
856
- projectId: string;
857
- isVisible: boolean;
858
- data?: any[];
859
- pageUrl?: string;
860
- timestamp?: number;
861
- visualizer: Visualizer;
862
- heatmapVisualizer: HeatmapVisualizer;
863
- time: number;
864
- parentRef: RefObject<HTMLDivElement>;
865
- videoAreaRef: RefObject<HTMLDivElement>;
866
- impressionData?: ImpressionData;
867
- panelContent: PanelContent;
868
- heatmapInfo?: IHeatmapConfig;
869
- heatmapLoading?: boolean;
870
- heatmapError?: any;
871
- heatmapErrorType?: ErrorType;
872
- snapshotStyleClassName?: string;
873
- heatmapSidebarRef?: RefObject<HTMLDivElement>;
874
- deviceType?: string;
875
- url?: string;
876
- sessionId: string;
877
- userId: string;
878
- pageNum: string;
879
- areaMapEditKey?: string;
880
- skipInactivity?: boolean;
881
- isPlaying?: boolean;
882
- timeChange: (time: number) => void;
883
- loadingCallback?: (loading: boolean) => void;
884
- latencyTrackerStart?: () => void;
885
- latencyTrackerEnd?: () => void;
886
- timelineInfo?: { timeline: TimelineItem[] };
887
- setFailedToFetchImpression?: (pageNum: number) => void;
888
- error?: any;
889
- errorType?: ErrorType;
890
- isLive?: boolean;
891
- setPanelContent: (content: PanelContent) => void;
892
- isDead?: boolean;
893
- classRef?: RefObject<ImpressionPlayer>;
894
- }
895
-
896
- interface ImpressionPlayerState {
897
- mergedPayload: MergedPayload | null;
898
- activeDurations: ActiveDuration[];
899
- snapshotRendered: boolean;
900
- }
901
-
902
- class ImpressionPlayer extends Component<ImpressionPlayerProps, ImpressionPlayerState> {
903
- private componentName = 'ImpressionPlayer';
904
- private goToEnd = false;
905
- private timeLineUpdateCb?: () => void;
906
-
907
- constructor(props: ImpressionPlayerProps) {
908
- super(props);
909
-
910
- this.state = {
911
- mergedPayload: null,
912
- activeDurations: [],
913
- snapshotRendered: false,
914
- };
915
-
916
- if (props.classRef) {
917
- (props.classRef as any).current = this;
918
- }
919
- }
920
-
921
- componentDidMount(): void {
922
- this.process();
923
- }
924
-
925
- componentDidUpdate(prevProps: ImpressionPlayerProps, prevState: ImpressionPlayerState): void {
926
- this.process(prevProps.data);
927
- this.goToActiveTime();
928
-
929
- if (prevProps.loading || !this.props.loading || this.props.isLive) {
930
- this.setSnapshotLoading(true);
931
- }
932
-
933
- if (prevState.activeDurations !== this.state.activeDurations && this.timeLineUpdateCb) {
934
- this.timeLineUpdateCb();
935
- this.timeLineUpdateCb = undefined;
936
- }
937
-
938
- if (
939
- prevState.snapshotRendered !== this.state.snapshotRendered ||
940
- prevState.mergedPayload?.events?.length !== this.state.mergedPayload?.events?.length
941
- ) {
942
- this.props.loadingCallback?.(!this.state.snapshotRendered || !this.isProcessed());
943
- }
944
-
945
- if (this.props.error) {
946
- this.handleImpressionFetchError();
947
- }
948
- }
949
-
950
- private process = (prevData?: any[]): void => {
951
- if (
952
- this.props.data &&
953
- (!this.state.mergedPayload ||
954
- (Array.isArray(prevData) && this.props.data.length !== prevData?.length))
955
- ) {
956
- this.props.latencyTrackerStart?.();
957
-
958
- let data = this.props.data;
959
- // Filter logic if needed based on pageUrl and timestamp
960
-
961
- const merged = this.props.visualizer.merge(data);
962
-
963
- this.setState({
964
- mergedPayload: merged,
965
- activeDurations: this.calculateActiveDurations(merged.events),
966
- });
967
-
968
- this.props.latencyTrackerEnd?.();
969
- }
970
- };
971
-
972
- private calculateActiveDurations = (events: any[]): ActiveDuration[] => {
973
- // Implementation to calculate active durations from events
974
- return [];
975
- };
976
-
977
- private isProcessed = (): boolean => {
978
- return !!(
979
- this.state.mergedPayload &&
980
- this.state.mergedPayload.events &&
981
- this.state.mergedPayload.events.length > 0
982
- );
983
- };
984
-
985
- private handleImpressionFetchError = (): void => {
986
- if (!this.props.timelineInfo) return;
987
-
988
- this.props.loadingCallback?.(false);
989
-
990
- const currentPage = this.props.timelineInfo.timeline[parseInt(this.props.pageNum)];
991
-
992
- if (currentPage && !currentPage.failedToFetch && this.props.impressionData) {
993
- this.props.setFailedToFetchImpression?.(parseInt(this.props.pageNum));
994
-
995
- const newTime =
996
- this.props.time +
997
- Math.max(
998
- 0.8 * this.props.impressionData.duration!,
999
- this.props.impressionData.duration! - 3000,
1000
- );
1001
-
1002
- this.props.timeChange(newTime);
1003
- }
1004
- };
1005
-
1006
- private getNextActiveTime = (currentTime: number, callback?: () => void): number => {
1007
- if (this.state.activeDurations && this.state.activeDurations.length > 0) {
1008
- for (let i = 0; i < this.state.activeDurations.length; i++) {
1009
- if (currentTime < this.state.activeDurations[i].startTime) {
1010
- if (this.state.activeDurations[i].active) {
1011
- return this.state.activeDurations[i].startTime;
1012
- } else if (i < this.state.activeDurations.length - 1) {
1013
- return this.state.activeDurations[i + 1].startTime;
1014
- } else {
1015
- return this.state.activeDurations[i].startTime + this.state.activeDurations[i].duration;
1016
- }
1017
- }
1018
- }
1019
- this.timeLineUpdateCb = callback;
1020
- }
1021
- return currentTime;
1022
- };
1023
-
1024
- private goToActiveTime = (): void => {
1025
- if (this.props.skipInactivity && !this.props.isLive) {
1026
- let index = 0;
1027
- let found = false;
1028
-
1029
- while (index < this.state.activeDurations.length) {
1030
- if (this.props.time < this.state.activeDurations[index].startTime) {
1031
- found = true;
1032
- break;
1033
- }
1034
- index++;
1035
- }
1036
-
1037
- if (this.state.activeDurations && this.state.activeDurations.length > 0) {
1038
- if (index > 0 && found && !this.state.activeDurations[index - 1].active) {
1039
- this.goToEnd = false;
1040
- this.props.timeChange(this.state.activeDurations[index].startTime);
1041
- } else if (
1042
- index !== this.state.activeDurations.length ||
1043
- this.state.activeDurations[index - 1].active ||
1044
- this.goToEnd
1045
- ) {
1046
- if (this.goToEnd) {
1047
- this.goToEnd = false;
1048
- }
1049
- } else {
1050
- this.goToEnd = true;
1051
- this.props.timeChange(
1052
- this.state.activeDurations[index - 1].startTime +
1053
- this.state.activeDurations[index - 1].duration,
1054
- );
1055
- }
1056
- }
1057
- }
1058
- };
1059
-
1060
- private setSnapshotLoading = (loading: boolean): void => {
1061
- this.setState({
1062
- snapshotRendered: !loading,
1063
- });
1064
- };
1065
-
1066
- render() {
1067
- if (!this.isProcessed()) {
1068
- if (this.props.error) {
1069
- return <div className="error-message">Error loading impression</div>;
1070
- }
1071
- return <div className="loadingCentered">Loading...</div>;
1072
- }
1073
-
1074
- return (
1075
- <>
1076
- {!this.state.snapshotRendered && <div className="loadingCentered">Loading snapshot...</div>}
1077
- <HeatmapWrapper
1078
- projectId={this.props.projectId}
1079
- isVisible={this.props.isVisible}
1080
- loadingCallback={this.props.loadingCallback}
1081
- mergedPayload={this.state.mergedPayload}
1082
- time={this.props.time}
1083
- parentRef={this.props.parentRef}
1084
- videoAreaRef={this.props.videoAreaRef}
1085
- impressionHeight={this.props.impressionData?.documentHeight}
1086
- panelContent={this.props.panelContent}
1087
- heatmapConfig={this.props.heatmapInfo}
1088
- heatmapLoading={this.props.heatmapLoading}
1089
- heatmapErrorType={this.props.heatmapErrorType}
1090
- snapshotStyleClassName={this.props.snapshotStyleClassName}
1091
- heatmapSidebarRef={this.props.heatmapSidebarRef}
1092
- deviceType={this.props.impressionData?.device}
1093
- url={this.props.impressionData?.url}
1094
- visualizer={this.props.visualizer}
1095
- heatmapVisualizer={this.props.heatmapVisualizer}
1096
- areaMapEditKey={`${this.props.projectId}${this.props.sessionId}${this.props.userId}${this.props.pageNum}${this.props.impressionData?.url}`}
1097
- snapshotLoading={this.setSnapshotLoading}
1098
- setPanelContent={this.props.setPanelContent}
1099
- isDead={this.props.isDead}
1100
- />
1101
- </>
1102
- );
1103
- }
1104
- }
1105
-
1106
- export default ImpressionPlayer;