change(ui) - player user steps improvements (#1201)

* change(ui) - user steps

* change(ui) - user steps

* change(ui) - user steps

* change(ui) - user steps - icon and other styles
This commit is contained in:
Shekar Siri 2023-04-24 16:02:18 +02:00 committed by GitHub
parent 8b6dff356e
commit d9404d1d13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 414 additions and 408 deletions

View file

@ -1,195 +0,0 @@
import React from 'react';
import copy from 'copy-to-clipboard';
import cn from 'classnames';
import { Icon, TextEllipsis, Tooltip } from 'UI';
import { TYPES } from 'Types/session/event';
import { prorata } from 'App/utils';
import withOverlay from 'Components/hocs/withOverlay';
import LoadInfo from './LoadInfo';
import cls from './event.module.css';
import { numberWithCommas } from 'App/utils';
function isFrustrationEvent(evt) {
if (evt.type === 'mouse_thrashing' || evt.type === TYPES.CLICKRAGE) {
return true;
}
if (evt.type === TYPES.CLICK || evt.type === TYPES.INPUT) {
return evt.hesitation > 1000
}
return false
}
@withOverlay()
export default class Event extends React.PureComponent {
state = {
menuOpen: false,
}
componentDidMount() {
this.wrapper.addEventListener('contextmenu', this.onContextMenu);
}
onContextMenu = (e) => {
e.preventDefault();
this.setState({ menuOpen: true });
}
onMouseLeave = () => this.setState({ menuOpen: false })
copyHandler = (e) => {
e.stopPropagation();
//const ctrlOrCommandPressed = e.ctrlKey || e.metaKey;
//if (ctrlOrCommandPressed && e.keyCode === 67) {
const { event } = this.props;
copy(event.getIn([ 'target', 'path' ]) || event.url || '');
this.setState({ menuOpen: false });
}
toggleInfo = (e) => {
e.stopPropagation();
this.props.toggleInfo();
}
// eslint-disable-next-line complexity
renderBody = () => {
const { event } = this.props;
let title = event.type;
let body;
let icon;
const isFrustration = isFrustrationEvent(event);
const tooltip = { disabled: true, text: '' }
switch (event.type) {
case TYPES.LOCATION:
title = 'Visited';
body = event.url;
icon = 'location';
break;
case TYPES.CLICK:
title = 'Clicked';
body = event.label;
icon = isFrustration ? 'click_hesitation' : 'click';
isFrustration ? Object.assign(tooltip, { disabled: false, text: `User hesitated to click for ${Math.round(event.hesitation/1000)}s`, }) : null;
break;
case TYPES.INPUT:
title = 'Input';
body = event.value;
icon = isFrustration ? 'input_hesitation' : 'input';
isFrustration ? Object.assign(tooltip, { disabled: false, text: `User hesitated to enter a value for ${Math.round(event.hesitation/1000)}s`, }) : null;
break;
case TYPES.CLICKRAGE:
title = `${ event.count } Clicks`;
body = event.label;
icon = 'clickrage'
break;
case TYPES.IOS_VIEW:
title = 'View';
body = event.name;
icon = 'ios_view'
break;
case 'mouse_thrashing':
title = 'Mouse Thrashing';
icon = 'mouse_thrashing'
break;
}
const isLocation = event.type === TYPES.LOCATION;
return (
<Tooltip title={tooltip.text} disabled={tooltip.disabled} placement={"left"} anchorClassName={"w-full"} containerClassName={"w-full"}>
<div className={ cn(cls.main, 'flex flex-col w-full') } >
<div className="flex items-center w-full">
{ event.type && <Icon name={`event/${icon}`} size="16" color={'gray-dark' } /> }
<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'}}>
<span className={cls.title}>{ title }</span>
{/* { body && !isLocation && <div className={ cls.data }>{ body }</div> } */}
{ body && !isLocation &&
<TextEllipsis maxWidth="60%" className="w-full ml-2 text-sm color-gray-medium" text={body} />
}
</div>
{ isLocation && event.speedIndex != null &&
<div className="color-gray-medium flex font-medium items-center leading-none justify-end">
<div className="font-size-10 pr-2">{"Speed Index"}</div>
<div>{ numberWithCommas(event.speedIndex || 0) }</div>
</div>
}
</div>
{ event.target && event.target.label &&
<div className={ cls.badge } >{ event.target.label }</div>
}
</div>
</div>
{ isLocation &&
<div className="mt-1">
<span className="text-sm font-normal color-gray-medium">{ body }</span>
</div>
}
</div>
</Tooltip>
);
};
render() {
const {
event,
selected,
isCurrent,
onClick,
showSelection,
showLoadInfo,
toggleLoadInfo,
isRed,
presentInSearch = false,
whiteBg,
} = this.props;
const { menuOpen } = this.state;
const isFrustration = isFrustrationEvent(event);
return (
<div
ref={ ref => { this.wrapper = ref } }
onMouseLeave={ this.onMouseLeave }
data-openreplay-label="Event"
data-type={event.type}
className={ cn(cls.event, {
[ cls.menuClosed ]: !menuOpen,
[ cls.highlighted ]: showSelection ? selected : isCurrent,
[ cls.selected ]: selected,
[ cls.showSelection ]: showSelection,
[ cls.red ]: isRed,
[ cls.clickType ]: event.type === TYPES.CLICK,
[ cls.inputType ]: event.type === TYPES.INPUT,
[ cls.frustration ]: isFrustration,
[ cls.highlight ] : presentInSearch,
[ cls.lastInGroup ]: whiteBg,
}) }
onClick={ onClick }
>
{ menuOpen &&
<button onClick={ this.copyHandler } className={ cls.contextMenu }>
{ event.target ? 'Copy CSS' : 'Copy URL' }
</button>
}
<div className={ cn(cls.topBlock, 'w-full') }>
<div className={ cn(cls.firstLine, 'w-full') }>
{ this.renderBody() }
</div>
</div>
{ event.type === TYPES.LOCATION && (event.fcpTime || event.visuallyComplete || event.timeToInteractive) &&
<LoadInfo
showInfo={ showLoadInfo }
onClick={ toggleLoadInfo }
event={ event }
prorata={ prorata({
parts: 100,
elements: { a: event.fcpTime, b: event.visuallyComplete, c: event.timeToInteractive },
startDivisorFn: elements => elements / 1.2,
// eslint-disable-next-line no-mixed-operators
divisorFn: (elements, parts) => elements / (2 * parts + 1),
}) }
/>
}
</div>
);
}
}

View file

@ -0,0 +1,215 @@
import React, { useRef, useState } from 'react';
import copy from 'copy-to-clipboard';
import cn from 'classnames';
import { Icon, TextEllipsis, Tooltip } from 'UI';
import { TYPES } from 'Types/session/event';
import { prorata } from 'App/utils';
import withOverlay from 'Components/hocs/withOverlay';
import LoadInfo from './LoadInfo';
import cls from './event.module.css';
import { numberWithCommas } from 'App/utils';
type Props = {
event: any;
selected?: boolean;
isCurrent?: boolean;
onClick?: () => void;
showSelection?: boolean;
showLoadInfo?: boolean;
toggleLoadInfo?: () => void;
isRed?: boolean;
presentInSearch?: boolean;
whiteBg?: boolean;
};
const isFrustrationEvent = (evt: any): boolean => {
if (evt.type === 'mouse_thrashing' || evt.type === TYPES.CLICKRAGE) {
return true;
}
if (evt.type === TYPES.CLICK || evt.type === TYPES.INPUT) {
return evt.hesitation > 1000;
}
return false;
};
const Event: React.FC<Props> = ({
event,
selected = false,
isCurrent = false,
onClick,
showSelection = false,
showLoadInfo,
toggleLoadInfo,
isRed = false,
presentInSearch = false,
whiteBg,
}) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const [menuOpen, setMenuOpen] = useState(false);
const onContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setMenuOpen(true);
};
const onMouseLeave = () => setMenuOpen(false);
const copyHandler = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
const path = event.getIn(['target', 'path']) || event.url || '';
copy(path);
setMenuOpen(false);
};
const renderBody = () => {
let title = event.type;
let body;
let icon;
const isFrustration = isFrustrationEvent(event);
const tooltip = { disabled: true, text: '' };
switch (event.type) {
case TYPES.LOCATION:
title = 'Visited';
body = event.url;
icon = 'location';
break;
case TYPES.CLICK:
title = 'Clicked';
body = event.label;
icon = isFrustration ? 'click_hesitation' : 'click';
isFrustration
? Object.assign(tooltip, {
disabled: false,
text: `User hesitated to click for ${Math.round(event.hesitation / 1000)}s`,
})
: null;
break;
case TYPES.INPUT:
title = 'Input';
body = event.value;
icon = isFrustration ? 'input_hesitation' : 'input';
isFrustration
? Object.assign(tooltip, {
disabled: false,
text: `User hesitated to enter a value for ${Math.round(event.hesitation / 1000)}s`,
})
: null;
break;
case TYPES.CLICKRAGE:
title = `${event.count} Clicks`;
body = event.label;
icon = 'clickrage';
break;
case TYPES.IOS_VIEW:
title = 'View';
body = event.name;
icon = 'ios_view';
break;
case 'mouse_thrashing':
title = 'Mouse Thrashing';
icon = 'mouse_thrashing';
break;
}
const isLocation = event.type === TYPES.LOCATION;
return (
<Tooltip
title={tooltip.text}
disabled={tooltip.disabled}
placement={'left'}
anchorClassName={'w-full'}
containerClassName={'w-full'}
>
<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={`event/${icon}`} size="16" color={'gray-dark'} />}
<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' }}>
<span className={cn(cls.title, {'font-medium' : isLocation })}>{title}</span>
{body && !isLocation && (
<TextEllipsis
maxWidth="60%"
className="w-full ml-2 text-sm color-gray-medium"
text={body}
/>
)}
</div>
{isLocation && event.speedIndex != null && (
<div className="color-gray-medium flex font-medium items-center leading-none justify-end">
<div className="font-size-10 pr-2">{'Speed Index'}</div>
<div>{numberWithCommas(event.speedIndex || 0)}</div>
</div>
)}
</div>
{event.target && event.target.label && (
<div className={cls.badge}>{event.target.label}</div>
)}
</div>
</div>
{isLocation && (
<div className="mt-1 px-4">
<span className="text-sm font-normal color-gray-medium">{body}</span>
</div>
)}
</div>
</Tooltip>
);
};
const isFrustration = isFrustrationEvent(event);
return (
<div
ref={wrapperRef}
onMouseLeave={onMouseLeave}
data-openreplay-label="Event"
data-type={event.type}
className={cn(cls.event, {
[cls.menuClosed]: !menuOpen,
[cls.highlighted]: showSelection ? selected : isCurrent,
[cls.selected]: selected,
[cls.showSelection]: showSelection,
[cls.red]: isRed,
[cls.clickType]: event.type === TYPES.CLICK,
[cls.inputType]: event.type === TYPES.INPUT,
[cls.frustration]: isFrustration,
[cls.highlight]: presentInSearch,
[cls.lastInGroup]: whiteBg,
['mx-4 rounded']: event.type !== TYPES.LOCATION,
})}
onClick={onClick}
onContextMenu={onContextMenu}
>
{menuOpen && (
<button onClick={copyHandler} className={cls.contextMenu}>
{event.target ? 'Copy CSS' : 'Copy URL'}
</button>
)}
<div className={cn(cls.topBlock, 'w-full')}>
<div className={cn(cls.firstLine, 'w-full')}>{renderBody()}</div>
</div>
{event.type === TYPES.LOCATION &&
(event.fcpTime || event.visuallyComplete || event.timeToInteractive) && (
<LoadInfo
showInfo={showLoadInfo}
onClick={toggleLoadInfo}
event={event}
prorata={prorata({
parts: 100,
elements: {
a: event.fcpTime,
b: event.visuallyComplete,
c: event.timeToInteractive,
},
startDivisorFn: (elements) => elements / 1.2,
divisorFn: (elements, parts) => elements / (2 * parts + 1),
})}
/>
)}
</div>
);
};
export default withOverlay()(Event);

