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:
parent
b8ed5c5d9e
commit
64cde1fb7c
8 changed files with 83 additions and 34 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue