diff --git a/frontend/app/components/Session_/EventsBlock/Event.tsx b/frontend/app/components/Session_/EventsBlock/Event.tsx index 8b2f8b746..9f77f9ebf 100644 --- a/frontend/app/components/Session_/EventsBlock/Event.tsx +++ b/frontend/app/components/Session_/EventsBlock/Event.tsx @@ -8,6 +8,7 @@ import withOverlay from 'Components/hocs/withOverlay'; import LoadInfo from './LoadInfo'; import cls from './event.module.css'; import { numberWithCommas } from 'App/utils'; +import { Navigation, MessageCircleQuestion, Pointer, TextCursorInput, Angry, MousePointerClick } from 'lucide-react' type Props = { event: any; @@ -65,7 +66,8 @@ const Event: React.FC = ({ const renderBody = () => { let title = event.type; let body; - let icon; + let icon = null; + let iconName = null; const isFrustration = isFrustrationEvent(event); const tooltip = { disabled: true, text: '' }; @@ -73,22 +75,22 @@ const Event: React.FC = ({ case TYPES.LOCATION: title = 'Visited'; body = event.url; - icon = 'event/location'; + icon = break; case TYPES.SWIPE: title = 'Swipe'; body = event.direction; - icon = `chevron-${event.direction}` + iconName = `chevron-${event.direction}` break; case TYPES.TOUCH: title = 'Tapped'; body = event.label; - icon = 'event/click'; + iconName = 'event/click'; break; case TYPES.CLICK: title = 'Clicked'; body = event.label; - icon = isFrustration ? 'event/click_hesitation' : 'event/click'; + icon = isFrustration ? : ; isFrustration ? Object.assign(tooltip, { disabled: false, @@ -99,7 +101,7 @@ const Event: React.FC = ({ case TYPES.INPUT: title = 'Input'; body = event.value; - icon = isFrustration ? 'event/input_hesitation' : 'event/input'; + icon = isFrustration ? : ; isFrustration ? Object.assign(tooltip, { disabled: false, @@ -111,16 +113,16 @@ const Event: React.FC = ({ case TYPES.TAPRAGE: title = event.count ? `${event.count} Clicks` : 'Click Rage'; body = event.label; - icon = 'event/clickrage'; + icon = ; break; case TYPES.IOS_VIEW: title = 'View'; body = event.name; - icon = 'event/ios_view'; + iconName = 'event/ios_view'; break; case 'mouse_thrashing': title = 'Mouse Thrashing'; - icon = 'event/mouse_thrashing'; + icon = ; break; } @@ -134,7 +136,7 @@ const Event: React.FC = ({ >
- {event.type && } + {event.type && iconName ? : icon}
@@ -189,7 +191,7 @@ const Event: React.FC = ({ [cls.frustration]: isFrustration, [cls.highlight]: presentInSearch, [cls.lastInGroup]: whiteBg, - ['pl-4 pr-6 ml-4 py-2 border-l']: event.type !== TYPES.LOCATION, + ['pl-4 pr-6 py-2']: event.type !== TYPES.LOCATION, ['border-0 border-l-0 ml-0']: mobileTypes.includes(event.type), })} onClick={onClick} diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js index e3cccba3c..4ae9e6676 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js @@ -1,6 +1,5 @@ import UxtEvent from "Components/Session_/EventsBlock/UxtEvent"; import React from 'react'; -import { durationFromMsFormatted } from "App/date"; import { connect } from 'react-redux'; import { TextEllipsis, Icon } from 'UI'; import withToggle from 'HOCs/withToggle'; @@ -120,9 +119,38 @@ class EventGroupWrapper extends React.Component { /> ) } + + const shadowColor = this.props.isPrev + ? '#A7BFFF' + : this.props.isCurrent ? '#394EFF' : 'transparent' return ( <>
+
+ {this.props.isCurrent ? ( +
+ ) : null} {isFirst && isLocation && event.referrer && (
@@ -163,4 +191,4 @@ function TabChange({ from, to, activeUrl, onClick }) { ) } -export default EventGroupWrapper; +export default React.memo(EventGroupWrapper); diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 7564914d0..312a9b673 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -42,7 +42,7 @@ function EventsBlock(props: IProps) { const { store, player } = React.useContext(PlayerContext); - const { playing, tabStates, tabChangeEvents = [] } = store.get(); + const { time, endTime, playing, tabStates, tabChangeEvents = [] } = store.get(); const { filteredEvents, @@ -64,7 +64,6 @@ function EventsBlock(props: IProps) { eventListNow.concat(store.get().eventListNow); } - const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0; const usedEvents = React.useMemo(() => { if (tabStates !== undefined) { tabChangeEvents.forEach((ev) => { @@ -100,6 +99,27 @@ function EventsBlock(props: IProps) { props.zoomStartTs, props.zoomEndTs, ]); + const findLastFitting = React.useCallback((time: number) => { + if (!usedEvents.length) return 0; + let i = usedEvents.length - 1; + if (time > endTime / 2) { + while (i >= 0) { + const event = usedEvents[i]; + if ('time' in event && event.time <= time) break; + i--; + } + return i; + } else { + let l = 0; + while (l < i) { + const event = usedEvents[l]; + if ('time' in event && event.time >= time) break; + l++; + } + return l; + } + }, [usedEvents, time, endTime]); + const currentTimeEventIndex = findLastFitting(time) const write = ({ target: { value } }: React.ChangeEvent) => { props.setEventFilter({ query: value }); @@ -162,7 +182,7 @@ function EventsBlock(props: IProps) { const isNote = 'noteId' in event; const isTabChange = 'type' in event && event.type === 'TABCHANGE'; const isCurrent = index === currentTimeEventIndex; - + const isPrev = index < currentTimeEventIndex; return ( {({ measure, registerChild }) => ( @@ -180,6 +200,7 @@ function EventsBlock(props: IProps) { showSelection={!playing} isNote={isNote} isTabChange={isTabChange} + isPrev={isPrev} filterOutNote={filterOutNote} />
diff --git a/frontend/app/components/Session_/Player/player.module.css b/frontend/app/components/Session_/Player/player.module.css index d281b7c35..e6329b337 100644 --- a/frontend/app/components/Session_/Player/player.module.css +++ b/frontend/app/components/Session_/Player/player.module.css @@ -1,11 +1,6 @@ .playerBody { background: $white; - /* border-radius: 3px; */ - /* padding: 10px 10px 5px 10px; */ - /* box-shadow: 0px 2px 10px 0 $gray-light; */ height: 100%; - /* border: solid thin $gray-light; */ - border-right: solid thin $gray-light; } .screenWrapper { diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts index 6e0b92358..6efe4508b 100644 --- a/frontend/app/player/web/MessageLoader.ts +++ b/frontend/app/player/web/MessageLoader.ts @@ -105,8 +105,9 @@ export default class MessageLoader { } }); - const sortedMsgs = msgs.sort((m1, m2) => m1.time - m2.time); - // .sort(brokenDomSorter); + const sortedMsgs = msgs + .sort((m1, m2) => m1.time - m2.time); + // .sort(brokenDomSorter); if (brokenMessages > 0) { console.warn('Broken timestamp messages', brokenMessages); @@ -286,14 +287,14 @@ function brokenDomSorter(m1: PlayerMsg, m2: PlayerMsg) { if (m1.tp !== MType.CreateDocument && m2.tp === MType.CreateDocument) return 1; - // if (m1.tp === MType.CreateIFrameDocument && m2.tp === MType.CreateElementNode) { - // if (m2.id === m1.frameID) return 1; - // if (m2.parentID === m1.id) return -1 - // } - // if (m1.tp === MType.CreateElementNode && m2.tp === MType.CreateIFrameDocument) { - // if (m1.id === m2.frameID) return -1; - // if (m1.parentID === m2.id) return 1 - // } + if (m1.tp === MType.CreateIFrameDocument && m2.tp === MType.CreateElementNode) { + if (m2.id === m1.frameID) return 1; + if (m2.parentID === m1.id) return -1 + } + if (m1.tp === MType.CreateElementNode && m2.tp === MType.CreateIFrameDocument) { + if (m1.id === m2.frameID) return -1; + if (m1.parentID === m2.id) return 1 + } const m1IsDOM = DOMMessages.includes(m1.tp); const m2IsDOM = DOMMessages.includes(m2.tp); if (m1IsDOM && m2IsDOM) { @@ -355,4 +356,4 @@ function findBrokenNodes(nodes: any[]) { return result; } -window.searchOrphans = findBrokenNodes; +window.searchOrphans = (msgs) => findBrokenNodes(msgs.filter(m => [8,9,10,70].includes(m.tp))); diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index ff61f6f3f..3e51ec202 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -126,7 +126,7 @@ export default class DOMManager extends ListWalker { } const parent = this.vElements.get(parentID) || this.olVRoots.get(parentID) if (!parent) { - logger.error("Insert error. Parent vNode not found", parentID, this.vElements, this.olVRoots); + logger.error(`${id} Insert error. Parent vNode ${parentID} not found`, this.vElements, this.olVRoots); return; } diff --git a/frontend/app/player/web/managers/DOM/safeCSSRules.ts b/frontend/app/player/web/managers/DOM/safeCSSRules.ts index 22dec1c77..9bd47b942 100644 --- a/frontend/app/player/web/managers/DOM/safeCSSRules.ts +++ b/frontend/app/player/web/managers/DOM/safeCSSRules.ts @@ -4,7 +4,7 @@ function isChromium(item) { return ['Chromium', 'Google Chrome', 'NewBrowser'].includes(item.brand); } // @ts-ignore -const isChromeLike = navigator.userAgentData.brands.some(isChromium) +const isChromeLike = navigator.userAgentData?.brands?.some(isChromium) export function insertRule( sheet: { insertRule: (rule: string, index?: number) => void }, diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts index ca787fba5..10b1ed23e 100644 --- a/frontend/app/types/session/event.ts +++ b/frontend/app/types/session/event.ts @@ -243,6 +243,7 @@ export class UxtEvent { timestamp: number; title: string; indexNum: number; + time: number; constructor(event: Record) { Object.assign(this, { @@ -255,6 +256,7 @@ export class UxtEvent { status: event.status, taskId: event.taskId, timestamp: event.timestamp, + time: event.time, title: event.title, indexNum: event.indexNum, });