View file

@ -1,13 +1,13 @@
import React from 'react';
import cn from 'classnames';
import { connect } from 'react-redux'
import { connect } from 'react-redux';
import { TextEllipsis } from 'UI';
import withToggle from 'HOCs/withToggle';
import { TYPES } from 'Types/session/event';
import Event from './Event'
import Event from './Event';
import stl from './eventGroupWrapper.module.css';
import NoteEvent from './NoteEvent';
import { setEditNoteTooltip } from 'Duck/sessions';;
import { setEditNoteTooltip } from 'Duck/sessions';
// TODO: incapsulate toggler in LocationEvent
@withToggle('showLoadInfo', 'toggleLoadInfo')
@ -66,65 +66,68 @@ class EventGroupWrapper extends React.Component {
const safeRef = String(event.referrer || '');
return (
<div
className={cn(
stl.container,
'!py-1',
{
[stl.last]: isLastInGroup,
[stl.first]: event.type === TYPES.LOCATION,
[stl.dashAfter]: isLastInGroup && !isLastEvent,
},
isLastInGroup && '!pb-2',
event.type === TYPES.LOCATION && '!pt-2 !pb-2'
)}
>
{isFirst && isLocation && event.referrer && (
<div className={stl.referrer}>
<TextEllipsis>
Referrer: <span className={stl.url}>{safeRef}</span>
</TextEllipsis>
</div>
)}
{isNote ? (
<NoteEvent
note={event}
filterOutNote={filterOutNote}
onEdit={this.props.setEditNoteTooltip}
noEdit={this.props.currentUserId !== event.userId}
/>
) : isLocation ? (
<Event
extended={isFirst}
key={event.key}
event={event}
onClick={this.onEventClick}
selected={isSelected}
showLoadInfo={showLoadInfo}
toggleLoadInfo={this.toggleLoadInfo}
isCurrent={isCurrent}
presentInSearch={presentInSearch}
isLastInGroup={isLastInGroup}
whiteBg={whiteBg}
/>
) : (
<Event
key={event.key}
event={event}
onClick={this.onEventClick}
onCheckboxClick={this.onCheckboxClick}
selected={isSelected}
isCurrent={isCurrent}
showSelection={showSelection}
overlayed={isEditing}
presentInSearch={presentInSearch}
isLastInGroup={isLastInGroup}
whiteBg={whiteBg}
/>
)}
</div>
<>
<div
className={cn(
'!py-1',
{
[stl.last]: isLastInGroup,
[stl.first]: event.type === TYPES.LOCATION,
[stl.dashAfter]: isLastInGroup && !isLastEvent,
},
isLastInGroup && '!pb-2',
event.type === TYPES.LOCATION && '!pt-2 !pb-2'
)}
>
{isFirst && isLocation && event.referrer && (
<TextEllipsis>
<div className={stl.referrer}>
Referrer: <span className={stl.url}>{safeRef}</span>
</div>
</TextEllipsis>
)}
{isNote ? (
<NoteEvent
note={event}
filterOutNote={filterOutNote}
onEdit={this.props.setEditNoteTooltip}
noEdit={this.props.currentUserId !== event.userId}
/>
) : isLocation ? (
<Event
extended={isFirst}
key={event.key}
event={event}
onClick={this.onEventClick}
selected={isSelected}
showLoadInfo={showLoadInfo}
toggleLoadInfo={this.toggleLoadInfo}
isCurrent={isCurrent}
presentInSearch={presentInSearch}
isLastInGroup={isLastInGroup}
whiteBg={true}
/>
) : (
<Event
key={event.key}
event={event}
onClick={this.onEventClick}
onCheckboxClick={this.onCheckboxClick}
selected={isSelected}
isCurrent={isCurrent}
showSelection={showSelection}
overlayed={isEditing}
presentInSearch={presentInSearch}
isLastInGroup={isLastInGroup}
whiteBg={whiteBg}
/>
)}
</div>
{isLastInGroup && <div className="border-t mx-5 border-color-gray-light-shade" />}
</>
);
}
}
export default EventGroupWrapper
export default EventGroupWrapper;

