change(ui): sort and pick events around selected time, add components around picking, allow selecting pick range etc
This commit is contained in:
parent
a68230359c
commit
e3b924a046
14 changed files with 368 additions and 105 deletions
|
|
@ -69,7 +69,7 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps
|
|||
}
|
||||
|
||||
bugReportStore.updateReportDefaults(defaults)
|
||||
bugReportStore.setSteps(mapEvents(events))
|
||||
bugReportStore.setDefaultSteps(mapEvents(events))
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col p-4 gap-4 bg-white overflow-y-scroll"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ function Comments() {
|
|||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (inputRef.current && bugReportStore.isTitleEdit) {
|
||||
if (inputRef.current && bugReportStore.isCommentEdit) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [bugReportStore.isCommentEdit]);
|
||||
|
|
@ -26,13 +26,13 @@ function Comments() {
|
|||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<SectionTitle>Comments</SectionTitle>{' '}
|
||||
<SectionTitle>Comments</SectionTitle>
|
||||
<div className="text-disabled-text mb-2">(Optional)</div>
|
||||
</div>
|
||||
{bugReportStore.isCommentEdit ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
name="name"
|
||||
name="reportComments"
|
||||
placeholder="Comment..."
|
||||
rows={3}
|
||||
autoFocus
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import SectionTitle from './SectionTitle'
|
||||
import React from 'react';
|
||||
import SectionTitle from './SectionTitle';
|
||||
|
||||
interface EnvObj {
|
||||
Device: string;
|
||||
|
|
@ -10,7 +10,13 @@ interface EnvObj {
|
|||
Rev?: string;
|
||||
}
|
||||
|
||||
export default function MetaInfo({ envObject, metadata }: { envObject: EnvObj, metadata: Record<string, any> }) {
|
||||
export default function MetaInfo({
|
||||
envObject,
|
||||
metadata,
|
||||
}: {
|
||||
envObject: EnvObj;
|
||||
metadata: Record<string, any>;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
|
|
@ -26,15 +32,17 @@ export default function MetaInfo({ envObject, metadata }: { envObject: EnvObj, m
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<SectionTitle>Metadata</SectionTitle>
|
||||
{Object.keys(metadata).map((meta) => (
|
||||
<div key={meta} className="flex items-center rounded overflow-hidden bg-gray-lightest">
|
||||
<div className="bg-gray-light-shade py-1 px-2">{meta}</div>
|
||||
<div className="py-1 px-2 text-gray-medium">{metadata[meta]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{Object.keys(metadata).length > 0 ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<SectionTitle>Metadata</SectionTitle>
|
||||
{Object.keys(metadata).map((meta) => (
|
||||
<div key={meta} className="flex items-center rounded overflow-hidden bg-gray-lightest">
|
||||
<div className="bg-gray-light-shade py-1 px-2">{meta}</div>
|
||||
<div className="py-1 px-2 text-gray-medium">{metadata[meta]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function ReportTitle() {
|
|||
{bugReportStore.isTitleEdit ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
name="name"
|
||||
name="reportTitle"
|
||||
className="rounded fluid border-0 -mx-2 px-2 h-8 text-2xl"
|
||||
value={bugReportStore.reportTitle}
|
||||
onChange={(e) => bugReportStore.setTitle(e.target.value)}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import React from 'react';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import cn from 'classnames';
|
||||
import { Icon } from 'UI';
|
||||
import EventRow from 'App/components/Session_/OverviewPanel/components/EventRow';
|
||||
import TimelinePointer from 'App/components/Session_/OverviewPanel/components/TimelinePointer';
|
||||
import { RADIUS } from '../utils';
|
||||
import SectionTitle from './SectionTitle';
|
||||
import { Step as IStep } from '../types';
|
||||
|
||||
const STEP_NAMES = { CLICKRAGE: 'Multiple click', CLICK: 'Clicked', LOCATION: 'Visited' };
|
||||
import XRay from './StepsComponents/XRay';
|
||||
import StepRenderer from './StepsComponents/StepRenderer';
|
||||
import StepRadius from './StepsComponents/StepRadius'
|
||||
|
||||
interface Props {
|
||||
xrayProps: {
|
||||
|
|
@ -22,90 +19,62 @@ interface Props {
|
|||
|
||||
function Steps({ xrayProps }: Props) {
|
||||
const { bugReportStore } = useStore();
|
||||
const [stepPickRadius, setRadius] = React.useState(RADIUS);
|
||||
const [timePointer, setPointer] = React.useState(0);
|
||||
const xrayContainer = React.useRef<HTMLDivElement>();
|
||||
const { resourceList, exceptionsList, eventsList, endTime } = xrayProps;
|
||||
|
||||
const resources = {
|
||||
NETWORK: resourceList,
|
||||
ERRORS: exceptionsList,
|
||||
CLICKRAGE: eventsList.filter((item: any) => item.type === 'CLICKRAGE'),
|
||||
const shouldShowEventReset = bugReportStore.chosenEventSteps.length > 0;
|
||||
|
||||
const handleStepsSelection = () => {
|
||||
if (shouldShowEventReset) {
|
||||
return clearEventSelection();
|
||||
}
|
||||
if (timePointer > 0) {
|
||||
// temp ?
|
||||
return bugReportStore.setSteps(bugReportStore.sessionEventSteps);
|
||||
} else {
|
||||
bugReportStore.setSteps(bugReportStore.sessionEventSteps);
|
||||
}
|
||||
};
|
||||
|
||||
const pickEventRadius = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
setPointer(e.clientX - xrayContainer.current?.getBoundingClientRect().left);
|
||||
const clearEventSelection = () => {
|
||||
setPointer(0);
|
||||
bugReportStore.resetSteps();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SectionTitle>Steps to reproduce</SectionTitle>
|
||||
|
||||
<div className="mt-2 text-gray-dark">XRAY</div>
|
||||
<div
|
||||
className="relative"
|
||||
onClick={pickEventRadius}
|
||||
ref={xrayContainer}
|
||||
style={{ background: timePointer > 0 ? 'rgba(57, 78, 255, 0.07)' : undefined }}
|
||||
>
|
||||
{timePointer > 0 ? (
|
||||
<div
|
||||
className="absolute h-full bg-white"
|
||||
style={{ zIndex: 19, width: 41, left: timePointer - 20, pointerEvents: 'none' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: 1,
|
||||
border: '1px dashed rgba(0,0,0, 0.5)',
|
||||
left: 20,
|
||||
position: 'absolute',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{Object.keys(resources).map(feature => (
|
||||
<div
|
||||
key={feature}
|
||||
className="border-b last:border-none z-20 -mx-4"
|
||||
>
|
||||
<EventRow
|
||||
title={feature}
|
||||
// @ts-ignore
|
||||
list={resources[feature]}
|
||||
zIndex={20}
|
||||
renderElement={(pointer: any) => (
|
||||
<TimelinePointer noClick pointer={pointer} type={feature} />
|
||||
)}
|
||||
endTime={endTime}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<XRay
|
||||
xrayProps={xrayProps}
|
||||
timePointer={timePointer}
|
||||
clearEventSelection={clearEventSelection}
|
||||
setPointer={setPointer}
|
||||
stepPickRadius={stepPickRadius}
|
||||
/>
|
||||
|
||||
<div className="mt-4 mb-2 text-gray-dark">STEPS</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{bugReportStore.sessionEventSteps.map((step, ind) => (
|
||||
<React.Fragment key={step.key}>
|
||||
<Step step={step} ind={ind} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mt-4 mb-2 text-gray-dark flex items-center gap-4">
|
||||
STEPS
|
||||
|
||||
function Step({ step, ind }: { step: IStep; ind: number }) {
|
||||
return (
|
||||
<div className="py-1 px-2 flex items-center gap-2 w-full rounded hover:bg-figmaColors-secondary-outlined-hover-background">
|
||||
<div className="rounded-3xl px-4 bg-gray-lightest">{ind + 1}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* @ts-ignore */}
|
||||
<Icon name={step.icon} size={16} color="gray-darkest" />
|
||||
{/* @ts-ignore */}
|
||||
<div className="font-semibold">{STEP_NAMES[step.type]}</div>
|
||||
<div className="text-gray-medium">{step.details}</div>
|
||||
{timePointer > 0 ? <StepRadius pickRadius={stepPickRadius} setRadius={setRadius} /> : null}
|
||||
</div>
|
||||
<div className="text-blue cursor-pointer" onClick={handleStepsSelection}>
|
||||
{!shouldShowEventReset ? (
|
||||
<span>Add {timePointer > 0 ? '' : 'All'} Steps</span>
|
||||
) : (
|
||||
<span>Reset</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<StepRenderer
|
||||
isDefault={bugReportStore.chosenEventSteps.length === 0}
|
||||
steps={
|
||||
bugReportStore.chosenEventSteps.length === 0
|
||||
? bugReportStore.sessionEventSteps
|
||||
: bugReportStore.chosenEventSteps
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { Step as IStep } from '../../types';
|
||||
const STEP_NAMES = { CLICKRAGE: 'Multiple click', CLICK: 'Clicked', LOCATION: 'Visited' };
|
||||
import { useStore } from 'App/mstore';
|
||||
import cn from 'classnames';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
function Step({ step, ind, isDefault }: { step: IStep; ind: number; isDefault?: boolean }) {
|
||||
const { bugReportStore } = useStore();
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'py-1 px-2 flex items-center gap-2 w-full rounded',
|
||||
isDefault ? '' : 'hover:bg-figmaColors-secondary-outlined-hover-background group'
|
||||
)}
|
||||
>
|
||||
<div className="rounded-3xl px-4 bg-gray-lightest">{ind + 1}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* @ts-ignore */}
|
||||
<Icon name={step.icon} size={16} color="gray-darkest" />
|
||||
<div className="px-2 text-disabled-text rounded bg-light-blue-bg">{Duration.fromMillis(step.time).toFormat('hh:mm:ss')}</div>
|
||||
{/* @ts-ignore */}
|
||||
<div className="font-semibold">{STEP_NAMES[step.type]}</div>
|
||||
<div className="text-gray-medium">{step.details}</div>
|
||||
</div>
|
||||
<div className="hidden group-hover:flex items-center ml-auto gap-4">
|
||||
<Icon name="plus" size={16} className="cursor-pointer hover:fill-gray-darkest" />
|
||||
<div onClick={() => bugReportStore.removeStep(step)}>
|
||||
<Icon name="trash" size={16} className="cursor-pointer hover:fill-gray-darkest" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Step;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { Tooltip } from 'react-tippy'
|
||||
|
||||
interface Props {
|
||||
pickRadius: number;
|
||||
setRadius: (v: number) => void;
|
||||
}
|
||||
|
||||
function StepRadius({ pickRadius, setRadius }: Props) {
|
||||
return (
|
||||
<div className="w-full flex items-center gap-4">
|
||||
<div className="border-b border-dotted border-gray-medium cursor-help">
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip html={<span>Closest step to the selected timestamp ± {pickRadius}.</span>}>
|
||||
<span>± {pickRadius}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div
|
||||
className="rounded px-2 bg-light-blue-bg cursor-pointer hover:bg-teal-light"
|
||||
onClick={() => setRadius(pickRadius + 1)}
|
||||
>
|
||||
+1
|
||||
</div>
|
||||
<div
|
||||
className="rounded px-2 bg-light-blue-bg cursor-pointer hover:bg-teal-light"
|
||||
onClick={() => (pickRadius > 1 ? setRadius(pickRadius - 1) : null)}
|
||||
>
|
||||
-1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StepRadius;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react'
|
||||
import Step from './EventStep';
|
||||
import { Step as IStep } from '../../types';
|
||||
|
||||
function StepRenderer(props: { steps: IStep[]; isDefault: boolean }) {
|
||||
const stepAmount = props.steps.length;
|
||||
const shouldSkip = stepAmount > 2;
|
||||
if (props.isDefault && shouldSkip) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 opacity-50">
|
||||
<Step step={props.steps[0]} ind={1} isDefault />
|
||||
<div className="ml-4"> + {stepAmount - 2} Steps</div>
|
||||
<Step step={props.steps[stepAmount - 1]} ind={stepAmount} isDefault />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{props.steps.map((step, ind) => (
|
||||
<React.Fragment key={step.key}>
|
||||
<Step step={step} ind={ind} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StepRenderer
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
import React from 'react';
|
||||
import { Duration } from 'luxon';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Icon } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { INDEXES } from 'App/constants/zindex';
|
||||
import TimelinePointer from 'App/components/Session_/OverviewPanel/components/TimelinePointer';
|
||||
import EventRow from 'App/components/Session_/OverviewPanel/components/EventRow';
|
||||
import { selectEventSteps } from '../../utils';
|
||||
|
||||
interface IXRay {
|
||||
xrayProps: {
|
||||
currentLocation: Record<string, any>[];
|
||||
resourceList: Record<string, any>[];
|
||||
exceptionsList: Record<string, any>[];
|
||||
eventsList: Record<string, any>[];
|
||||
endTime: number;
|
||||
};
|
||||
timePointer: number;
|
||||
stepPickRadius: number;
|
||||
clearEventSelection: () => void;
|
||||
setPointer: (time: number) => void;
|
||||
}
|
||||
|
||||
function XRay({ xrayProps, timePointer, stepPickRadius, clearEventSelection, setPointer }: IXRay) {
|
||||
const [selectedTime, setTime] = React.useState(0);
|
||||
const xrayContainer = React.useRef<HTMLDivElement>();
|
||||
const { bugReportStore } = useStore();
|
||||
|
||||
const { resourceList, exceptionsList, eventsList, endTime } = xrayProps;
|
||||
|
||||
const resources = {
|
||||
NETWORK: resourceList,
|
||||
ERRORS: exceptionsList,
|
||||
CLICKRAGE: eventsList.filter((item: any) => item.type === 'CLICKRAGE'),
|
||||
};
|
||||
|
||||
const pickEventRadius = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const pos = e.clientX - xrayContainer.current?.getBoundingClientRect().left;
|
||||
const percent = pos / xrayContainer.current?.getBoundingClientRect().width;
|
||||
const targetTime = percent * endTime;
|
||||
const selectedSteps = selectEventSteps(
|
||||
bugReportStore.sessionEventSteps,
|
||||
targetTime,
|
||||
stepPickRadius
|
||||
);
|
||||
|
||||
setTime(targetTime);
|
||||
setPointer(e.clientX - xrayContainer.current?.getBoundingClientRect().left);
|
||||
bugReportStore.setSteps(selectedSteps);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (timePointer > 0 && selectedTime > 0 && bugReportStore.chosenEventSteps) {
|
||||
const selectedSteps = selectEventSteps(
|
||||
bugReportStore.sessionEventSteps,
|
||||
selectedTime,
|
||||
stepPickRadius
|
||||
);
|
||||
|
||||
bugReportStore.setSteps(selectedSteps);
|
||||
}
|
||||
}, [stepPickRadius])
|
||||
|
||||
const shouldShowPointerReset = timePointer > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between my-2">
|
||||
<div className=" text-gray-dark">
|
||||
XRAY
|
||||
{timePointer > 0 ? (
|
||||
<span className="text-disabled-text ml-2">
|
||||
{Duration.fromMillis(selectedTime).toFormat('hh:mm:ss')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{!shouldShowPointerReset ? (
|
||||
<div className="flex items-center gap-2 px-2 py-1 rounded bg-light-blue-bg">
|
||||
<Icon name="info-circle" size={16} />
|
||||
<div>
|
||||
Click anywhere on <span className="font-semibold">X-RAY</span> to drilldown and add
|
||||
steps
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-blue py-1 cursor-pointer" onClick={clearEventSelection}>
|
||||
Clear Selection
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="relative cursor-pointer"
|
||||
onClick={pickEventRadius}
|
||||
ref={xrayContainer}
|
||||
style={{ background: timePointer > 0 ? 'rgba(57, 78, 255, 0.07)' : undefined }}
|
||||
>
|
||||
{timePointer > 0 ? (
|
||||
<div
|
||||
className="absolute h-full bg-white"
|
||||
style={{
|
||||
zIndex: INDEXES.BUG_REPORT_PICKER,
|
||||
width: 41,
|
||||
left: timePointer - 20,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: 0,
|
||||
border: '1px dashed rgba(0,0,0, 0.5)',
|
||||
left: 20,
|
||||
position: 'absolute',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{Object.keys(resources).map((feature) => (
|
||||
<div key={feature} className="border-b-2 last:border-none z-20">
|
||||
<EventRow
|
||||
title={feature}
|
||||
// @ts-ignore
|
||||
list={resources[feature]}
|
||||
zIndex={INDEXES.BUG_REPORT}
|
||||
noMargin
|
||||
renderElement={(pointer: any) => (
|
||||
<TimelinePointer noClick pointer={pointer} type={feature} />
|
||||
)}
|
||||
endTime={endTime}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(XRay);
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { Step } from './types'
|
||||
|
||||
const TYPES = { CLICKRAGE: 'CLICKRAGE', CLICK: 'CLICK', LOCATION: 'LOCATION' }
|
||||
export const RADIUS = 3
|
||||
|
||||
export function mapEvents(events: Record<string,any>[]): Step[] {
|
||||
const steps: Step[] = []
|
||||
|
|
@ -39,3 +40,30 @@ export function mapEvents(events: Record<string,any>[]): Step[] {
|
|||
|
||||
return steps
|
||||
}
|
||||
|
||||
export function getClosestEventStep(time: number, arr: Step[]) {
|
||||
let mid;
|
||||
let low = 0;
|
||||
let high = arr.length - 1;
|
||||
while (high - low > 1) {
|
||||
mid = Math.floor ((low + high) / 2);
|
||||
if (arr[mid].time < time) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
if (time - arr[low].time <= arr[high].time - time) {
|
||||
return { targetStep: arr[low], index: low } ;
|
||||
}
|
||||
return { targetStep: arr[high], index: high } ;
|
||||
}
|
||||
|
||||
export const selectEventSteps = (steps: Step[], targetTime: number, radius: number) => {
|
||||
const { targetStep, index } = getClosestEventStep(targetTime, steps)
|
||||
|
||||
const stepsBeforeEvent = steps.slice(index - radius, index)
|
||||
const stepsAfterEvent = steps.slice(index + 1, index + 1 + radius)
|
||||
|
||||
return [...stepsBeforeEvent, targetStep, ...stepsAfterEvent]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ interface Props {
|
|||
renderElement?: (item: any) => React.ReactNode;
|
||||
isGraph?: boolean;
|
||||
zIndex?: number;
|
||||
noMargin?: boolean;
|
||||
|
||||
}
|
||||
const EventRow = React.memo((props: Props) => {
|
||||
|
|
@ -31,22 +32,22 @@ const EventRow = React.memo((props: Props) => {
|
|||
|
||||
return (
|
||||
<div className={cn('w-full flex flex-col py-2', className)} style={{ height: isGraph ? 60 : 50 }}>
|
||||
<div className="uppercase color-gray-medium ml-4 text-sm flex items-center py-1">
|
||||
<div className={cn("uppercase color-gray-medium text-sm flex items-center py-1", props.noMargin ? '' : 'ml-4' )}>
|
||||
<div style={{ zIndex: props.zIndex ? props.zIndex : undefined }} className="mr-2 leading-none">{title}</div>
|
||||
{message ? <RowInfo zIndex={props.zIndex} message={message} /> : null}
|
||||
</div>
|
||||
<div className="relative w-full">
|
||||
<div className="relative w-full" style={{ zIndex: props.zIndex ? props.zIndex : undefined }}>
|
||||
{isGraph ? (
|
||||
<PerformanceGraph list={list} />
|
||||
) : (
|
||||
_list.length > 0 ? _list.map((item: any, index: number) => {
|
||||
return (
|
||||
<div key={index} className="absolute" style={{ left: item.left + '%', zIndex: props.zIndex ? props.zIndex : undefined }}>
|
||||
<div key={index} className="absolute" style={{ left: `clamp(0%, calc(${item.left}% - 7px), calc(100% - 14px))`, zIndex: props.zIndex ? props.zIndex : undefined }}>
|
||||
{props.renderElement ? props.renderElement(item) : null}
|
||||
</div>
|
||||
);
|
||||
}) : (
|
||||
<div className="ml-4 color-gray-medium text-sm pt-2">None captured.</div>
|
||||
<div className={cn("color-gray-medium text-sm pt-2", props.noMargin ? '' : 'ml-4')}>None captured.</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
export const INDEXES = {
|
||||
PLAYER_REQUEST_WINDOW: 10,
|
||||
BUG_REPORT_PICKER: 19,
|
||||
BUG_REPORT: 20,
|
||||
POPUP_GUIDE_BG: 99998,
|
||||
POPUP_GUIDE_BTN: 99999,
|
||||
PLAYER_REQUEST_WINDOW: 10,
|
||||
}
|
||||
|
||||
export const getHighest = () => {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import React, { useState, useCallback } from 'react';
|
|||
|
||||
type SupportedElements = HTMLInputElement | HTMLSelectElement;
|
||||
|
||||
export default function(state: string = ""): [string, React.ChangeEventHandler<SupportedElements>, (string) => void] {
|
||||
export default function(state: string = ""): [string, React.ChangeEventHandler<SupportedElements>, (value: string) => void] {
|
||||
const [ value, setValue ] = useState<string>(state);
|
||||
const onChange = useCallback(
|
||||
({ target: { value } }: React.ChangeEvent<SupportedElements>) =>
|
||||
setValue(value),
|
||||
({ target: { value } }: React.ChangeEvent<SupportedElements>) =>
|
||||
setValue(value),
|
||||
[]);
|
||||
return [ value, onChange, setValue ];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export default class BugReportStore {
|
|||
|
||||
bugReport: Partial<BugReportPdf>
|
||||
sessionEventSteps: Step[] = []
|
||||
chosenEventSteps: Step[] = []
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this)
|
||||
|
|
@ -52,7 +53,19 @@ export default class BugReportStore {
|
|||
this.bugReport = Object.assign(this.bugReport || {}, defaults)
|
||||
}
|
||||
|
||||
setSteps(steps: Step[]) {
|
||||
setDefaultSteps(steps: Step[]) {
|
||||
this.sessionEventSteps = steps
|
||||
}
|
||||
|
||||
setSteps(steps: Step[]) {
|
||||
this.chosenEventSteps = steps
|
||||
}
|
||||
|
||||
removeStep(step: Step) {
|
||||
this.chosenEventSteps = this.chosenEventSteps.filter(chosenStep => chosenStep.key !== step.key)
|
||||
}
|
||||
|
||||
resetSteps() {
|
||||
this.chosenEventSteps = []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue