feat ui: highlight current event plus fix scroll (#2076)

* feat ui: highlight current event plus fix scroll

* change icon styles

* saas fix

* rm cons
This commit is contained in:
Delirium 2024-04-11 15:38:38 +02:00 committed by GitHub
parent b8ed5c5d9e
commit 64cde1fb7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 83 additions and 34 deletions

View file

@ -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<Props> = ({
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<Props> = ({
case TYPES.LOCATION:
title = 'Visited';
body = event.url;
icon = 'event/location';
icon = <Navigation size={16} strokeWidth={1} />
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 ? <MessageCircleQuestion size={16} strokeWidth={1} /> : <Pointer size={16} strokeWidth={1} />;
isFrustration
? Object.assign(tooltip, {
disabled: false,
@ -99,7 +101,7 @@ const Event: React.FC<Props> = ({
case TYPES.INPUT:
title = 'Input';
body = event.value;
icon = isFrustration ? 'event/input_hesitation' : 'event/input';
icon = isFrustration ? <MessageCircleQuestion size={16} strokeWidth={1} /> : <TextCursorInput size={16} strokeWidth={1} />;
isFrustration
? Object.assign(tooltip, {
disabled: false,
@ -111,16 +113,16 @@ const Event: React.FC<Props> = ({
case TYPES.TAPRAGE:
title = event.count ? `${event.count} Clicks` : 'Click Rage';
body = event.label;
icon = 'event/clickrage';
icon = <Angry size={16} strokeWidth={1} />;
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 = <MousePointerClick size={16} strokeWidth={1} />;
break;
}
@ -134,7 +136,7 @@ const Event: React.FC<Props> = ({
>
<div className={cn(cls.main, 'flex flex-col w-full')}>
<div className={cn('flex items-center w-full', { 'px-4': isLocation })}>
{event.type && <Icon name={icon} size='16' color={'gray-dark'} />}
{event.type && iconName ? <Icon name={icon} size='16' color={'gray-dark'} /> : icon}
<div className='ml-3 w-full'>
<div className='flex w-full items-first justify-between'>
<div className='flex items-center w-full' style={{ minWidth: '0' }}>
@ -189,7 +191,7 @@ const Event: React.FC<Props> = ({
[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}

View file

@ -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 (
<>
<div>
<div
style={{
position: 'absolute',
left: 0,
top: 0,
width: 1.5,
height: '100%',
backgroundColor: shadowColor,
zIndex: 98,
}}
/>
{this.props.isCurrent ? (
<div
style={{
position: 'absolute',
top: '50%',
left: -7,
width: 10,
height: 10,
transform: 'rotate(45deg) translate(0, -50%)',
background: '#394EFF',
zIndex: 99,
}}
/>
) : null}
{isFirst && isLocation && event.referrer && (
<TextEllipsis>
<div className={stl.referrer}>
@ -163,4 +191,4 @@ function TabChange({ from, to, activeUrl, onClick }) {
)
}
export default EventGroupWrapper;
export default React.memo(EventGroupWrapper);

View file

@ -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<HTMLInputElement>) => {
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 (
<CellMeasurer key={key} cache={cache} parent={parent} rowIndex={index}>
{({ measure, registerChild }) => (
@ -180,6 +200,7 @@ function EventsBlock(props: IProps) {
showSelection={!playing}
isNote={isNote}
isTabChange={isTabChange}
isPrev={isPrev}
filterOutNote={filterOutNote}
/>
</div>

View file

@ -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 {

View file

@ -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)));

View file

@ -126,7 +126,7 @@ export default class DOMManager extends ListWalker<Message> {
}
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;
}

View file

@ -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 },

View file

@ -243,6 +243,7 @@ export class UxtEvent {
timestamp: number;
title: string;
indexNum: number;
time: number;
constructor(event: Record<string, any>) {
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,
});