View file

@ -1,43 +1,44 @@
import React from 'react'
import { Input, Icon } from 'UI'
import React from 'react';
import { Input, Button } from 'UI';
import { PlayerContext } from 'App/components/Session/playerContext';
function EventSearch(props) {
const { player } = React.useContext(PlayerContext)
const { player } = React.useContext(PlayerContext);
const { onChange, value, header, setActiveTab } = props;
const toggleEvents = () => player.toggleEvents()
const toggleEvents = () => player.toggleEvents();
return (
<div className="flex items-center w-full relative">
<div className="flex flex-1 flex-col">
<div className='flex flex-center justify-between'>
<span>{header}</span>
<div
onClick={() => { setActiveTab(''); toggleEvents(); }}
className=" flex items-center justify-center bg-white cursor-pointer"
>
<Icon name="close" size="18" />
</div>
<div className="flex items-center">
<Input
autoFocus
type="text"
placeholder="Filter by Event Type, URL or Keyword"
className="inset-0 w-full"
name="query"
value={value}
onChange={onChange}
wrapperClassName="w-full"
style={{ height: '32px' }}
autoComplete="off chromebugfix"
/>
<Button
className="ml-2"
icon="close"
variant="text"
onClick={() => {
setActiveTab('');
toggleEvents();
}}
/>
</div>
<div className="flex items-center mt-2">
<Input
autoFocus
type="text"
placeholder="Filter by Event Type, URL or Keyword"
className="inset-0 w-full"
name="query"
value={value}
onChange={onChange}
wrapperClassName="w-full"
style={{ height: '32px' }}
autoComplete="off chromebugfix"
/>
</div>
</div>
</div>
)
);
}
export default EventSearch
export default EventSearch;

