Player ux improvements (#2834)
* Player UX improvements. DevTools (Including multi-tab) Actions panel (User events, Click maps, Tag Elements) * ui: remove unused imports, remove str templ classnames --------- Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com>
This commit is contained in:
parent
0ac4ed1fa2
commit
f6cf1cfb4a
34 changed files with 373 additions and 253 deletions
|
|
@ -9,7 +9,6 @@ import {
|
|||
import React from 'react';
|
||||
|
||||
import ExCard from './ExCard';
|
||||
import { size } from '@floating-ui/react-dom-interactions';
|
||||
|
||||
const TYPES = {
|
||||
Frustrations: 'frustrations',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useStore } from 'App/mstore';
|
||||
import React from 'react';
|
||||
// import Select from 'Shared/Select';
|
||||
import { Select } from 'antd';
|
||||
|
||||
const sortOptions = [
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ function PlayerBlockHeader(props: any) {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative border-l border-l-gray-lighter" style={{ minWidth: '270px' }}>
|
||||
<div className="px-2 relative border-l border-l-gray-lighter" style={{ minWidth: '270px' }}>
|
||||
<Tabs
|
||||
tabs={TABS}
|
||||
active={activeTab}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Segmented } from 'antd';
|
||||
import React from 'react';
|
||||
import { VList, VListHandle } from 'virtua';
|
||||
import { PlayerContext } from "App/components/Session/playerContext";
|
||||
|
|
@ -12,7 +11,9 @@ import {
|
|||
} from 'App/components/Client/Integrations/apiMethods';
|
||||
import BottomBlock from 'App/components/shared/DevTools/BottomBlock';
|
||||
import { capitalize } from 'App/utils';
|
||||
import { Icon, Input } from 'UI';
|
||||
import { Icon } from 'UI';
|
||||
import { Segmented, Input, Tooltip } from 'antd';
|
||||
import {SearchOutlined} from '@ant-design/icons';
|
||||
import { client } from 'App/mstore';
|
||||
import { FailedFetch, LoadingFetch } from "./StatusMessages";
|
||||
import {
|
||||
|
|
@ -82,26 +83,44 @@ function BackendLogsPanel() {
|
|||
return (
|
||||
<BottomBlock style={{ height: '100%' }}>
|
||||
<BottomBlock.Header>
|
||||
<div className={'flex gap-2 items-center w-full'}>
|
||||
<div className={'font-semibold'}>Traces</div>
|
||||
{tabs.length && tab ? (
|
||||
<div>
|
||||
<Segmented options={tabs} value={tab} onChange={setTab} />
|
||||
<div className='flex items-center justify-between w-full'>
|
||||
<div className={'flex gap-2 items-center'}>
|
||||
<div className={'font-semibold'}>Traces</div>
|
||||
{tabs.length && tab ? (
|
||||
<div>
|
||||
<Segmented options={tabs} value={tab} onChange={setTab} size='small' />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={'ml-auto'} />
|
||||
<Segmented options={[{ label: 'All Tabs', value: 'all' }]} />
|
||||
<Input
|
||||
className="input-small h-8"
|
||||
placeholder="Filter by keyword"
|
||||
icon="search"
|
||||
name="filter"
|
||||
height={28}
|
||||
onChange={onFilterChange}
|
||||
value={filter}
|
||||
<div className='flex items-center gap-2'>
|
||||
<Segmented
|
||||
options={[
|
||||
{ label: 'All Tabs', value: 'all', },
|
||||
{ label: (
|
||||
<Tooltip title="Backend logs are fetched for all tabs combined.">
|
||||
<span>Current Tab</span>
|
||||
</Tooltip>),
|
||||
value: 'current', disabled: true},
|
||||
]}
|
||||
defaultValue="all"
|
||||
size="small"
|
||||
className="rounded-full font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<Input
|
||||
className="rounded-lg"
|
||||
placeholder="Filter by keyword"
|
||||
name="filter"
|
||||
onChange={onFilterChange}
|
||||
value={filter}
|
||||
size='small'
|
||||
prefix={<SearchOutlined className='text-neutral-400' />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
|
||||
<BottomBlock.Content className="overflow-y-auto">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function LoadingFetch({ provider }: { provider: string }) {
|
|||
'w-full h-full flex items-center justify-center flex-col gap-2'
|
||||
}
|
||||
>
|
||||
<LoadingOutlined style={{ fontSize: 32 }} />
|
||||
<LoadingOutlined size={32} />
|
||||
<div>Fetching logs from {provider}...</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -33,16 +33,23 @@ export function FailedFetch({
|
|||
'w-full h-full flex flex-col items-center justify-center gap-2'
|
||||
}
|
||||
>
|
||||
<Icon name={'exclamation-circle'} size={32} />
|
||||
<div className={'flex items-center gap-1'}>
|
||||
|
||||
<div className={'flex items-center gap-1 font-medium'}>
|
||||
<Icon name={'exclamation-circle'} size={14} />
|
||||
<span>Failed to fetch logs from {provider}. </span>
|
||||
<div className={'link'} onClick={onRetry}>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-3'>
|
||||
|
||||
<Button type='text' size='small' onClick={onRetry}>
|
||||
Retry
|
||||
</div>
|
||||
</div>
|
||||
<div className={'link'} onClick={() => history.push(intPath)}>
|
||||
</Button>
|
||||
|
||||
<Button type='text' size='small' onClick={() => history.push(intPath)}>
|
||||
Check Configuration
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,51 @@ import { useStore } from 'App/mstore';
|
|||
import SaveModal from 'Components/Session/Player/TagWatch/SaveModal';
|
||||
import React from 'react';
|
||||
import { PlayerContext } from 'Components/Session/playerContext';
|
||||
import { Button, Input } from 'antd';
|
||||
import { CopyButton } from 'UI';
|
||||
import { SearchOutlined, ZoomInOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, Tooltip } from 'antd';
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import { ZoomInOutlined } from '@ant-design/icons';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import { toast } from 'react-toastify';
|
||||
import { FilterKey } from "App/types/filter/filterType";
|
||||
import { addOptionsToFilter } from "App/types/filter/newFilter";
|
||||
import { FilterKey } from 'App/types/filter/filterType';
|
||||
import { addOptionsToFilter } from 'App/types/filter/newFilter';
|
||||
|
||||
interface CopyableTextAreaProps {
|
||||
selector: string;
|
||||
setSelector: (value: string) => void;
|
||||
}
|
||||
|
||||
const CopyableTextArea: React.FC<CopyableTextAreaProps> = ({ selector, setSelector }) => {
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(selector);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full relative">
|
||||
<Input.TextArea
|
||||
value={selector}
|
||||
onChange={(e) => setSelector(e.target.value)}
|
||||
className="rounded-lg font-mono text-sm placeholder:font-sans placeholder:text-base placeholder:text-gray-400"
|
||||
rows={4}
|
||||
style={{ paddingRight: '40px' }}
|
||||
placeholder='Enter selector to tag elements. E.g. .btn-primary'
|
||||
/>
|
||||
<Tooltip title="Copy">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={handleCopy}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
right: '8px',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function TagWatch() {
|
||||
const { tagWatchStore, searchStore } = useStore();
|
||||
|
|
@ -50,7 +87,7 @@ function TagWatch() {
|
|||
ignoreClickRage: ignoreClRage,
|
||||
ignoreDeadClick: ignoreDeadCl,
|
||||
});
|
||||
const tags = await tagWatchStore.getTags()
|
||||
const tags = await tagWatchStore.getTags();
|
||||
if (tags) {
|
||||
addOptionsToFilter(
|
||||
FilterKey.TAGGED_ELEMENT,
|
||||
|
|
@ -58,42 +95,41 @@ function TagWatch() {
|
|||
);
|
||||
searchStore.refreshFilterOptions();
|
||||
}
|
||||
// @ts-ignore
|
||||
toast.success('Tag created');
|
||||
setSelector('');
|
||||
return tag
|
||||
return tag;
|
||||
} catch {
|
||||
// @ts-ignore
|
||||
toast.error('Failed to create tag');
|
||||
}
|
||||
};
|
||||
|
||||
const openSaveModal = () => {
|
||||
if (selector === '') {
|
||||
return;
|
||||
}
|
||||
showModal(<SaveModal onSave={onSave} hideModal={hideModal} />, { right: true, width: 400 });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'w-full h-full p-2 flex flex-col gap-2'}>
|
||||
<div className={'flex items-center justify-between'}>
|
||||
<div className={'font-semibold text-xl'}>Element Selector</div>
|
||||
<CopyButton content={selector} />
|
||||
<div className="w-full h-full p-4 flex flex-col gap-2">
|
||||
<div className="flex flex-col items-center justify-between">
|
||||
<p>Select elements in the session play area to tag by class selector and filter sessions to verify their rendering.</p>
|
||||
|
||||
</div>
|
||||
<Input.TextArea value={selector} onChange={(e) => setSelector(e.target.value)} />
|
||||
|
||||
<CopyableTextArea selector={selector} setSelector={setSelector} />
|
||||
|
||||
<Button
|
||||
onClick={openSaveModal}
|
||||
type={'primary'}
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<ZoomInOutlined />}
|
||||
disabled={selector === ''}
|
||||
>
|
||||
Tag Element
|
||||
</Button>
|
||||
<div className={'text-disabled-text text-sm'}>
|
||||
Create and filter sessions by ‘watch elements’ to determine if they rendered or not.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(TagWatch);
|
||||
export default observer(TagWatch);
|
||||
|
|
@ -18,7 +18,7 @@ function RightBlock({
|
|||
switch (activeTab) {
|
||||
case 'EVENTS':
|
||||
return (
|
||||
<div className={cn('flex flex-col bg-white border-l', stl.panel)}>
|
||||
<div className={cn('flex flex-col border-l', stl.panel)}>
|
||||
<EventsBlock setActiveTab={setActiveTab} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ const Tabs = ({ tabs, active, onClick, border = true, className }: Props) => {
|
|||
return (
|
||||
<div className={cn(stl.tabs, className, { [stl.bordered]: border })} role="tablist">
|
||||
<Segmented
|
||||
className='w-full'
|
||||
size="small"
|
||||
value={active}
|
||||
options={tabs.map(({ key, text, hidden = false, disabled = false, iconComp = null }) => ({
|
||||
label: (
|
||||
|
|
@ -29,14 +31,14 @@ const Tabs = ({ tabs, active, onClick, border = true, className }: Props) => {
|
|||
onClick={() => {
|
||||
onClick(key);
|
||||
}}
|
||||
className={'font-semibold flex gap-1 items-center'}
|
||||
className={'font-medium flex gap-1 items-center hover:text-teal rounded-lg'}
|
||||
>
|
||||
{iconComp ? iconComp : <Icon size={16} color={'black'} name={iconMap[key as keyof typeof iconMap]} />}
|
||||
{iconComp ? iconComp : <Icon size={14} color="currentColor" style={{ fill: 'currentColor', strokeWidth:'0' }} name={iconMap[key as keyof typeof iconMap]} />}
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
),
|
||||
value: key,
|
||||
disabled: disabled,
|
||||
disabled: disabled,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -159,7 +159,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 })}
|
||||
className={cn('flex items-start w-full', { 'px-4': isLocation })}
|
||||
>
|
||||
<div style={{ minWidth: '16px' }}>
|
||||
{event.type && iconName ? (
|
||||
|
|
@ -169,20 +169,18 @@ const Event: React.FC<Props> = ({
|
|||
)}
|
||||
</div>
|
||||
<div className="ml-3 w-full">
|
||||
<div className="flex w-full items-first justify-between">
|
||||
<div className="flex w-full items-start">
|
||||
<div
|
||||
className="flex items-center w-full"
|
||||
className="flex flex-col justify-center items-start w-full"
|
||||
style={{ minWidth: '0' }}
|
||||
>
|
||||
<span
|
||||
className={cn(cls.title, { 'font-medium': isLocation })}
|
||||
>
|
||||
<span className={cn(cls.title, 'font-medium')}>
|
||||
{title}
|
||||
</span>
|
||||
{body && !isLocation && (
|
||||
<TextEllipsis
|
||||
maxWidth="60%"
|
||||
className="w-full ml-2 text-sm color-gray-medium"
|
||||
maxWidth="80%"
|
||||
className="w-full text-sm color-gray-medium"
|
||||
text={body}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -202,8 +200,7 @@ const Event: React.FC<Props> = ({
|
|||
{isLocation && (
|
||||
<div className="pt-1 px-4">
|
||||
<TextEllipsis
|
||||
maxWidth="80%"
|
||||
className="text-sm font-normal color-gray-medium"
|
||||
className="text-sm ms-8 font-normal color-gray-medium"
|
||||
text={body}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Icon, TextEllipsis } from 'UI';
|
|||
import Event from './Event';
|
||||
import NoteEvent from './NoteEvent';
|
||||
import stl from './eventGroupWrapper.module.css';
|
||||
import cn from 'classnames'
|
||||
|
||||
function EventGroupWrapper(props) {
|
||||
const { userStore } = useStore();
|
||||
|
|
@ -132,7 +133,7 @@ function EventGroupWrapper(props) {
|
|||
{isFirst && isLocation && event.referrer && (
|
||||
<TextEllipsis>
|
||||
<div className={stl.referrer}>
|
||||
Referrer: <span className={stl.url}>{safeRef}</span>
|
||||
Referrer: <span className={cn(stl.url, '!font-normal')}>{safeRef}</span>
|
||||
</div>
|
||||
</TextEllipsis>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Input, Button } from 'UI';
|
||||
import {Input, Button, Tooltip} from 'antd';
|
||||
import {CloseOutlined, SearchOutlined} from '@ant-design/icons';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
||||
function EventSearch(props) {
|
||||
const { player } = React.useContext(PlayerContext);
|
||||
|
||||
const { onChange, value, header, setActiveTab } = props;
|
||||
const { onChange, value, header, setActiveTab, eventsText } = props;
|
||||
|
||||
const toggleEvents = () => player.toggleEvents();
|
||||
|
||||
|
|
@ -16,25 +17,25 @@ function EventSearch(props) {
|
|||
<Input
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder="Filter"
|
||||
className="inset-0 w-full"
|
||||
placeholder={`Filter ${eventsText}`}
|
||||
className="w-full rounded-lg"
|
||||
name="query"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
wrapperClassName="w-full"
|
||||
style={{ height: '32px' }}
|
||||
autoComplete="off chromebugfix"
|
||||
prefix={<SearchOutlined />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="ml-2"
|
||||
icon="close"
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
setActiveTab('');
|
||||
toggleEvents();
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Close Panel" placement='bottom' >
|
||||
<Button
|
||||
className="ml-2"
|
||||
type='text'
|
||||
onClick={() => {
|
||||
setActiveTab('');
|
||||
toggleEvents();
|
||||
}}
|
||||
icon={<CloseOutlined />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ function EventsBlock(props: IProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className={cn(styles.header, 'p-4')}>
|
||||
<div className={cn(styles.header, 'py-4 px-2 bg-gradient-to-t from-transparent to-neutral-50 h-[57px]' )}>
|
||||
{uxtestingStore.isUxt() ? (
|
||||
<div style={{ width: 240, height: 130 }} className={'relative'}>
|
||||
<video
|
||||
|
|
@ -219,14 +219,14 @@ function EventsBlock(props: IProps) {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={cn(styles.hAndProgress, 'mt-3')}>
|
||||
<div className={cn(styles.hAndProgress, 'mt-0')}>
|
||||
<EventSearch
|
||||
onChange={write}
|
||||
setActiveTab={setActiveTab}
|
||||
value={query}
|
||||
eventsText={usedEvents.length ? `${usedEvents.length} Events` : '0 Events'}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 color-gray-medium">{eventsText}</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn('flex-1 pb-4', styles.eventsList)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Segmented } from 'antd';
|
||||
import {InfoCircleOutlined} from '@ant-design/icons'
|
||||
import cn from 'classnames';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { useEffect } from 'react';
|
||||
|
|
@ -343,13 +344,13 @@ function PanelComponent({
|
|||
) : null}
|
||||
</div>
|
||||
{isSpot ? null : (
|
||||
<div className="flex items-center h-20 mr-4 gap-2">
|
||||
<TabSelector />
|
||||
<TimelineZoomButton />
|
||||
<div className="flex items-center h-20 mr-4 gap-3">
|
||||
<FeatureSelection
|
||||
list={selectedFeatures}
|
||||
updateList={setSelectedFeatures}
|
||||
/>
|
||||
<TabSelector />
|
||||
<TimelineZoomButton />
|
||||
</div>
|
||||
)}
|
||||
</BottomBlock.Header>
|
||||
|
|
@ -366,7 +367,7 @@ function PanelComponent({
|
|||
style={{ height: '60px', minHeight: 'unset', padding: 0 }}
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<Icon name="info-circle" className="mr-2" size="18" />
|
||||
<InfoCircleOutlined size={18} />
|
||||
Select a debug option to visualize on timeline.
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { getTimelinePosition } from 'App/utils';
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import { Icon } from 'UI';
|
||||
import { InfoCircleOutlined} from '@ant-design/icons'
|
||||
import {Tooltip} from 'antd';
|
||||
import PerformanceGraph from '../PerformanceGraph';
|
||||
interface Props {
|
||||
list?: any[];
|
||||
|
|
@ -92,17 +94,20 @@ const EventRow = React.memo((props: Props) => {
|
|||
>
|
||||
<div
|
||||
className={cn(
|
||||
'uppercase text-sm flex items-center py-1',
|
||||
'uppercase text-sm flex items-center py-1 gap-1',
|
||||
props.noMargin ? '' : 'ml-2'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
style={{ zIndex: props.zIndex ? props.zIndex : undefined }}
|
||||
className="mr-2 leading-none"
|
||||
className="leading-none mt-0.5"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{message ? <RowInfo message={message} /> : null}
|
||||
|
||||
<Tooltip title={message} placement='left'>
|
||||
<InfoCircleOutlined className='text-neutral-400' />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="relative w-full" style={{ zIndex: props.zIndex ? props.zIndex : undefined }}>
|
||||
{isGraph ? (
|
||||
|
|
@ -124,7 +129,7 @@ const EventRow = React.memo((props: Props) => {
|
|||
);
|
||||
})
|
||||
) : (
|
||||
<div className={cn('color-gray-medium text-sm', props.noMargin ? '' : 'ml-4')}>
|
||||
<div className={cn('color-gray-medium text-xs', props.noMargin ? '' : 'ml-2')}>
|
||||
None captured.
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -134,11 +139,3 @@ const EventRow = React.memo((props: Props) => {
|
|||
});
|
||||
|
||||
export default EventRow;
|
||||
|
||||
function RowInfo({ message }: any) {
|
||||
return (
|
||||
<Tooltip title={message} delay={0}>
|
||||
<Icon name="info-circle" color="gray-medium" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Popover, Checkbox } from 'antd';
|
||||
import { Popover, Checkbox, Button } from 'antd';
|
||||
import {EyeInvisibleOutlined} from '@ant-design/icons';
|
||||
import { Icon } from 'UI'
|
||||
import Funnel from '@/types/funnel';
|
||||
|
||||
const NETWORK = 'NETWORK';
|
||||
const ERRORS = 'ERRORS';
|
||||
|
|
@ -59,7 +61,7 @@ function FeatureSelection(props: Props) {
|
|||
<Popover
|
||||
trigger="click"
|
||||
content={
|
||||
<div>
|
||||
<div className='flex flex-col gap-3'>
|
||||
<div
|
||||
className={'flex items-center gap-2 cursor-pointer'}
|
||||
onClick={() => toggleAllFeatures()}
|
||||
|
|
@ -81,10 +83,9 @@ function FeatureSelection(props: Props) {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<div className={'font-semibold flex items-center gap-2 text-main cursor-pointer'}>
|
||||
<Icon size={16} name={'funnel'} color={'main'} />
|
||||
<div>X-Ray Events</div>
|
||||
</div>
|
||||
<Button color='primary' size='small' type='text' className={'font-medium'} icon={<EyeInvisibleOutlined size={12} />} >
|
||||
Hide / Show
|
||||
</Button>
|
||||
</Popover>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { AreaChart, Area, ResponsiveContainer } from 'recharts';
|
||||
import {InfoCircleOutlined} from '@ant-design/icons'
|
||||
|
||||
interface Props {
|
||||
list: any;
|
||||
|
|
@ -57,10 +58,11 @@ const PerformanceGraph = React.memo((props: Props) => {
|
|||
{disabled ? (
|
||||
<div
|
||||
className={
|
||||
'absolute top-0 bottom-0 left-0 right-0 flex justify-center'
|
||||
'flex justify-start'
|
||||
}
|
||||
>
|
||||
<div className={'text-disabled-text decoration-dotted'}>Disabled for "All Tabs" View</div>
|
||||
<div className={'text-xs text-neutral-400 ps-2'}>
|
||||
Multi-tab performance overview is not available.</div>
|
||||
</div>
|
||||
) : null}
|
||||
<ResponsiveContainer height={35}>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export function FrustrationElement({ item, createEventClickHandler }: CommonProp
|
|||
const elData = getFrustration(item);
|
||||
return (
|
||||
<Tooltip
|
||||
placement={'right'}
|
||||
placement={'top'}
|
||||
title={
|
||||
<div className="">
|
||||
<b>{elData.name}</b>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Loader, Icon } from 'UI';
|
||||
import { Loader } from 'UI';
|
||||
import {Button, Tooltip} from 'antd';
|
||||
import {CloseOutlined} from '@ant-design/icons';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from 'App/mstore';
|
||||
import SelectorsList from './components/SelectorsList/SelectorsList';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { compareJsonObjects } from 'App/utils';
|
||||
|
||||
import Select from 'Shared/Select';
|
||||
import {Select, Form} from 'antd';
|
||||
|
||||
const JUMP_OFFSET = 1000;
|
||||
interface Props {
|
||||
|
|
@ -58,34 +60,29 @@ function PageInsightsPanel({ setActiveTab }: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-white">
|
||||
<div className="pb-3 flex items-center" style={{ maxWidth: '241px', paddingTop: '5px' }}>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-1 text-xl">Clicks</span>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
setActiveTab('');
|
||||
}}
|
||||
className="ml-auto flex items-center justify-center bg-white cursor-pointer"
|
||||
>
|
||||
<Icon name="close" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 flex items-center">
|
||||
<div className="mr-2 flex-shrink-0">In Page</div>
|
||||
<Select
|
||||
isSearchable={true}
|
||||
right
|
||||
placeholder="change"
|
||||
options={urlOptions}
|
||||
name="url"
|
||||
defaultValue={defaultValue}
|
||||
onChange={onPageSelect}
|
||||
id="change-dropdown"
|
||||
className="w-full"
|
||||
style={{ width: '100%' }}
|
||||
<div className="p-2 py-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-3 overflow-hidden">
|
||||
<div className="flex-shrink-0 font-medium">Page</div>
|
||||
<Form.Item name="url" className='mb-0 w-[176px]'>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="change"
|
||||
options={urlOptions}
|
||||
defaultValue={defaultValue}
|
||||
onChange={onPageSelect}
|
||||
id="change-dropdown"
|
||||
className="w-full rounded-lg max-w-[270px]"
|
||||
dropdownStyle={{ }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title="Close Panel" placement='bottomRight'>
|
||||
<Button
|
||||
className="ml-2"
|
||||
type='text'
|
||||
onClick={() => { setActiveTab(''); }}
|
||||
icon={<CloseOutlined />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Loader loading={loading}>
|
||||
<SelectorsList />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
.wrapper {
|
||||
padding: 10px;
|
||||
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
padding: 1rem;
|
||||
background-color: $gray-lightest;
|
||||
margin-bottom: 15px;
|
||||
|
||||
|
|
@ -18,8 +16,6 @@
|
|||
border-radius: 10px;
|
||||
background-color: $tealx;
|
||||
flex-shrink: 0;
|
||||
border: solid thin white;
|
||||
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -17,20 +17,20 @@ export default function SelectorCard({ index = 1, target, showContent }: Props)
|
|||
|
||||
return (
|
||||
// @ts-ignore TODO for Alex
|
||||
<div className={cn(stl.wrapper, { [stl.active]: showContent })} onClick={() => activeTarget(index)}>
|
||||
<div className={cn(stl.wrapper, 'rounded-xl', { [stl.active]: showContent })} onClick={() => activeTarget(index)}>
|
||||
<div className={stl.top}>
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip position="top" title="Rank of the most clicked element">
|
||||
<div className={stl.index}>{index + 1}</div>
|
||||
</Tooltip>
|
||||
<div className="truncate">{target.selector}</div>
|
||||
<div className="truncate font-mono">{target.selector}</div>
|
||||
</div>
|
||||
{showContent && (
|
||||
<div className={stl.counts}>
|
||||
<div>
|
||||
{target.count} Clicks - {target.percent}%
|
||||
{target.count} Click{target.count > 1 ? 's' : ''} - {target.percent}%
|
||||
</div>
|
||||
<div className="color-gray-medium">TOTAL CLICKS</div>
|
||||
<div className="text-neutral-400">TOTAL CLICKS</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
} from 'recharts';
|
||||
import { durationFromMsFormatted } from 'App/date';
|
||||
import { formatBytes } from 'App/utils';
|
||||
import {Tooltip as TooltipANT} from 'antd';
|
||||
|
||||
import stl from './performance.module.css';
|
||||
|
||||
|
|
@ -458,17 +459,32 @@ function Performance() {
|
|||
return (
|
||||
<BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
<div className="flex items-center w-full">
|
||||
<div className="font-semibold color-gray-medium mr-auto">Performance</div>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<Segmented options={[{ label: 'Current Tab', value: 'all' }]} />
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="font-semibold color-gray-medium mr-auto">Performance</div>
|
||||
<InfoLine>
|
||||
<InfoLine.Point
|
||||
label="Device Heap Size"
|
||||
value={formatBytes(userDeviceHeapSize)}
|
||||
display={true}
|
||||
/>
|
||||
</InfoLine>
|
||||
<InfoLine.Point
|
||||
label="Device Heap Size"
|
||||
value={formatBytes(userDeviceHeapSize)}
|
||||
display={true}
|
||||
/>
|
||||
</InfoLine>
|
||||
</div>
|
||||
|
||||
<div className={'flex items-center gap-3'}>
|
||||
<Segmented
|
||||
options={[
|
||||
{ label: (
|
||||
<TooltipANT title="Performance overview isn't supported across tabs.">
|
||||
<span>All Tabs</span>
|
||||
</TooltipANT>
|
||||
), value: 'all', disabled: true, },
|
||||
{ label: 'Current Tab', value: 'current' },
|
||||
]}
|
||||
defaultValue="current"
|
||||
size="small"
|
||||
className="rounded-full font-medium"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function TimelineZoomButton() {
|
|||
}, [])
|
||||
return (
|
||||
<Tooltip title="Select a portion of the timeline to view the x-ray and activity for that specific selection." placement='top'>
|
||||
<Button onClick={onClickHandler} size={'small'} className={'flex items-center font-semibold'}>
|
||||
<Button onClick={onClickHandler} size={'small'} className={'flex items-center font-medium'}>
|
||||
Focus Mode: {enabled ? 'On' : 'Off'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ import cn from 'classnames';
|
|||
import cls from './infoLine.module.css';
|
||||
|
||||
const InfoLine = ({ children }) => (
|
||||
<div className={ cls.info }>
|
||||
<div className={ cn(cls.info, 'text-sm')}>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
|
||||
const Point = ({ label = '', value = '', display=true, color, dotColor }) => display
|
||||
? <div className={ cls.infoPoint } style={{ color }}>
|
||||
? <div className={ cn(cls.infoPoint, 'text-sm') } style={{ color }}>
|
||||
{ dotColor != null && <div className={ cn(cls.dot, `bg-${dotColor}`) } /> }
|
||||
<span className={cls.label}>{ `${label}` }</span> { value }
|
||||
<span className={cn(cls.label, 'text-sm')}>{ `${label}` }</span> { value }
|
||||
</div>
|
||||
: null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
||||
import { LogLevel, ILog } from 'Player';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import { Tabs, Input, Icon, NoContent } from 'UI';
|
||||
import { Tabs, Icon, NoContent } from 'UI';
|
||||
import {Input} from 'antd';
|
||||
import {SearchOutlined, InfoCircleOutlined} from '@ant-design/icons';
|
||||
import cn from 'classnames';
|
||||
import ConsoleRow from '../ConsoleRow';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
|
@ -195,13 +197,13 @@ function ConsolePanel({
|
|||
<div className={'flex items-center gap-2'}>
|
||||
<TabSelector />
|
||||
<Input
|
||||
className="input-small h-8"
|
||||
className="rounded-lg"
|
||||
placeholder="Filter by keyword"
|
||||
icon="search"
|
||||
name="filter"
|
||||
height={28}
|
||||
onChange={onFilterChange}
|
||||
value={filter}
|
||||
size='small'
|
||||
prefix={<SearchOutlined className='text-neutral-400' />}
|
||||
/>
|
||||
</div>
|
||||
{/* @ts-ignore */}
|
||||
|
|
@ -210,8 +212,8 @@ function ConsolePanel({
|
|||
<BottomBlock.Content className="overflow-y-auto">
|
||||
<NoContent
|
||||
title={
|
||||
<div className="capitalize flex items-center mt-16">
|
||||
<Icon name="info-circle" className="mr-2" size="18" />
|
||||
<div className="capitalize flex items-center mt-16 gap-2">
|
||||
<InfoCircleOutlined size={18} />
|
||||
No Data
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorD
|
|||
import { useModal } from 'App/components/Modal';
|
||||
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
|
||||
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
|
||||
import {InfoCircleOutlined} from '@ant-design/icons'
|
||||
|
||||
const ALL = 'ALL';
|
||||
const INFO = 'INFO';
|
||||
|
|
@ -139,20 +140,20 @@ function MobileConsolePanel() {
|
|||
<Tabs tabs={TABS} active={activeTab} onClick={onTabClick} border={false} />
|
||||
</div>
|
||||
<Input
|
||||
className="input-small h-8"
|
||||
className="rounded-lg"
|
||||
placeholder="Filter by keyword"
|
||||
icon="search"
|
||||
name="filter"
|
||||
height={28}
|
||||
onChange={onFilterChange}
|
||||
value={filter}
|
||||
size='small'
|
||||
prefix={<SearchOutlined className='text-neutral-400' />}
|
||||
/>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content className="overflow-y-auto">
|
||||
<NoContent
|
||||
title={
|
||||
<div className="capitalize flex items-center mt-16">
|
||||
<Icon name="info-circle" className="mr-2" size="18" />
|
||||
<div className="capitalize flex items-center mt-16 gap-2">
|
||||
<InfoCircleOutlined size={18} />
|
||||
No Data
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ function ConsoleRow(props: Props) {
|
|||
<div
|
||||
style={style}
|
||||
className={cn(
|
||||
'border-b flex items-start gap-1 py-1 px-4 pe-8 overflow-hidden group relative',
|
||||
'border-b border-neutral-950/5 flex items-start gap-2 py-1 px-4 pe-8 overflow-hidden group relative',
|
||||
{
|
||||
info: !log.isYellow && !log.isRed,
|
||||
warn: log.isYellow,
|
||||
|
|
@ -61,7 +61,7 @@ function ConsoleRow(props: Props) {
|
|||
onClick={clickable ? () => (!!log.errorId ? props.onClick?.() : toggleExpand()) : undefined}
|
||||
>
|
||||
{logSource !== -1 && <TabTag tabNum={logSource} />}
|
||||
<Icon size="14" {...iconProps} />
|
||||
<Icon size="14" {...iconProps} className='mt-0.5' />
|
||||
<div key={log.key} data-scroll-item={log.isRed}>
|
||||
<div className="flex items-start text-sm">
|
||||
<div className={cn('flex items-start', { 'cursor-pointer underline decoration-dotted decoration-gray-400': !!log.errorId })}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import { shortDurationFromMs } from "App/date";
|
||||
import { Tooltip } from 'UI';
|
||||
import { CaretRightOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import { shortDurationFromMs } from 'App/date';
|
||||
|
||||
interface Props {
|
||||
onClick: any;
|
||||
|
|
@ -12,19 +14,24 @@ function JumpButton(props: Props) {
|
|||
return (
|
||||
<div className="absolute right-2 top-0 bottom-0 my-auto flex items-center">
|
||||
<Tooltip title={tooltip} disabled={!tooltip}>
|
||||
<div
|
||||
className="border cursor-pointer hidden group-hover:flex rounded bg-white text-xs items-center px-2 py-1 color-teal hover:shadow h-6"
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
className="hidden group-hover:flex rounded-lg text-xs p-1 py-0 gap-0 h-6"
|
||||
iconPosition="end"
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
props.onClick();
|
||||
}}
|
||||
icon={<CaretRightOutlined />}
|
||||
>
|
||||
<Icon name="caret-right-fill" size="12" color="teal" />
|
||||
<span>JUMP</span>
|
||||
</div>
|
||||
{props.time ? <div className={'block group-hover:hidden mr-2'}>
|
||||
{shortDurationFromMs(props.time)}
|
||||
</div> : null}
|
||||
JUMP
|
||||
</Button>
|
||||
{props.time ? (
|
||||
<div className={'block group-hover:hidden mr-2 text-sm'}>
|
||||
{shortDurationFromMs(props.time)}
|
||||
</div>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { ResourceType, Timed } from 'Player';
|
||||
import MobilePlayer from 'Player/mobile/IOSPlayer';
|
||||
import WebPlayer from 'Player/web/WebPlayer';
|
||||
import TabTag from "../TabTag";
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
|
|
@ -13,14 +12,16 @@ import {
|
|||
import { formatMs } from 'App/date';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { formatBytes } from 'App/utils';
|
||||
import { Icon, Input, NoContent, Tabs, Toggler, Tooltip } from 'UI';
|
||||
import { Icon, NoContent, Tabs } from 'UI';
|
||||
import { Tooltip, Input, Switch, Form } from 'antd';
|
||||
import { SearchOutlined, InfoCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
import FetchDetailsModal from 'Shared/FetchDetailsModal';
|
||||
import { WsChannel } from "App/player/web/messages";
|
||||
import { WsChannel } from 'App/player/web/messages';
|
||||
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import InfoLine from '../BottomBlock/InfoLine';
|
||||
import TabSelector from "../TabSelector";
|
||||
import TabSelector from '../TabSelector';
|
||||
import TimeTable from '../TimeTable';
|
||||
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
|
||||
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
|
||||
|
|
@ -134,7 +135,7 @@ function renderStatus({
|
|||
error?: string;
|
||||
}) {
|
||||
const displayedStatus = error ? (
|
||||
<Tooltip delay={0} title={error}>
|
||||
<Tooltip title={error}>
|
||||
<div
|
||||
style={{ width: 90 }}
|
||||
className={'overflow-hidden overflow-ellipsis'}
|
||||
|
|
@ -148,7 +149,7 @@ function renderStatus({
|
|||
return (
|
||||
<>
|
||||
{cached ? (
|
||||
<Tooltip title={'Served from cache'}>
|
||||
<Tooltip title={'Served from cache'} placement="top">
|
||||
<div className="flex items-center">
|
||||
<span className="mr-1">{displayedStatus}</span>
|
||||
<Icon name="wifi" size={16} />
|
||||
|
|
@ -161,11 +162,7 @@ function renderStatus({
|
|||
);
|
||||
}
|
||||
|
||||
function NetworkPanelCont({
|
||||
panelHeight,
|
||||
}: {
|
||||
panelHeight: number;
|
||||
}) {
|
||||
function NetworkPanelCont({ panelHeight }: { panelHeight: number }) {
|
||||
const { player, store } = React.useContext(PlayerContext);
|
||||
const { sessionStore, uiPlayerStore } = useStore();
|
||||
|
||||
|
|
@ -194,14 +191,27 @@ function NetworkPanelCont({
|
|||
} else {
|
||||
const fetchList = tabValues.flatMap((tab) => tab.fetchList);
|
||||
const resourceList = tabValues.flatMap((tab) => tab.resourceList);
|
||||
const fetchListNow = tabValues.flatMap((tab) => tab.fetchListNow).filter(Boolean);
|
||||
const resourceListNow = tabValues.flatMap((tab) => tab.resourceListNow).filter(Boolean);
|
||||
const fetchListNow = tabValues
|
||||
.flatMap((tab) => tab.fetchListNow)
|
||||
.filter(Boolean);
|
||||
const resourceListNow = tabValues
|
||||
.flatMap((tab) => tab.resourceListNow)
|
||||
.filter(Boolean);
|
||||
const websocketList = tabValues.flatMap((tab) => tab.websocketList);
|
||||
const websocketListNow = tabValues.flatMap((tab) => tab.websocketListNow).filter(Boolean);
|
||||
return { fetchList, resourceList, fetchListNow, resourceListNow, websocketList, websocketListNow };
|
||||
const websocketListNow = tabValues
|
||||
.flatMap((tab) => tab.websocketListNow)
|
||||
.filter(Boolean);
|
||||
return {
|
||||
fetchList,
|
||||
resourceList,
|
||||
fetchListNow,
|
||||
resourceListNow,
|
||||
websocketList,
|
||||
websocketListNow,
|
||||
};
|
||||
}
|
||||
}, [currentTab, tabStates, dataSource, tabValues]);
|
||||
const getTabNum = (tab: string) => (tabsArr.findIndex((t) => t === tab) + 1);
|
||||
const getTabNum = (tab: string) => tabsArr.findIndex((t) => t === tab) + 1;
|
||||
|
||||
return (
|
||||
<NetworkPanelComp
|
||||
|
|
@ -223,11 +233,7 @@ function NetworkPanelCont({
|
|||
);
|
||||
}
|
||||
|
||||
function MobileNetworkPanelCont({
|
||||
panelHeight,
|
||||
}: {
|
||||
panelHeight: number;
|
||||
}) {
|
||||
function MobileNetworkPanelCont({ panelHeight }: { panelHeight: number }) {
|
||||
const { player, store } = React.useContext(MobilePlayerContext);
|
||||
const { uiPlayerStore, sessionStore } = useStore();
|
||||
const startedAt = sessionStore.current.startedAt;
|
||||
|
|
@ -331,7 +337,9 @@ export const NetworkPanelComp = observer(
|
|||
getTabNum,
|
||||
showSingleTab,
|
||||
}: Props) => {
|
||||
const [selectedWsChannel, setSelectedWsChannel] = React.useState<WsChannel[] | null>(null)
|
||||
const [selectedWsChannel, setSelectedWsChannel] = React.useState<
|
||||
WsChannel[] | null
|
||||
>(null);
|
||||
const { showModal } = useModal();
|
||||
const [showOnlyErrors, setShowOnlyErrors] = useState(false);
|
||||
|
||||
|
|
@ -487,10 +495,10 @@ export const NetworkPanelComp = observer(
|
|||
const showDetailsModal = (item: any) => {
|
||||
if (item.type === 'websocket') {
|
||||
const socketMsgList = websocketList.filter(
|
||||
(ws) => ws.channelName === item.channelName
|
||||
);
|
||||
(ws) => ws.channelName === item.channelName
|
||||
);
|
||||
|
||||
return setSelectedWsChannel(socketMsgList)
|
||||
return setSelectedWsChannel(socketMsgList);
|
||||
}
|
||||
setIsDetailsModalActive(true);
|
||||
showModal(
|
||||
|
|
@ -552,16 +560,23 @@ export const NetworkPanelComp = observer(
|
|||
dataKey: 'duration',
|
||||
render: renderDuration,
|
||||
},
|
||||
]
|
||||
];
|
||||
if (!showSingleTab) {
|
||||
cols.unshift({
|
||||
label: 'Source',
|
||||
width: 64,
|
||||
render: (r: Record<string, any>) => <div>Tab {getTabNum?.(r.tabId) ?? 0}</div>,
|
||||
})
|
||||
cols.unshift({
|
||||
label: 'Source',
|
||||
width: 64,
|
||||
render: (r: Record<string, any>) => (
|
||||
<Tooltip title="@Nikita show tab title here..." placement="left">
|
||||
<div className="bg-gray-light rounded-full min-w-5 min-h-5 w-5 h-5 flex items-center justify-center text-xs cursor-default">
|
||||
{' '}
|
||||
{getTabNum?.(r.tabId) ?? 0}
|
||||
</div>
|
||||
</Tooltip>
|
||||
),
|
||||
});
|
||||
}
|
||||
return cols
|
||||
}, [showSingleTab])
|
||||
return cols;
|
||||
}, [showSingleTab]);
|
||||
|
||||
return (
|
||||
<BottomBlock
|
||||
|
|
@ -588,26 +603,36 @@ export const NetworkPanelComp = observer(
|
|||
<div className={'flex items-center gap-2'}>
|
||||
<TabSelector />
|
||||
<Input
|
||||
className="input-small"
|
||||
className="rounded-lg"
|
||||
placeholder="Filter by name, type, method or value"
|
||||
icon="search"
|
||||
name="filter"
|
||||
onChange={onFilterChange}
|
||||
height={28}
|
||||
width={280}
|
||||
value={filter}
|
||||
size="small"
|
||||
prefix={<SearchOutlined className="text-neutral-400" />}
|
||||
/>
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<div className="flex items-center justify-between px-4 border-b bg-teal/5 h-8">
|
||||
<div>
|
||||
<Toggler
|
||||
checked={showOnlyErrors}
|
||||
name="show-errors-only"
|
||||
onChange={() => setShowOnlyErrors(!showOnlyErrors)}
|
||||
label="4xx-5xx Only"
|
||||
/>
|
||||
<Form.Item name="show-errors-only" className="mb-0">
|
||||
<label
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
checked={showOnlyErrors}
|
||||
onChange={() => setShowOnlyErrors(!showOnlyErrors)}
|
||||
size="small"
|
||||
/>
|
||||
<span className="text-sm ms-2">4xx-5xx Only</span>
|
||||
</label>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<InfoLine>
|
||||
<InfoLine.Point
|
||||
|
|
@ -647,8 +672,8 @@ export const NetworkPanelComp = observer(
|
|||
</div>
|
||||
<NoContent
|
||||
title={
|
||||
<div className="capitalize flex items-center">
|
||||
<Icon name="info-circle" className="mr-2" size="18" />
|
||||
<div className="capitalize flex items-center gap-2">
|
||||
<InfoCircleOutlined size={18} />
|
||||
No Data
|
||||
</div>
|
||||
}
|
||||
|
|
@ -675,7 +700,10 @@ export const NetworkPanelComp = observer(
|
|||
{tableCols}
|
||||
</TimeTable>
|
||||
{selectedWsChannel ? (
|
||||
<WSPanel socketMsgList={selectedWsChannel} onClose={() => setSelectedWsChannel(null)} />
|
||||
<WSPanel
|
||||
socketMsgList={selectedWsChannel}
|
||||
onClose={() => setSelectedWsChannel(null)}
|
||||
/>
|
||||
) : null}
|
||||
</NoContent>
|
||||
</BottomBlock.Content>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Timed } from 'Player';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Tabs, Input, NoContent, Icon } from 'UI';
|
||||
import { Tabs, NoContent, Icon } from 'UI';
|
||||
import {Input} from 'antd';
|
||||
import {SearchOutlined, InfoCircleOutlined} from '@ant-design/icons';
|
||||
import { PlayerContext, MobilePlayerContext } from 'App/components/Session/playerContext';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
|
|
@ -10,7 +12,7 @@ import { typeList } from 'Types/session/stackEvent';
|
|||
import StackEventRow from 'Shared/DevTools/StackEventRow';
|
||||
|
||||
import StackEventModal from '../StackEventModal';
|
||||
import { Segmented } from 'antd'
|
||||
import { Segmented, Tooltip } from 'antd'
|
||||
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
|
||||
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
|
||||
import { VList, VListHandle } from 'virtua';
|
||||
|
|
@ -177,23 +179,36 @@ const EventsPanel = observer(({
|
|||
/>
|
||||
</div>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<Segmented options={[{ label: 'All Tabs', value: 'all' }]} />
|
||||
<Segmented
|
||||
options={[
|
||||
{ label: 'All Tabs', value: 'all', },
|
||||
{ label: (
|
||||
<Tooltip title="Stack Events overview is available only for all tabs combined.">
|
||||
<span>Current Tab</span>
|
||||
</Tooltip>),
|
||||
value: 'current', disabled: true},
|
||||
]}
|
||||
defaultValue="all"
|
||||
size="small"
|
||||
className="rounded-full font-medium"
|
||||
/>
|
||||
<Input
|
||||
className="input-small h-8"
|
||||
className="rounded-lg"
|
||||
placeholder="Filter by keyword"
|
||||
icon="search"
|
||||
name="filter"
|
||||
height={28}
|
||||
onChange={onFilterChange}
|
||||
value={filter}
|
||||
size='small'
|
||||
prefix={<SearchOutlined className='text-neutral-400' />}
|
||||
/>
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content className="overflow-y-auto">
|
||||
<NoContent
|
||||
title={
|
||||
<div className="capitalize flex items-center mt-16">
|
||||
<Icon name="info-circle" className="mr-2" size="18" />
|
||||
<div className="capitalize flex items-center mt-16 gap-2">
|
||||
<InfoCircleOutlined size={18} />
|
||||
No Data
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ function TabSelector() {
|
|||
uiPlayerStore.changeDataSource(value)
|
||||
}
|
||||
return (
|
||||
<Segmented options={options} value={currentValue} onChange={onChange} />
|
||||
<Segmented options={options} value={currentValue} onChange={onChange} className='font-medium rounded-lg' size='small' />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
|
||||
function TabTag({ tabNum }: { tabNum?: React.ReactNode }) {
|
||||
return (
|
||||
<div className={'w-fit px-2 border border-gray-light rounded text-sm whitespace-nowrap'}>
|
||||
|
||||
<Tooltip title="@Nikita show tab title here..." placement='left'>
|
||||
<div className={'bg-gray-light rounded-full min-w-5 min-h-5 w-5 h-5 flex items-center justify-center text-xs cursor-default'}>
|
||||
{tabNum}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'dev-row border-b border-color-gray-light-shade group items-center',
|
||||
'dev-row border-b border-neutral-950/5 group items-center text-sm',
|
||||
stl.row,
|
||||
{
|
||||
[stl.hoverable]: hoverable,
|
||||
|
|
@ -215,7 +215,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
|
|||
{columns
|
||||
.filter((i: any) => !i.hidden)
|
||||
.map(({ dataKey, render, width, label }) => (
|
||||
<div key={parseInt(label.replace(' ', '')+dataKey, 36)} className={cn(stl.cell, 'overflow-ellipsis overflow-hidden')} style={{ width: `${width}px` }}>
|
||||
<div key={parseInt(label.replace(' ', '')+dataKey, 36)} className={cn(stl.cell, 'overflow-ellipsis overflow-hidden !py-0.5')} style={{ width: `${width}px` }}>
|
||||
{render
|
||||
? render(row)
|
||||
: row[dataKey || ''] || <i className="color-gray-light">{'empty'}</i>}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,11 @@
|
|||
import React from 'react';
|
||||
// import Select from 'Shared/Select';
|
||||
import { Dropdown, MenuProps, Select, Space } from 'antd';
|
||||
import { DownOutlined, SmileOutlined } from '@ant-design/icons';
|
||||
import { MenuProps, Select } from 'antd';
|
||||
|
||||
interface Props {
|
||||
payload: any;
|
||||
}
|
||||
|
||||
function NodeDropdown(props: Props) {
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<a target='_blank' rel='noopener noreferrer' href='https://www.antgroup.com'>
|
||||
1st menu item
|
||||
</a>
|
||||
)
|
||||
}
|
||||
];
|
||||
return (
|
||||
<Select style={{ width: 120 }} placeholder='Slect Event' dropdownStyle={{
|
||||
border: 'none'
|
||||
|
|
|
|||
|
|
@ -436,3 +436,7 @@ p {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ant-segmented-item{
|
||||
border-radius: .5rem !important;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue