* feat spot: init commit for extension * nvmrc * fix login flow * Spots Gridview Updates (#2422) * feat ui: login flow for spot extension * spot list, store and service created * some fixing for header * start work on single spot * spot player start * header for player, comments, icons, etc * split stuff into compoennts, create player state manager * player controls, activity panel etc etc * comments, empty page, rename and stuff * interval buttons etc * access modal * pubkey support * fix tooltip * limit 10 -> 9 * hls lib instead of videojs * some warnings * fix date display for exp * change public links * display more client data * fix cleaning, init comment * map network to replay player network ev * stream support, console panel, close panels on X * fixing streaming, destroy on leave * fix autoplay * show notification on spot login * fix spot login * backup player added, fix audio issue * show thumbnail when no video, add spot roles * add poster thumbnail * some fixes to video check * fix events jump * fix play btn * try catch over pubkey * feat ui: login flow for spot extension * spot list, store and service created * some fixing for header * start work on single spot * spot player start * header for player, comments, icons, etc * split stuff into compoennts, create player state manager * player controls, activity panel etc etc * comments, empty page, rename and stuff * interval buttons etc * access modal * pubkey support * fix tooltip * limit 10 -> 9 * hls lib instead of videojs * some warnings * fix date display for exp * change public links * display more client data * fix cleaning, init comment * map network to replay player network ev * stream support, console panel, close panels on X * fixing streaming, destroy on leave * fix autoplay * show notification on spot login * fix spot login * backup player added, fix audio issue * show thumbnail when no video, add spot roles * add poster thumbnail * some fixes to video check * fix events jump * fix play btn * try catch over pubkey * icons * Various updates * Update SVG.tsx * Update SideMenu.tsx * SpotList & Menu updates * feat ui: login flow for spot extension * spot list, store and service created * some fixing for header * start work on single spot * spot player start * header for player, comments, icons, etc * split stuff into compoennts, create player state manager * player controls, activity panel etc etc * comments, empty page, rename and stuff * interval buttons etc * access modal * pubkey support * fix tooltip * limit 10 -> 9 * hls lib instead of videojs * some warnings * fix date display for exp * change public links * display more client data * fix cleaning, init comment * map network to replay player network ev * stream support, console panel, close panels on X * fixing streaming, destroy on leave * fix autoplay * show notification on spot login * fix spot login * backup player added, fix audio issue * show thumbnail when no video, add spot roles * add poster thumbnail * some fixes to video check * fix events jump * fix play btn * try catch over pubkey * icons * spot login pinging * Spot List & Player Updates * move spot login flow to login comp, use separate spot login path for unique jwt * invalidate spot jwt on logout * add visual data on page load event * typo fix * Spot Listing improvements post review. * Update SpotListItem.tsx * Improved Spot List and Item Details * Minor improvements * More improvements * Public player header improvements * Moved formatExpirationTime to utils * fixes after merge --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * set sso link to <a>? * some small perf fixes * login duck reformat... * Update frontend.yaml * add observer to spot list header * split list header * update spotjwt param in router * fix toast in router * fix async fetch, move ctx * capture space btn ev * fix header link * public sharing error msg * fix err msg for unsuccessful rec start * fix list alignment * Caching assets. Finally!!! * fix typing in comment field * add pubkey to comments, fix console jump btn * no content comp * change refresh token logic * move thumbnail ts * move thumbnail ts * fix tab change * switch up toggler * early exit if no jwt present * regenerate icons * fix location str * fix ctx * change thumnail res, return autoplay for video player * parse links in console rows, fix injected method parse? * remove ts from js * fix console parsing order? * fixes for autoplay * xray for spot player * move to spot list after login; esc to cancel; fix signup link; move ux commit * kb sc for skipping; xray for spot ext * track aborted requests * tooltip for readability * fixing empty state * New blank state + various minor improvements (#2471) * New blank state + various minor improvements * apres merge --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * rm temp v * init or card * empty state debug * empty state debug * empty state debug * fix initor img * spotonly scope support * Improved Spot dead-end pages (#2475) * Improved Spot dead-end pages * Initiate OpenReplay Setup and some more * get scope changes * fix crash * scope upgrade/downgrade * scope setup flow * ping for backend * upgrade wxt deps * cancel ping int on expiration * check rec status * fix ping * check video processing state * check video processing state * fix xray close, network highlight, fcp rounding * update wxt, move open spot stuff to settings * fix some history issues * fix spot login flow * fix spot login again * fix spot login again * don't send two requests * limit messages for logged users * limit messages for logged users * fix public ignore * microphone stuff * microphone stuff * Various improvements (#2509) * Various improvements - Updated icons in mic settings - Included prefix in Spot title - Save recording notification has been updated - Other minor UI improvements * Inline declaration of spot name field, and settings UI * str f --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * UI changes in player header, spot list (#2510) * Added UI elements in player page - Badge with counts for comments - Download and Delete dropdown in player - Spot selection -- UI improvement * Minor copy updates * completing changes --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * rm cmt * fix cellmeasurer * thumbnail dur * fix download * Minor fixes (#2512) - Spot delete confirmation - Spot comments UI update - Minor copy updates * limit number of notif messages * add spot title to doc title, add cache groups for webpack * drop mic controls from recording popup view * fix for webpack compress * fix for auto mic pickup * change status banners * move svgs around, remove undefined check * refactor svgs * fix timetable scaling * fix error popup * self contain css * pre-select spot on spot onboarding --------- Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com> Co-authored-by: Rajesh Rajendran <rjshrjndrn@users.noreply.github.com>
366 lines
10 KiB
TypeScript
366 lines
10 KiB
TypeScript
import React from 'react';
|
|
import { VList, VListHandle } from 'virtua';
|
|
import cn from 'classnames';
|
|
import { Duration } from 'luxon';
|
|
import { NoContent, Button } from 'UI';
|
|
import { percentOf } from 'App/utils';
|
|
|
|
import BarRow from './BarRow';
|
|
import stl from './timeTable.module.css';
|
|
|
|
import autoscrollStl from '../autoscroll.module.css'; //aaa
|
|
import JumpButton from '../JumpButton';
|
|
|
|
type Timed = {
|
|
time: number;
|
|
};
|
|
|
|
type Durationed = {
|
|
duration: number;
|
|
};
|
|
|
|
type CanBeRed = {
|
|
//+isRed: boolean,
|
|
isRed: boolean;
|
|
};
|
|
|
|
interface Row extends Timed, Durationed, CanBeRed {
|
|
[key: string]: any;
|
|
key: string;
|
|
}
|
|
|
|
type Line = {
|
|
color: string; // Maybe use typescript?
|
|
hint?: string;
|
|
onClick?: any;
|
|
} & Timed;
|
|
|
|
type Column = {
|
|
label: string;
|
|
width: number;
|
|
dataKey?: string;
|
|
render?: (row: any) => void;
|
|
referenceLines?: Array<Line>;
|
|
style?: React.CSSProperties;
|
|
onClick?: void;
|
|
} & RenderOrKey;
|
|
|
|
// type RenderOrKey = { // Disjoint?
|
|
// render: Row => React.Node
|
|
// } | {
|
|
// dataKey: string,
|
|
// }
|
|
type RenderOrKey =
|
|
| {
|
|
render?: (row: Row) => React.ReactNode;
|
|
key?: string;
|
|
}
|
|
| {
|
|
dataKey: string;
|
|
};
|
|
|
|
type Props = {
|
|
className?: string;
|
|
rows: Array<Row>;
|
|
children: Array<Column>;
|
|
tableHeight?: number;
|
|
activeIndex?: number;
|
|
renderPopup?: boolean;
|
|
navigation?: boolean;
|
|
referenceLines?: any[];
|
|
additionalHeight?: number;
|
|
hoverable?: boolean;
|
|
onRowClick?: (row: any, index: number) => void;
|
|
onJump?: (time: any) => void;
|
|
};
|
|
|
|
type TimeLineInfo = {
|
|
timestart: number;
|
|
timewidth: number;
|
|
};
|
|
|
|
type State = TimeLineInfo & typeof initialState;
|
|
|
|
//const TABLE_HEIGHT = 195;
|
|
let _additionalHeight = 0;
|
|
const ROW_HEIGHT = 24;
|
|
//const VISIBLE_COUNT = Math.ceil(TABLE_HEIGHT/ROW_HEIGHT);
|
|
|
|
const TIME_SECTIONS_COUNT = 8;
|
|
const ZERO_TIMEWIDTH = 1000;
|
|
function formatTime(ms: number) {
|
|
if (ms < 0) return '';
|
|
if (ms < 1000) return Duration.fromMillis(ms).toFormat('0.SSS');
|
|
return Duration.fromMillis(ms).toFormat('mm:ss');
|
|
}
|
|
|
|
function computeTimeLine(
|
|
rows: Array<Row>,
|
|
firstVisibleRowIndex: number,
|
|
visibleCount: number
|
|
): TimeLineInfo {
|
|
const visibleRows = rows.slice(
|
|
firstVisibleRowIndex,
|
|
firstVisibleRowIndex + visibleCount + _additionalHeight
|
|
);
|
|
let timestart = visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0;
|
|
// TODO: GraphQL requests do not have a duration, so their timeline is borked. Assume a duration of 0.2s for every GraphQL request
|
|
const timeend =
|
|
visibleRows.length > 0 ? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200))) : 0;
|
|
let timewidth = timeend - timestart;
|
|
const offset = timewidth / 70;
|
|
if (timestart >= offset) {
|
|
timestart -= offset;
|
|
}
|
|
timewidth *= 1.5; // += offset;
|
|
if (timewidth === 0) {
|
|
timewidth = ZERO_TIMEWIDTH;
|
|
}
|
|
return {
|
|
timestart,
|
|
timewidth,
|
|
};
|
|
}
|
|
|
|
const initialState = {
|
|
firstVisibleRowIndex: 0,
|
|
};
|
|
|
|
export default class TimeTable extends React.PureComponent<Props, State> {
|
|
state = {
|
|
...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount),
|
|
...initialState,
|
|
};
|
|
|
|
get tableHeight() {
|
|
return this.props.tableHeight || 195;
|
|
}
|
|
|
|
get visibleCount() {
|
|
return Math.ceil(this.tableHeight / ROW_HEIGHT);
|
|
}
|
|
|
|
scroller = React.createRef<VListHandle>();
|
|
autoScroll = true;
|
|
|
|
adjustScroll(prevActiveIndex: number) {
|
|
if (
|
|
this.props.activeIndex &&
|
|
this.props.activeIndex >= 0 &&
|
|
prevActiveIndex !== this.props.activeIndex &&
|
|
this.scroller.current
|
|
) {
|
|
this.scroller.current.scrollToIndex(this.props.activeIndex);
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps: any, prevState: any) {
|
|
if (
|
|
prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex ||
|
|
(this.props.rows.length <= this.visibleCount + _additionalHeight &&
|
|
prevProps.rows.length !== this.props.rows.length)
|
|
) {
|
|
this.setState({
|
|
...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount),
|
|
});
|
|
}
|
|
|
|
// this.adjustScroll(prevProps.activeIndex);
|
|
}
|
|
|
|
onScroll = ({
|
|
scrollTop,
|
|
scrollHeight,
|
|
clientHeight,
|
|
}: {
|
|
scrollTop: number;
|
|
scrollHeight: number;
|
|
clientHeight: number;
|
|
}): void => {
|
|
const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33);
|
|
|
|
if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) {
|
|
this.autoScroll = scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2;
|
|
this.setState({ firstVisibleRowIndex });
|
|
}
|
|
};
|
|
|
|
onJump = (index: any) => {
|
|
if (this.props.onJump) {
|
|
this.props.onJump(this.props.rows[index]);
|
|
}
|
|
};
|
|
|
|
renderRow = (index: number) => {
|
|
const { activeIndex } = this.props;
|
|
const { children: columns, rows, renderPopup, hoverable, onRowClick } = this.props;
|
|
const { timestart, timewidth } = this.state;
|
|
const row = rows[index];
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'dev-row border-b border-color-gray-light-shade group items-center',
|
|
stl.row,
|
|
{
|
|
[stl.hoverable]: hoverable,
|
|
'error color-red': row.isRed,
|
|
'cursor-pointer': typeof onRowClick === 'function',
|
|
[stl.activeRow]: activeIndex === index,
|
|
[stl.inactiveRow]: !activeIndex || index > activeIndex,
|
|
}
|
|
)}
|
|
onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined}
|
|
id="table-row"
|
|
>
|
|
{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` }}>
|
|
{render
|
|
? render(row)
|
|
: row[dataKey || ''] || <i className="color-gray-light">{'empty'}</i>}
|
|
</div>
|
|
))}
|
|
<div className={cn('relative flex-1 flex', stl.timeBarWrapper)}>
|
|
<BarRow resource={row} timestart={timestart} timewidth={timewidth} popup={renderPopup} />
|
|
</div>
|
|
<JumpButton onClick={() => this.onJump(index)} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
onPrevClick = () => {
|
|
let prevRedIndex = -1;
|
|
for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) {
|
|
if (this.props.rows[i].isRed) {
|
|
prevRedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (this.scroller.current != null) {
|
|
this.scroller.current.scrollToIndex(prevRedIndex);
|
|
}
|
|
};
|
|
|
|
onNextClick = () => {
|
|
let prevRedIndex = -1;
|
|
for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) {
|
|
if (this.props.rows[i].isRed) {
|
|
prevRedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (this.scroller.current != null) {
|
|
this.scroller.current.scrollToIndex(prevRedIndex);
|
|
}
|
|
};
|
|
|
|
onColumnClick = (dataKey: string, onClick: any) => {
|
|
if (typeof onClick === 'function') {
|
|
onClick(dataKey);
|
|
}
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
className,
|
|
rows,
|
|
navigation = false,
|
|
referenceLines = [],
|
|
additionalHeight = 0,
|
|
activeIndex,
|
|
} = this.props;
|
|
const columns = this.props.children.filter((i: any) => !i.hidden);
|
|
const { timewidth, timestart } = this.state;
|
|
|
|
_additionalHeight = additionalHeight;
|
|
|
|
const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT);
|
|
const timeColumns: number[] = [];
|
|
if (timewidth > 0) {
|
|
for (let i = 0; i < TIME_SECTIONS_COUNT; i++) {
|
|
timeColumns.push(timestart + i * sectionDuration);
|
|
}
|
|
}
|
|
|
|
const visibleRefLines = referenceLines.filter(
|
|
({ time }) => time > timestart && time < timestart + timewidth
|
|
);
|
|
|
|
const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0);
|
|
|
|
return (
|
|
<div className={cn(className, 'relative')}>
|
|
{navigation && (
|
|
<div className={cn(autoscrollStl.navButtons, 'flex items-center')}>
|
|
<Button
|
|
variant="text-primary"
|
|
icon="chevron-up"
|
|
tooltip={{
|
|
title: 'Previous Error',
|
|
delay: 0,
|
|
}}
|
|
onClick={this.onPrevClick}
|
|
/>
|
|
<Button
|
|
variant="text-primary"
|
|
icon="chevron-down"
|
|
tooltip={{
|
|
title: 'Next Error',
|
|
delay: 0,
|
|
}}
|
|
onClick={this.onNextClick}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className={stl.headers}>
|
|
<div className={stl.infoHeaders}>
|
|
{columns.map(({ label, width, dataKey, onClick = null }) => (
|
|
<div
|
|
key={parseInt(label.replace(' ', ''), 36)}
|
|
className={cn(stl.headerCell, 'flex items-center select-none', {
|
|
'cursor-pointer': typeof onClick === 'function',
|
|
})}
|
|
style={{ width: `${width}px` }}
|
|
// onClick={() => this.onColumnClick(dataKey, onClick)}
|
|
>
|
|
<span>{label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className={stl.waterfallHeaders}>
|
|
{timeColumns.map((time, i) => (
|
|
<div className={stl.timeCell} key={`tc-${i}`}>
|
|
{formatTime(time)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<NoContent size="small" show={rows.length === 0}>
|
|
<div className="relative" style={{ height: this.tableHeight }}>
|
|
<div className={stl.timePart} style={{ left: `${columnsSumWidth}px` }}>
|
|
{timeColumns.map((_, index) => (
|
|
<div key={`tc-${index}`} className={stl.timeCell} />
|
|
))}
|
|
{visibleRefLines.map(({ time, color, onClick }) => (
|
|
<div
|
|
key={time}
|
|
className={cn(stl.refLine, `bg-${color}`)}
|
|
style={{
|
|
left: `${percentOf(time - timestart, timewidth)}%`,
|
|
cursor: typeof onClick === 'function' ? 'click' : 'auto',
|
|
}}
|
|
onClick={onClick}
|
|
/>
|
|
))}
|
|
</div>
|
|
<VList className={stl.list} ref={this.scroller} itemSize={ROW_HEIGHT} count={rows.length}>
|
|
{this.props.rows.map((_, index) => this.renderRow(index))}
|
|
</VList>
|
|
</div>
|
|
</NoContent>
|
|
</div>
|
|
);
|
|
}
|
|
}
|