View file

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import cn from 'classnames';
import { Icon } from 'UI';
import { List, AutoSizer, CellMeasurer } from "react-virtualized";
import { List, AutoSizer, CellMeasurer } from 'react-virtualized';
import { TYPES } from 'Types/session/event';
import { setEventFilter, filterOutNote } from 'Duck/sessions';
import EventGroupWrapper from './EventGroupWrapper';
@ -10,33 +10,33 @@ import styles from './eventsBlock.module.css';
import EventSearch from './EventSearch/EventSearch';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
import { RootStore } from 'App/duck'
import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'
import { InjectedEvent } from 'Types/session/event'
import Session from 'Types/session'
import { RootStore } from 'App/duck';
import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache';
import { InjectedEvent } from 'Types/session/event';
import Session from 'Types/session';
interface IProps {
setEventFilter: (filter: { query: string }) => void
filteredEvents: InjectedEvent[]
setActiveTab: (tab?: string) => void
query: string
events: Session['events']
notesWithEvents: Session['notesWithEvents']
filterOutNote: (id: string) => void
eventsIndex: number[]
setEventFilter: (filter: { query: string }) => void;
filteredEvents: InjectedEvent[];
setActiveTab: (tab?: string) => void;
query: string;
events: Session['events'];
notesWithEvents: Session['notesWithEvents'];
filterOutNote: (id: string) => void;
eventsIndex: number[];
}
function EventsBlock(props: IProps) {
const [mouseOver, setMouseOver] = React.useState(true)
const scroller = React.useRef<List>(null)
const [mouseOver, setMouseOver] = React.useState(true);
const scroller = React.useRef<List>(null);
const cache = useCellMeasurerCache(undefined, {
fixedWidth: true,
defaultHeight: 300
defaultHeight: 300,
});
const { store, player } = React.useContext(PlayerContext)
const { store, player } = React.useContext(PlayerContext);
const { eventListNow, playing } = store.get()
const { eventListNow, playing } = store.get();
const {
filteredEvents,
@ -46,23 +46,23 @@ function EventsBlock(props: IProps) {
setActiveTab,
events,
notesWithEvents,
} = props
} = props;
const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0
const usedEvents = filteredEvents || notesWithEvents
const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0;
const usedEvents = filteredEvents || notesWithEvents;
const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
props.setEventFilter({ query: value })
props.setEventFilter({ query: value });
setTimeout(() => {
if (!scroller.current) return;
scroller.current.scrollToRow(0);
}, 100)
}
}, 100);
};
const clearSearch = () => {
props.setEventFilter({ query: '' })
props.setEventFilter({ query: '' });
if (scroller.current) {
scroller.current.forceUpdateGrid();
}
@ -71,14 +71,14 @@ function EventsBlock(props: IProps) {
if (!scroller.current) return;
scroller.current.scrollToRow(0);
}, 100)
}
}, 100);
};
React.useEffect(() => {
return () => {
clearSearch()
}
}, [])
clearSearch();
};
}, []);
React.useEffect(() => {
if (scroller.current) {
scroller.current.forceUpdateGrid();
@ -86,40 +86,49 @@ function EventsBlock(props: IProps) {
scroller.current.scrollToRow(currentTimeEventIndex);
}
}
}, [currentTimeEventIndex])
}, [currentTimeEventIndex]);
const onEventClick = (_: React.MouseEvent, event: { time: number }) => player.jump(event.time)
const onMouseOver = () => setMouseOver(true)
const onMouseLeave = () => setMouseOver(false)
const onEventClick = (_: React.MouseEvent, event: { time: number }) => {
player.jump(event.time);
props.setEventFilter({ query: '' });
};
const onMouseOver = () => setMouseOver(true);
const onMouseLeave = () => setMouseOver(false);
const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: React.CSSProperties; parent: any }) => {
const renderGroup = ({
index,
key,
style,
parent,
}: {
index: number;
key: string;
style: React.CSSProperties;
parent: any;
}) => {
const isLastEvent = index === usedEvents.length - 1;
const isLastInGroup = isLastEvent || usedEvents[index + 1]?.type === TYPES.LOCATION;
const event = usedEvents[index];
const isNote = 'noteId' in event
const isNote = 'noteId' in event;
const isCurrent = index === currentTimeEventIndex;
const heightBug = index === 0 && event?.type === TYPES.LOCATION && 'referrer' in event ? { top: 2 } : {}
const heightBug =
index === 0 && event?.type === TYPES.LOCATION && 'referrer' in event ? { top: 2 } : {};
return (
<CellMeasurer
key={key}
cache={cache}
parent={parent}
rowIndex={index}
>
{({measure, registerChild}) => (
<CellMeasurer key={key} cache={cache} parent={parent} rowIndex={index}>
{({ measure, registerChild }) => (
<div style={{ ...style, ...heightBug }} ref={registerChild}>
<EventGroupWrapper
query={query}
presentInSearch={eventsIndex.includes(index)}
isFirst={index==0}
isFirst={index == 0}
mesureHeight={measure}
onEventClick={ onEventClick }
event={ event }
isLastEvent={ isLastEvent }
isLastInGroup={ isLastInGroup }
isCurrent={ isCurrent }
showSelection={ !playing }
onEventClick={onEventClick}
event={event}
isLastEvent={isLastEvent}
isLastInGroup={isLastInGroup}
isCurrent={isCurrent}
showSelection={!playing}
isNote={isNote}
filterOutNote={filterOutNote}
/>
@ -127,50 +136,44 @@ function EventsBlock(props: IProps) {
)}
</CellMeasurer>
);
}
};
const isEmptySearch = query && (usedEvents.length === 0 || !usedEvents)
const isEmptySearch = query && (usedEvents.length === 0 || !usedEvents);
return (
<>
<div className={ cn(styles.header, 'p-4') }>
<div className={ cn(styles.hAndProgress, 'mt-3') }>
<EventSearch
onChange={write}
setActiveTab={setActiveTab}
value={query}
header={
<div className="text-xl">User Steps <span className="color-gray-medium">{ events.length }</span></div>
}
/>
<div className={cn(styles.header, 'p-4')}>
<div className={cn(styles.hAndProgress, 'mt-3')}>
<EventSearch onChange={write} setActiveTab={setActiveTab} value={query} />
</div>
<div className="mt-1 color-gray-medium">Displaying {usedEvents.length} events</div>
</div>
<div
className={ cn("flex-1 px-4 pb-4", styles.eventsList) }
className={cn('flex-1 pb-4', styles.eventsList)}
id="eventList"
data-openreplay-masked
onMouseOver={ onMouseOver }
onMouseLeave={ onMouseLeave }
onMouseOver={onMouseOver}
onMouseLeave={onMouseLeave}
>
{isEmptySearch && (
<div className='flex items-center'>
<div className="flex items-center p-4">
<Icon name="binoculars" size={18} />
<span className='ml-2'>No Matching Results</span>
<span className="ml-2">No Matching Results</span>
</div>
)}
<AutoSizer disableWidth>
{({ height }) => (
<List
ref={scroller}
className={ styles.eventsList }
className={styles.eventsList}
height={height + 10}
width={248}
width={270}
overscanRowCount={6}
itemSize={230}
rowCount={usedEvents.length}
deferredMeasurementCache={cache}
rowHeight={cache.rowHeight}
rowRenderer={renderGroup}
scrollToAlignment="start"
scrollToAlignment="center"
/>
)}
</AutoSizer>
@ -179,14 +182,17 @@ function EventsBlock(props: IProps) {
);
}
export default connect((state: RootStore) => ({
session: state.getIn([ 'sessions', 'current' ]),
notesWithEvents: state.getIn([ 'sessions', 'current' ]).notesWithEvents,
events: state.getIn([ 'sessions', 'current' ]).events,
filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]),
query: state.getIn(['sessions', 'eventsQuery']),
eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]),
}), {
setEventFilter,
filterOutNote
})(observer(EventsBlock))
export default connect(
(state: RootStore) => ({
session: state.getIn(['sessions', 'current']),
notesWithEvents: state.getIn(['sessions', 'current']).notesWithEvents,
events: state.getIn(['sessions', 'current']).events,
filteredEvents: state.getIn(['sessions', 'filteredEvents']),
query: state.getIn(['sessions', 'eventsQuery']),
eventsIndex: state.getIn(['sessions', 'eventsIndex']),
}),
{
setEventFilter,
filterOutNote,
}
)(observer(EventsBlock));

