diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js
index 858e9cb30..27324e686 100644
--- a/frontend/app/components/BugFinder/SessionList/SessionList.js
+++ b/frontend/app/components/BugFinder/SessionList/SessionList.js
@@ -1,12 +1,12 @@
import { connect } from 'react-redux';
-import { Loader, NoContent, Button, LoadMoreButton, Pagination } from 'UI';
+import { Loader, NoContent, Button, Pagination } from 'UI';
import { applyFilter, addAttribute, addEvent } from 'Duck/filters';
-import { fetchSessions, addFilterByKeyAndValue, updateCurrentPage } from 'Duck/search';
+import { fetchSessions, addFilterByKeyAndValue, updateCurrentPage, setScrollPosition } from 'Duck/search';
import SessionItem from 'Shared/SessionItem';
import SessionListHeader from './SessionListHeader';
import { FilterKey } from 'Types/filter/filterType';
-const ALL = 'all';
+// const ALL = 'all';
const PER_PAGE = 10;
const AUTOREFRESH_INTERVAL = 3 * 60 * 1000;
var timeoutId;
@@ -21,6 +21,7 @@ var timeoutId;
filters: state.getIn([ 'search', 'instance', 'filters' ]),
metaList: state.getIn(['customFields', 'list']).map(i => i.key),
currentPage: state.getIn([ 'search', 'currentPage' ]),
+ scrollY: state.getIn([ 'search', 'scrollY' ]),
}), {
applyFilter,
addAttribute,
@@ -28,24 +29,15 @@ var timeoutId;
fetchSessions,
addFilterByKeyAndValue,
updateCurrentPage,
+ setScrollPosition,
})
export default class SessionList extends React.PureComponent {
- state = {
- showPages: 1,
- }
+
constructor(props) {
super(props);
this.timeout();
}
- componentDidUpdate(prevProps) {
- if (prevProps.loading && !this.props.loading) {
- this.setState({ showPages: 1 });
- }
- }
-
- addPage = () => this.setState({ showPages: this.state.showPages + 1 })
-
onUserClick = (userId, userAnonymousId) => {
if (userId) {
this.props.addFilterByKeyAndValue(FilterKey.USERID, userId);
@@ -75,17 +67,22 @@ export default class SessionList extends React.PureComponent {
}
componentWillUnmount() {
+ this.props.setScrollPosition(window.scrollY)
clearTimeout(timeoutId)
}
-
+ componentDidMount() {
+ const { scrollY } = this.props;
+ console.log('scrollY', scrollY);
+ window.scrollTo(0, scrollY);
+ }
renderActiveTabContent(list) {
const {
loading,
filters,
- onMenuItemClick,
- allList,
+ // onMenuItemClick,
+ // allList,
activeTab,
metaList,
currentPage,
@@ -93,8 +90,6 @@ export default class SessionList extends React.PureComponent {
} = this.props;
const _filterKeys = filters.map(i => i.key);
const hasUserFilter = _filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID);
- const { showPages } = this.state;
- const displayedCount = Math.min(showPages * PER_PAGE, list.size);
return (
Please try changing your search parameters.
- {allList.size > 0 && (
+ {/* {allList.size > 0 && (
However, we found other sessions based on your search parameters.
@@ -115,7 +110,7 @@ export default class SessionList extends React.PureComponent {
>See All
- )}
+ )} */}
}
>
@@ -139,41 +134,29 @@ export default class SessionList extends React.PureComponent {
debounceRequest={1000}
/>
- {/*
- Haven't found the session in the above list?
Try being a bit more specific by setting a specific time frame or simply use different filters
-
- }
- /> */}
);
}
render() {
const { activeTab, allList, total } = this.props;
- var filteredList;
+ // var filteredList;
- if (activeTab.type !== ALL && activeTab.type !== 'bookmark' && activeTab.type !== 'live') { // Watchdog sessions
- filteredList = allList.filter(session => activeTab.fits(session))
- } else {
- filteredList = allList
- }
+ // if (activeTab.type !== ALL && activeTab.type !== 'bookmark' && activeTab.type !== 'live') { // Watchdog sessions
+ // filteredList = allList.filter(session => activeTab.fits(session))
+ // } else {
+ // filteredList = allList
+ // }
- if (activeTab.type === 'bookmark') {
- filteredList = filteredList.filter(item => item.favorite)
- }
- const _total = activeTab.type === 'all' ? total : filteredList.size
+ // if (activeTab.type === 'bookmark') {
+ // filteredList = filteredList.filter(item => item.favorite)
+ // }
+ // const _total = activeTab.type === 'all' ? total : allList.size
return (
-
- { this.renderActiveTabContent(filteredList) }
+
+ { this.renderActiveTabContent(allList) }
);
}
diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx
index 177dccf9a..0dfa6dbd3 100644
--- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx
+++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx
@@ -12,7 +12,7 @@ function CustomMetriPercentage(props: Props) {
return (
{numberWithCommas(data.count)}
-
{`${data.previousCount} ( ${data.countProgress}% )`}
+
{`${parseInt(data.previousCount).toFixed(1)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}
from previous period.
)
diff --git a/frontend/app/components/Session/Layout/PlayOverlay.js b/frontend/app/components/Session/Layout/PlayOverlay.js
index 537460c26..3fb156172 100644
--- a/frontend/app/components/Session/Layout/PlayOverlay.js
+++ b/frontend/app/components/Session/Layout/PlayOverlay.js
@@ -1,8 +1,6 @@
import cn from 'classnames';
import { useCallback, useState } from 'react';
-
import { Icon } from 'UI';
-
import cls from './PlayOverlay.css';
export default function PlayOverlay({ player }) {
@@ -11,20 +9,17 @@ export default function PlayOverlay({ player }) {
const togglePlay = useCallback(() => {
player.togglePlay();
setIconVisible(true);
- setTimeout(
- () => setIconVisible(false),
- 800,
- );
+ setTimeout(() => setIconVisible(false), 800);
});
return (
+ className="absolute inset-0 flex items-center justify-center"
+ onClick={ togglePlay }
+ >
+
+
+
+
);
}
diff --git a/frontend/app/components/Session/Layout/Player/Timeline.js b/frontend/app/components/Session/Layout/Player/Timeline.js
index 5f05834ee..ca1383ffa 100644
--- a/frontend/app/components/Session/Layout/Player/Timeline.js
+++ b/frontend/app/components/Session/Layout/Player/Timeline.js
@@ -1,12 +1,9 @@
import { useCallback } from 'react';
import cn from 'classnames';
import { Popup } from 'UI';
-
import { CRASHES, EVENTS } from 'Player/ios/state';
-
import TimeTracker from './TimeTracker';
import PlayerTime from './PlayerTime';
-
import cls from './timeline.css';
export default function Timeline({ player }) {
@@ -19,7 +16,7 @@ export default function Timeline({ player }) {
const time = Math.max(Math.round(p * player.state.endTime), 0);
player.jump(time);
});
- const scale = 100 / player.state.endTime;
+ const scale = 100 / player.state.endTime;
return (
diff --git a/frontend/app/components/Session_/Player/Controls/Circle.tsx b/frontend/app/components/Session_/Player/Controls/Circle.tsx
new file mode 100644
index 000000000..b49d23216
--- /dev/null
+++ b/frontend/app/components/Session_/Player/Controls/Circle.tsx
@@ -0,0 +1,18 @@
+import React, { memo, FC } from 'react';
+import styles from './timeline.css';
+
+interface Props {
+ preview?: boolean;
+}
+export const Circle: FC
= memo(function Box({ preview }) {
+ // const backgroundColor = yellow ? 'yellow' : 'white'
+ return (
+
+ )
+ })
+
+export default Circle;
\ No newline at end of file
diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js
index 22fd3b0cf..ab1baba3e 100644
--- a/frontend/app/components/Session_/Player/Controls/Controls.js
+++ b/frontend/app/components/Session_/Player/Controls/Controls.js
@@ -118,6 +118,7 @@ export default class Controls extends React.Component {
componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
}
+
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown);
//this.props.toggleInspectorMode(false);
@@ -166,10 +167,10 @@ export default class Controls extends React.Component {
return;
}
if (this.props.inspectorMode) return;
- if (e.key === ' ') {
- document.activeElement.blur();
- this.props.togglePlay();
- }
+ // if (e.key === ' ') {
+ // document.activeElement.blur();
+ // this.props.togglePlay();
+ // }
if (e.key === 'Esc' || e.key === 'Escape') {
this.props.fullscreenOff();
}
@@ -262,7 +263,7 @@ export default class Controls extends React.Component {
return (
- { !live &&
}
+ { !live &&
}
{ !fullscreen &&
diff --git a/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx b/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx
new file mode 100644
index 000000000..c72f03ce2
--- /dev/null
+++ b/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx
@@ -0,0 +1,98 @@
+import React, { memo } from 'react';
+import { useDragLayer } from "react-dnd";
+import Circle from './Circle'
+import type { CSSProperties, FC } from 'react'
+
+const layerStyles: CSSProperties = {
+ position: "fixed",
+ pointerEvents: "none",
+ zIndex: 100,
+ left: 0,
+ top: 0,
+ width: "100%",
+ height: "100%"
+ };
+
+const ItemTypes = {
+ BOX: 'box',
+}
+
+function getItemStyles(initialOffset, currentOffset, maxX, minX) {
+ if (!initialOffset || !currentOffset) {
+ return {
+ display: "none"
+ };
+ }
+ let { x, y } = currentOffset;
+ // if (isSnapToGrid) {
+ // x -= initialOffset.x;
+ // y -= initialOffset.y;
+ // [x, y] = [x, y];
+ // x += initialOffset.x;
+ // y += initialOffset.y;
+ // }
+ if (x > maxX) {
+ x = maxX;
+ }
+
+ if (x < minX) {
+ x = minX;
+ }
+ const transform = `translate(${x}px, ${initialOffset.y}px)`;
+ return {
+ transition: 'transform 0.1s ease-out',
+ transform,
+ WebkitTransform: transform
+ };
+}
+
+interface Props {
+ onDrag: (offset: { x: number, y: number } | null) => void;
+ maxX: number;
+ minX: number;
+}
+
+const CustomDragLayer: FC
= memo(function CustomDragLayer(props) {
+ const {
+ itemType,
+ isDragging,
+ item,
+ initialOffset,
+ currentOffset,
+ } = useDragLayer((monitor) => ({
+ item: monitor.getItem(),
+ itemType: monitor.getItemType(),
+ initialOffset: monitor.getInitialSourceClientOffset(),
+ currentOffset: monitor.getSourceClientOffset(),
+ isDragging: monitor.isDragging(),
+ }));
+
+ function renderItem() {
+ switch (itemType) {
+ case ItemTypes.BOX:
+ return ;
+ default:
+ return null;
+ }
+ }
+
+ if (!isDragging) {
+ return null;
+ }
+
+ if (isDragging) {
+ props.onDrag(currentOffset)
+ }
+
+ return (
+
+ );
+})
+
+export default CustomDragLayer;
\ No newline at end of file
diff --git a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx
new file mode 100644
index 000000000..9bdf37651
--- /dev/null
+++ b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx
@@ -0,0 +1,67 @@
+import React, { memo, FC, useEffect, useRef, CSSProperties } from 'react';
+import type { DragSourceMonitor } from 'react-dnd'
+import { useDrag } from 'react-dnd'
+import { getEmptyImage } from 'react-dnd-html5-backend'
+import Circle from './Circle'
+
+function getStyles(
+ left: number,
+ isDragging: boolean,
+ ): CSSProperties {
+ // const transform = `translate3d(${(left * 1161) / 100}px, -8px, 0)`
+ return {
+ position: 'absolute',
+ top: '-3px',
+ left: `${left}%`,
+ // transform,
+ // WebkitTransform: transform,
+ // IE fallback: hide the real node using CSS when dragging
+ // because IE will ignore our custom "empty image" drag preview.
+ opacity: isDragging ? 0 : 1,
+ height: isDragging ? 0 : '',
+ zIndex: '99999',
+ cursor: 'move'
+ }
+}
+
+const ItemTypes = {
+ BOX: 'box',
+}
+
+interface Props {
+ left: number;
+ top: number;
+ onDrop?: (item, monitor) => void;
+}
+
+const DraggableCircle: FC = memo(function DraggableCircle(props) {
+ const { left, top } = props
+ const [{ isDragging, item }, dragRef, preview] = useDrag(
+ () => ({
+ type: ItemTypes.BOX,
+ item: { left, top },
+ end: props.onDrop,
+ collect: (monitor: DragSourceMonitor) => ({
+ isDragging: monitor.isDragging(),
+ item: monitor.getItem(),
+ }),
+ }),
+ [left, top],
+ )
+
+ useEffect(() => {
+ preview(getEmptyImage(), { captureDraggingState: true })
+ }, [])
+
+ return (
+
+
+
+ );
+})
+
+export default DraggableCircle
\ No newline at end of file
diff --git a/frontend/app/components/Session_/Player/Controls/TimeTracker.js b/frontend/app/components/Session_/Player/Controls/TimeTracker.js
index be91f69fe..e3de669e5 100644
--- a/frontend/app/components/Session_/Player/Controls/TimeTracker.js
+++ b/frontend/app/components/Session_/Player/Controls/TimeTracker.js
@@ -4,10 +4,6 @@ import styles from './timeTracker.css';
const TimeTracker = ({ time, scale }) => (
-
{
// exception,
@@ -51,6 +53,8 @@ const getPointerIcon = (type) => {
}
@connectPlayer(state => ({
+ playing: state.playing,
+ time: state.time,
skipIntervals: state.skipIntervals,
events: state.eventList,
skip: state.skip,
@@ -72,6 +76,11 @@ const getPointerIcon = (type) => {
state.getIn([ 'sessions', 'current', 'returningLocationTime' ]),
}), { setTimelinePointer })
export default class Timeline extends React.PureComponent {
+ progressRef = React.createRef()
+ progressWidth = 0
+ seekTime = 0
+ wasPlaying = false
+
seekProgress = (e) => {
const { endTime } = this.props;
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
@@ -88,11 +97,32 @@ export default class Timeline extends React.PureComponent {
componentDidMount() {
const { issues, events, fetchList, skipToIssue } = this.props;
const firstIssue = issues.get(0);
+ this.progressWidth = this.progressRef.current.offsetWidth;
+
if (firstIssue && skipToIssue) {
this.props.jump(firstIssue.time);
}
}
+ onDragEnd = (item, monitor) => {
+ this.props.jump(this.seekTime);
+ if (this.wasPlaying) {
+ this.props.togglePlay();
+ }
+ }
+
+ onDrag = (offset) => {
+ const { endTime } = this.props;
+
+ const p = (offset.x - 60) / this.progressRef.current.offsetWidth;
+ const time = Math.max(Math.round(p * endTime), 0);
+ this.seekTime = time;
+ if (this.props.playing) {
+ this.wasPlaying = true;
+ this.props.pause();
+ }
+ }
+
render() {
const {
events,
@@ -103,7 +133,7 @@ export default class Timeline extends React.PureComponent {
live,
logList,
exceptionsList,
- resourceList,
+ resourceList,
clickRageTime,
stackList,
fetchList,
@@ -111,12 +141,19 @@ export default class Timeline extends React.PureComponent {
} = this.props;
const scale = 100 / endTime;
+
return (
{ !live &&
}
-
+
+
+
{ skip && skipIntervals.map(interval =>
(
{
+ // TODO Find a better way to do this
+ document.addEventListener('keydown', onKeyDown);
+
+ return () => {
+ document.removeEventListener('keydown', onKeyDown);
+ }
+ }, [])
+
+ const onKeyDown = (e) => {
+ if (e.key === ' ') {
+ togglePlayAnimated()
+ }
+ }
+
const togglePlayAnimated = useCallback(() => {
setShowPlayOverlayIcon(true);
togglePlay();
- setTimeout(
- () => setShowPlayOverlayIcon(false),
- 800,
- );
+ setTimeout(() => setShowPlayOverlayIcon(false), 800);
}, []);
+
return (
{
return {
type: REFRESH_FILTER_OPTIONS
}
+}
+
+export const setScrollPosition = (scrollPosition) => {
+ return {
+ type: SET_SCROLL_POSITION,
+ scrollPosition,
+ }
}
\ No newline at end of file
diff --git a/frontend/app/initialize.js b/frontend/app/initialize.js
index dcfd01058..874bd0586 100644
--- a/frontend/app/initialize.js
+++ b/frontend/app/initialize.js
@@ -1,13 +1,9 @@
import './init';
-
import { render } from 'react-dom';
import { Provider } from 'react-redux';
-
import store from './store';
import Router from './Router';
import { StoreProvider, RootStore } from './mstore';
-import { ModalProvider } from './components/Modal';
-import ModalRoot from './components/Modal/ModalRoot';
import { HTML5Backend } from 'react-dnd-html5-backend'
import { DndProvider } from 'react-dnd'
@@ -17,10 +13,7 @@ document.addEventListener('DOMContentLoaded', () => {
- {/* */}
- {/* */}
- {/* */}
diff --git a/frontend/app/svg/icons/pie-chart-fill.svg b/frontend/app/svg/icons/pie-chart-fill.svg
index 6aa71eb89..e3e67bb5c 100644
--- a/frontend/app/svg/icons/pie-chart-fill.svg
+++ b/frontend/app/svg/icons/pie-chart-fill.svg
@@ -1,3 +1,3 @@
-