View file

@ -2,9 +2,8 @@
position: absolute;
top: 27px;
right: 15px;
padding: 2px 3px;
/* padding: 2px 3px; */
background: $white;
border: 1px solid $gray-light;
border-radius: 3px;
cursor: pointer;
color: $gray-medium;
@ -15,15 +14,12 @@
.event {
position: relative;
background: #f6f6f6;
border-radius: 3px;
/* border-radius: 3px; */
user-select: none;
/* box-shadow: 0px 1px 3px 0 $gray-light; */
transition: all 0.2s;
cursor: pointer;
border: 1px solid transparent;
&:hover {
background-color: $active-blue;
border: 1px solid $active-blue-border;
}
& .title {
@ -55,7 +51,7 @@
&.menuClosed.showSelection {
&:hover, &.selected {
background-color: #EFFCFB;
background-color: $active-blue;
& .checkbox {
display: flex;
@ -69,9 +65,8 @@
&.highlighted {
transition: all 0.2s;
box-shadow: 0px 2px 10px 0 $gray-light;
border: 1px solid $active-blue-border;
/* background-color: red; */
background-color: $active-blue;
box-shadow: 0 0 0 2px $active-blue;
}
&.red {
@ -136,37 +131,22 @@
.clickType, .inputType {
/* border: 1px solid $gray-light; */
background-color: $gray-lightest;
cursor: pointer;
}
.frustration {
background-color: rgba(204, 0, 0, 0.1)!important;
box-shadow:
2px 2px 1px 1px white,
2px 2px 0px 1px rgba(0,0,0,0.4);
}
.clickrageType {
background-color: #FFF3F3;
border: 1px solid #CC0000;
box-shadow:
/* The top layer shadow */
/* 0 1px 1px rgba(0,0,0,0.15), */
/* The second layer */
2px 2px 1px 1px white,
/* The second layer shadow */
2px 2px 0px 1px rgba(0,0,0,0.4);
/* Padding for demo purposes */
/* padding: 12px; */
}
.highlight {
border: solid thin red;
/* border: solid thin red; */
}
.lastInGroup {
background: white;
box-shadow: 0px 1px 1px 0px rgb(0 0 0 / 18%);
}

View file

@ -1,18 +1,12 @@
.container {
padding: 0px 7px; /*0.35rem 0.5rem */
background-color: #f6f6f6;
}
.first {
padding-top: 7px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-radius: 0;
}
.last {
padding-bottom: 7px;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
padding-bottom: 2px;
margin-bottom: 5px;
/* border-bottom: 1px solid $gray-lightest; */
}
.dashAfter {
@ -25,6 +19,9 @@
font-weight: 500 !important;
display: flex;
align-items: center;
max-width: 280px;
margin: 0 20px;
margin-bottom: 5px;
& .url {
margin-left: 5px;
font-weight: 300;

View file

@ -1,5 +1,5 @@
.eventsBlock {
width: 270px;
/* width: 290px; */
margin-bottom: 5px;
}

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-cursor" viewBox="0 0 16 16">
<path d="M14.082 2.182a.5.5 0 0 1 .103.557L8.528 15.467a.5.5 0 0 1-.917-.007L5.57 10.694.803 8.652a.5.5 0 0 1-.006-.916l12.728-5.657a.5.5 0 0 1 .556.103zM2.25 8.184l3.897 1.67a.5.5 0 0 1 .262.263l1.67 3.897L12.743 3.52 2.25 8.184z"/>
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-send" viewBox="0 0 16 16">
<path d="M15.854.146a.5.5 0 0 1 .11.54l-5.819 14.547a.75.75 0 0 1-1.329.124l-3.178-4.995L.643 7.184a.75.75 0 0 1 .124-1.33L15.314.037a.5.5 0 0 1 .54.11ZM6.636 10.07l2.761 4.338L14.13 2.576 6.636 10.07Zm6.787-8.201L1.591 6.602l4.339 2.76 7.494-7.493Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 361 B