Merge remote-tracking branch 'origin/redux-toolkit-move' into rtm-temp
This commit is contained in:
commit
70293cd8de
11 changed files with 84 additions and 190 deletions
|
|
@ -1,8 +1,7 @@
|
||||||
import React, { useMemo, useContext, useState, useRef } from 'react';
|
import React, { useMemo, useContext, useState, useRef } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useStore } from 'App/mstore';
|
||||||
import TimeTracker from 'Components/Session_/Player/Controls/TimeTracker';
|
import TimeTracker from 'Components/Session_/Player/Controls/TimeTracker';
|
||||||
import stl from 'Components/Session_/Player/Controls/timeline.module.css';
|
import stl from 'Components/Session_/Player/Controls/timeline.module.css';
|
||||||
import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions';
|
|
||||||
import DraggableCircle from 'Components/Session_/Player/Controls/components/DraggableCircle';
|
import DraggableCircle from 'Components/Session_/Player/Controls/components/DraggableCircle';
|
||||||
import CustomDragLayer, { OnDragCallback } from 'Components/Session_/Player/Controls/components/CustomDragLayer';
|
import CustomDragLayer, { OnDragCallback } from 'Components/Session_/Player/Controls/components/CustomDragLayer';
|
||||||
import { debounce } from 'App/utils';
|
import { debounce } from 'App/utils';
|
||||||
|
|
@ -11,13 +10,11 @@ import { PlayerContext, ILivePlayerContext } from 'App/components/Session/player
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { Duration } from 'luxon';
|
import { Duration } from 'luxon';
|
||||||
|
|
||||||
interface IProps {
|
function Timeline() {
|
||||||
setTimelineHoverTime: (t: number) => void
|
const { sessionStore } = useStore();
|
||||||
startedAt: number
|
const startedAt = sessionStore.current.startedAt ?? 0;
|
||||||
tooltipVisible: boolean
|
const tooltipVisible = sessionStore.timeLineTooltip.isVisible;
|
||||||
}
|
const setTimelineHoverTime = sessionStore.setTimelineTooltip;
|
||||||
|
|
||||||
function Timeline(props: IProps) {
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const { player, store } = useContext<ILivePlayerContext>(PlayerContext)
|
const { player, store } = useContext<ILivePlayerContext>(PlayerContext)
|
||||||
const [wasPlaying, setWasPlaying] = useState(false)
|
const [wasPlaying, setWasPlaying] = useState(false)
|
||||||
|
|
@ -35,7 +32,7 @@ function Timeline(props: IProps) {
|
||||||
const scale = 100 / endTime;
|
const scale = 100 / endTime;
|
||||||
|
|
||||||
const debouncedJump = useMemo(() => debounce(player.jump, 500), [])
|
const debouncedJump = useMemo(() => debounce(player.jump, 500), [])
|
||||||
const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), [])
|
const debouncedTooltipChange = useMemo(() => debounce(setTimelineHoverTime, 50), [])
|
||||||
|
|
||||||
const onDragEnd = () => {
|
const onDragEnd = () => {
|
||||||
if (!liveTimeTravel) return;
|
if (!liveTimeTravel) return;
|
||||||
|
|
@ -59,7 +56,7 @@ function Timeline(props: IProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLiveTime = (e: React.MouseEvent) => {
|
const getLiveTime = (e: React.MouseEvent) => {
|
||||||
const duration = new Date().getTime() - props.startedAt;
|
const duration = new Date().getTime() - startedAt;
|
||||||
// @ts-ignore type mismatch from react?
|
// @ts-ignore type mismatch from react?
|
||||||
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
|
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
|
||||||
const time = Math.max(Math.round(p * duration), 0);
|
const time = Math.max(Math.round(p * duration), 0);
|
||||||
|
|
@ -69,7 +66,7 @@ function Timeline(props: IProps) {
|
||||||
|
|
||||||
const showTimeTooltip = (e: React.MouseEvent<HTMLDivElement>) => {
|
const showTimeTooltip = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (e.target !== progressRef.current && e.target !== timelineRef.current) {
|
if (e.target !== progressRef.current && e.target !== timelineRef.current) {
|
||||||
return props.tooltipVisible && hideTimeTooltip();
|
return tooltipVisible && hideTimeTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
const [time, duration] = getLiveTime(e);
|
const [time, duration] = getLiveTime(e);
|
||||||
|
|
@ -136,7 +133,7 @@ function Timeline(props: IProps) {
|
||||||
onMouseEnter={showTimeTooltip}
|
onMouseEnter={showTimeTooltip}
|
||||||
onMouseLeave={hideTimeTooltip}
|
onMouseLeave={hideTimeTooltip}
|
||||||
>
|
>
|
||||||
<TooltipContainer live />
|
<TooltipContainer />
|
||||||
<DraggableCircle
|
<DraggableCircle
|
||||||
left={time * scale}
|
left={time * scale}
|
||||||
onDrop={onDragEnd}
|
onDrop={onDragEnd}
|
||||||
|
|
@ -156,10 +153,4 @@ function Timeline(props: IProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default observer(Timeline)
|
||||||
(state: any) => ({
|
|
||||||
startedAt: state.getIn(['sessions', 'current']).startedAt || 0,
|
|
||||||
tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']),
|
|
||||||
}),
|
|
||||||
{ setTimelinePointer, setTimelineHoverTime }
|
|
||||||
)(observer(Timeline))
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { sessions as sessionsRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes';
|
import { sessions as sessionsRoute, withSiteId } from 'App/routes';
|
||||||
import { BackLink, Link } from 'UI';
|
import { BackLink } from 'UI';
|
||||||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
||||||
import UserCard from '../ReplayPlayer/EventsBlock/UserCard';
|
import UserCard from '../ReplayPlayer/EventsBlock/UserCard';
|
||||||
|
|
@ -23,10 +22,10 @@ function PlayerBlockHeader(props: any) {
|
||||||
|
|
||||||
const playerState = store?.get?.() || { width: 0, height: 0, showEvents: false };
|
const playerState = store?.get?.() || { width: 0, height: 0, showEvents: false };
|
||||||
const { width = 0, height = 0, showEvents = false } = playerState;
|
const { width = 0, height = 0, showEvents = false } = playerState;
|
||||||
const { customFieldStore, projectsStore } = useStore();
|
const { customFieldStore, projectsStore, sessionStore } = useStore();
|
||||||
|
const session = sessionStore.current;
|
||||||
const siteId = projectsStore.siteId!;
|
const siteId = projectsStore.siteId!;
|
||||||
const {
|
const {
|
||||||
session,
|
|
||||||
fullscreen,
|
fullscreen,
|
||||||
metaList,
|
metaList,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
|
|
@ -100,18 +99,12 @@ function PlayerBlockHeader(props: any) {
|
||||||
|
|
||||||
const PlayerHeaderCont = connect(
|
const PlayerHeaderCont = connect(
|
||||||
(state: any) => {
|
(state: any) => {
|
||||||
const session = state.getIn(['sessions', 'current']);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session,
|
|
||||||
sessionPath: state.getIn(['sessions', 'sessionPath']),
|
|
||||||
funnelRef: state.getIn(['funnels', 'navRef']),
|
funnelRef: state.getIn(['funnels', 'navRef']),
|
||||||
metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key),
|
metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
toggleFavorite,
|
|
||||||
setSessionPath,
|
|
||||||
}
|
}
|
||||||
)(observer(PlayerBlockHeader));
|
)(observer(PlayerBlockHeader));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { findDOMNode } from 'react-dom';
|
import { findDOMNode } from 'react-dom';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { EscapeButton } from 'UI';
|
import { EscapeButton } from 'UI';
|
||||||
|
|
@ -18,7 +17,6 @@ import { MobileExceptions } from 'Components/Session_/Exceptions/Exceptions';
|
||||||
import MobileControls from './MobileControls';
|
import MobileControls from './MobileControls';
|
||||||
import Overlay from './MobileOverlay'
|
import Overlay from './MobileOverlay'
|
||||||
import stl from 'Components/Session_/Player/player.module.css';
|
import stl from 'Components/Session_/Player/player.module.css';
|
||||||
import { updateLastPlayedSession } from 'Duck/sessions';
|
|
||||||
import { MobileOverviewPanel } from 'Components/Session_/OverviewPanel';
|
import { MobileOverviewPanel } from 'Components/Session_/OverviewPanel';
|
||||||
import MobileConsolePanel from 'Shared/DevTools/ConsolePanel/MobileConsolePanel';
|
import MobileConsolePanel from 'Shared/DevTools/ConsolePanel/MobileConsolePanel';
|
||||||
import { MobilePlayerContext } from 'App/components/Session/playerContext';
|
import { MobilePlayerContext } from 'App/components/Session/playerContext';
|
||||||
|
|
@ -32,32 +30,28 @@ import { useStore } from 'App/mstore';
|
||||||
interface IProps {
|
interface IProps {
|
||||||
fullView: boolean;
|
fullView: boolean;
|
||||||
isMultiview?: boolean;
|
isMultiview?: boolean;
|
||||||
nextId: string;
|
|
||||||
sessionId: string;
|
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
updateLastPlayedSession: (id: string) => void
|
|
||||||
videoURL: string[];
|
|
||||||
setActiveTab: (tab: string) => void;
|
setActiveTab: (tab: string) => void;
|
||||||
userDevice: string;
|
bottomBlock: any;
|
||||||
screenWidth: number;
|
fullscreen?: boolean;
|
||||||
screenHeight: number;
|
|
||||||
platform: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Player(props: IProps) {
|
function Player(props: IProps) {
|
||||||
const defaultHeight = getDefaultPanelHeight()
|
const defaultHeight = getDefaultPanelHeight()
|
||||||
const [panelHeight, setPanelHeight] = React.useState(defaultHeight);
|
const [panelHeight, setPanelHeight] = React.useState(defaultHeight);
|
||||||
const {
|
const {
|
||||||
nextId,
|
|
||||||
activeTab,
|
activeTab,
|
||||||
fullView,
|
fullView,
|
||||||
videoURL,
|
|
||||||
userDevice,
|
|
||||||
screenWidth,
|
|
||||||
screenHeight,
|
|
||||||
platform,
|
|
||||||
} = props;
|
} = props;
|
||||||
const { uiPlayerStore } = useStore();
|
const { uiPlayerStore, sessionStore } = useStore();
|
||||||
|
const nextId = sessionStore.nextId;
|
||||||
|
const sessionId = sessionStore.current.sessionId;
|
||||||
|
const userDevice = sessionStore.current.userDevice;
|
||||||
|
const videoURL = sessionStore.current.videoURL;
|
||||||
|
const platform = sessionStore.current.platform;
|
||||||
|
const screenWidth = sessionStore.current.screenWidth!;
|
||||||
|
const screenHeight = sessionStore.current.screenHeight!;
|
||||||
|
const updateLastPlayedSession = sessionStore.updateLastPlayedSession;
|
||||||
const fullscreenOff = uiPlayerStore.fullscreenOff;
|
const fullscreenOff = uiPlayerStore.fullscreenOff;
|
||||||
const fullscreen = uiPlayerStore.fullscreen;
|
const fullscreen = uiPlayerStore.fullscreen;
|
||||||
const bottomBlock = uiPlayerStore.bottomBlock;
|
const bottomBlock = uiPlayerStore.bottomBlock;
|
||||||
|
|
@ -68,7 +62,7 @@ function Player(props: IProps) {
|
||||||
const [isAttached, setAttached] = React.useState(false);
|
const [isAttached, setAttached] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
props.updateLastPlayedSession(props.sessionId);
|
updateLastPlayedSession(sessionId);
|
||||||
const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture
|
const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture
|
||||||
if (parentElement && !isAttached) {
|
if (parentElement && !isAttached) {
|
||||||
playerContext.player.attach(parentElement);
|
playerContext.player.attach(parentElement);
|
||||||
|
|
@ -166,17 +160,4 @@ function Player(props: IProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default observer(Player);
|
||||||
(state: any) => ({
|
|
||||||
nextId: state.getIn(['sessions', 'nextId']),
|
|
||||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
|
||||||
userDevice: state.getIn(['sessions', 'current']).userDevice,
|
|
||||||
videoURL: state.getIn(['sessions', 'current']).videoURL,
|
|
||||||
platform: state.getIn(['sessions', 'current']).platform,
|
|
||||||
screenWidth: state.getIn(['sessions', 'current']).screenWidth,
|
|
||||||
screenHeight: state.getIn(['sessions', 'current']).screenHeight,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
updateLastPlayedSession,
|
|
||||||
}
|
|
||||||
)(observer(Player));
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
withSiteId,
|
withSiteId,
|
||||||
} from 'App/routes';
|
} from 'App/routes';
|
||||||
import { BackLink, Link } from 'UI';
|
import { BackLink, Link } from 'UI';
|
||||||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
||||||
import UserCard from './EventsBlock/UserCard';
|
import UserCard from './EventsBlock/UserCard';
|
||||||
|
|
@ -20,24 +19,23 @@ import { IFRAME } from 'App/constants/storageKeys';
|
||||||
|
|
||||||
const SESSIONS_ROUTE = sessionsRoute();
|
const SESSIONS_ROUTE = sessionsRoute();
|
||||||
|
|
||||||
// TODO props
|
|
||||||
function PlayerBlockHeader(props: any) {
|
function PlayerBlockHeader(props: any) {
|
||||||
const [hideBack, setHideBack] = React.useState(false);
|
const [hideBack, setHideBack] = React.useState(false);
|
||||||
const { player, store } = React.useContext(PlayerContext);
|
const { player, store } = React.useContext(PlayerContext);
|
||||||
const { uxtestingStore, customFieldStore, projectsStore } = useStore()
|
const { uxtestingStore, customFieldStore, projectsStore, sessionStore } = useStore()
|
||||||
|
const session = sessionStore.current;
|
||||||
|
const sessionPath = sessionStore.sessionPath;
|
||||||
const siteId = projectsStore.siteId!;
|
const siteId = projectsStore.siteId!;
|
||||||
const playerState = store?.get?.() || { width: 0, height: 0, showEvents: false }
|
const playerState = store?.get?.() || { width: 0, height: 0, showEvents: false }
|
||||||
const { width = 0, height = 0, showEvents = false } = playerState
|
const { width = 0, height = 0, showEvents = false } = playerState
|
||||||
|
|
||||||
const {
|
const {
|
||||||
session,
|
|
||||||
fullscreen,
|
fullscreen,
|
||||||
metaList,
|
metaList,
|
||||||
closedLive = false,
|
closedLive = false,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
activeTab,
|
activeTab,
|
||||||
history,
|
history,
|
||||||
sessionPath,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -130,19 +128,12 @@ function PlayerBlockHeader(props: any) {
|
||||||
|
|
||||||
const PlayerHeaderCont = connect(
|
const PlayerHeaderCont = connect(
|
||||||
(state: any) => {
|
(state: any) => {
|
||||||
const session = state.getIn(['sessions', 'current']);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session,
|
|
||||||
sessionPath: state.getIn(['sessions', 'sessionPath']),
|
|
||||||
funnelRef: state.getIn(['funnels', 'navRef']),
|
funnelRef: state.getIn(['funnels', 'navRef']),
|
||||||
metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key),
|
metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
|
||||||
toggleFavorite,
|
|
||||||
setSessionPath,
|
|
||||||
}
|
|
||||||
)(observer(PlayerBlockHeader));
|
)(observer(PlayerBlockHeader));
|
||||||
|
|
||||||
export default withRouter(PlayerHeaderCont);
|
export default withRouter(PlayerHeaderCont);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { VList, VListHandle } from 'virtua';
|
||||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||||
import { RootStore } from 'App/duck';
|
import { RootStore } from 'App/duck';
|
||||||
import { useStore } from 'App/mstore';
|
import { useStore } from 'App/mstore';
|
||||||
import { filterOutNote, setEventFilter } from 'Duck/sessions';
|
|
||||||
import { Icon } from 'UI';
|
import { Icon } from 'UI';
|
||||||
|
|
||||||
import EventGroupWrapper from './EventGroupWrapper';
|
import EventGroupWrapper from './EventGroupWrapper';
|
||||||
|
|
@ -18,19 +17,19 @@ import EventSearch from './EventSearch/EventSearch';
|
||||||
import styles from './eventsBlock.module.css';
|
import styles from './eventsBlock.module.css';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
setEventFilter: (filter: { query: string }) => void;
|
|
||||||
filteredEvents: InjectedEvent[];
|
|
||||||
setActiveTab: (tab?: string) => void;
|
setActiveTab: (tab?: string) => void;
|
||||||
query: string;
|
|
||||||
events: Session['events'];
|
|
||||||
notesWithEvents: Session['notesWithEvents'];
|
|
||||||
filterOutNote: (id: string) => void;
|
|
||||||
eventsIndex: number[];
|
|
||||||
uxtVideo: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventsBlock(props: IProps) {
|
function EventsBlock(props: IProps) {
|
||||||
const { notesStore, uxtestingStore, uiPlayerStore } = useStore();
|
const { notesStore, uxtestingStore, uiPlayerStore, sessionStore } = useStore();
|
||||||
|
const session = sessionStore.current;
|
||||||
|
const notesWithEvents = session.notesWithEvents;
|
||||||
|
const uxtVideo = session.uxtVideo;
|
||||||
|
const filteredEvents = sessionStore.filteredEvents;
|
||||||
|
const query = sessionStore.eventsQuery;
|
||||||
|
const eventsIndex = sessionStore.eventsIndex;
|
||||||
|
const setEventFilter = sessionStore.setEventQuery;
|
||||||
|
const filterOutNote = sessionStore.filterOutNote;
|
||||||
const [mouseOver, setMouseOver] = React.useState(false);
|
const [mouseOver, setMouseOver] = React.useState(false);
|
||||||
const scroller = React.useRef<VListHandle>(null);
|
const scroller = React.useRef<VListHandle>(null);
|
||||||
const zoomEnabled = uiPlayerStore.timelineZoom.enabled;
|
const zoomEnabled = uiPlayerStore.timelineZoom.enabled;
|
||||||
|
|
@ -47,12 +46,7 @@ function EventsBlock(props: IProps) {
|
||||||
} = store.get();
|
} = store.get();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
filteredEvents,
|
|
||||||
eventsIndex,
|
|
||||||
filterOutNote,
|
|
||||||
query,
|
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
notesWithEvents = [],
|
|
||||||
} = props;
|
} = props;
|
||||||
const notes = notesStore.sessionNotes;
|
const notes = notesStore.sessionNotes;
|
||||||
|
|
||||||
|
|
@ -129,7 +123,7 @@ function EventsBlock(props: IProps) {
|
||||||
const write = ({
|
const write = ({
|
||||||
target: { value },
|
target: { value },
|
||||||
}: React.ChangeEvent<HTMLInputElement>) => {
|
}: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
props.setEventFilter({ query: value });
|
setEventFilter({ query: value });
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!scroller.current) return;
|
if (!scroller.current) return;
|
||||||
|
|
@ -139,7 +133,7 @@ function EventsBlock(props: IProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSearch = () => {
|
const clearSearch = () => {
|
||||||
props.setEventFilter({ query: '' });
|
setEventFilter({ query: '' });
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!scroller.current) return;
|
if (!scroller.current) return;
|
||||||
|
|
@ -163,7 +157,7 @@ function EventsBlock(props: IProps) {
|
||||||
|
|
||||||
const onEventClick = (_: React.MouseEvent, event: { time: number }) => {
|
const onEventClick = (_: React.MouseEvent, event: { time: number }) => {
|
||||||
player.jump(event.time);
|
player.jump(event.time);
|
||||||
props.setEventFilter({ query: '' });
|
setEventFilter({ query: '' });
|
||||||
};
|
};
|
||||||
const onMouseOver = () => setMouseOver(true);
|
const onMouseOver = () => setMouseOver(true);
|
||||||
const onMouseLeave = () => setMouseOver(false);
|
const onMouseLeave = () => setMouseOver(false);
|
||||||
|
|
@ -213,7 +207,7 @@ function EventsBlock(props: IProps) {
|
||||||
muted
|
muted
|
||||||
autoPlay
|
autoPlay
|
||||||
controls
|
controls
|
||||||
src={props.uxtVideo}
|
src={uxtVideo}
|
||||||
width={240}
|
width={240}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
@ -265,18 +259,4 @@ function EventsBlock(props: IProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default observer(EventsBlock);
|
||||||
(state: RootStore) => ({
|
|
||||||
session: state.getIn(['sessions', 'current']),
|
|
||||||
notesWithEvents: state.getIn(['sessions', 'current']).notesWithEvents,
|
|
||||||
events: state.getIn(['sessions', 'current']).events,
|
|
||||||
uxtVideo: state.getIn(['sessions', 'current']).uxtVideo,
|
|
||||||
filteredEvents: state.getIn(['sessions', 'filteredEvents']),
|
|
||||||
query: state.getIn(['sessions', 'eventsQuery']),
|
|
||||||
eventsIndex: state.getIn(['sessions', 'eventsIndex']),
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
setEventFilter,
|
|
||||||
filterOutNote,
|
|
||||||
}
|
|
||||||
)(observer(EventsBlock));
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import DraggableMarkers from 'Components/Session_/Player/Controls/components/ZoomDragLayer';
|
import DraggableMarkers from 'Components/Session_/Player/Controls/components/ZoomDragLayer';
|
||||||
import React, { useEffect, useMemo, useContext, useState, useRef } from 'react';
|
import React, { useEffect, useMemo, useContext, useState, useRef } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import stl from './timeline.module.css';
|
import stl from './timeline.module.css';
|
||||||
import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions';
|
|
||||||
import CustomDragLayer, { OnDragCallback } from './components/CustomDragLayer';
|
import CustomDragLayer, { OnDragCallback } from './components/CustomDragLayer';
|
||||||
import { debounce } from 'App/utils';
|
import { debounce } from 'App/utils';
|
||||||
import TooltipContainer from './components/TooltipContainer';
|
import TooltipContainer from './components/TooltipContainer';
|
||||||
|
|
@ -10,18 +8,12 @@ import { PlayerContext } from 'App/components/Session/playerContext';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useStore } from 'App/mstore';
|
import { useStore } from 'App/mstore';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
import Issue from 'Types/session/issue';
|
|
||||||
import { WebEventsList, MobEventsList } from './EventsList';
|
import { WebEventsList, MobEventsList } from './EventsList';
|
||||||
import NotesList from './NotesList';
|
import NotesList from './NotesList';
|
||||||
import SkipIntervalsList from './SkipIntervalsList';
|
import SkipIntervalsList from './SkipIntervalsList';
|
||||||
import TimelineTracker from 'Components/Session_/Player/Controls/TimelineTracker';
|
import TimelineTracker from 'Components/Session_/Player/Controls/TimelineTracker';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
issues: Issue[];
|
|
||||||
setTimelineHoverTime: (t: number) => void;
|
|
||||||
startedAt: number;
|
|
||||||
tooltipVisible: boolean;
|
|
||||||
timezone?: string;
|
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,10 +21,14 @@ function Timeline(props: IProps) {
|
||||||
const { player, store } = useContext(PlayerContext);
|
const { player, store } = useContext(PlayerContext);
|
||||||
const [wasPlaying, setWasPlaying] = useState(false);
|
const [wasPlaying, setWasPlaying] = useState(false);
|
||||||
const [maxWidth, setMaxWidth] = useState(0);
|
const [maxWidth, setMaxWidth] = useState(0);
|
||||||
const { settingsStore, uiPlayerStore } = useStore();
|
const { settingsStore, uiPlayerStore, sessionStore } = useStore();
|
||||||
|
const startedAt = sessionStore.current.startedAt ?? 0;
|
||||||
|
const tooltipVisible = sessionStore.timeLineTooltip.isVisible;
|
||||||
|
const setTimelineHoverTime = sessionStore.setTimelineTooltip;
|
||||||
|
const timezone = sessionStore.current.timezone;
|
||||||
|
const issues = sessionStore.current.issues;
|
||||||
const timelineZoomEnabled = uiPlayerStore.timelineZoom.enabled;
|
const timelineZoomEnabled = uiPlayerStore.timelineZoom.enabled;
|
||||||
const { playing, skipToIssue, ready, endTime, devtoolsLoading, domLoading } = store.get();
|
const { playing, skipToIssue, ready, endTime, devtoolsLoading, domLoading } = store.get();
|
||||||
const { issues, timezone } = props;
|
|
||||||
|
|
||||||
const progressRef = useRef<HTMLDivElement>(null);
|
const progressRef = useRef<HTMLDivElement>(null);
|
||||||
const timelineRef = useRef<HTMLDivElement>(null);
|
const timelineRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -51,7 +47,7 @@ function Timeline(props: IProps) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const debouncedJump = useMemo(() => debounce(player.jump, 500), []);
|
const debouncedJump = useMemo(() => debounce(player.jump, 500), []);
|
||||||
const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), []);
|
const debouncedTooltipChange = useMemo(() => debounce(setTimelineHoverTime, 50), []);
|
||||||
|
|
||||||
const onDragEnd = () => {
|
const onDragEnd = () => {
|
||||||
if (wasPlaying) {
|
if (wasPlaying) {
|
||||||
|
|
@ -78,17 +74,17 @@ function Timeline(props: IProps) {
|
||||||
// @ts-ignore black magic
|
// @ts-ignore black magic
|
||||||
!progressRef.current.contains(e.target)
|
!progressRef.current.contains(e.target)
|
||||||
) {
|
) {
|
||||||
return props.tooltipVisible && hideTimeTooltip();
|
return tooltipVisible && hideTimeTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = getTime(e);
|
const time = getTime(e);
|
||||||
if (!time) return;
|
if (!time) return;
|
||||||
const tz = settingsStore.sessionSettings.timezone.value;
|
const tz = settingsStore.sessionSettings.timezone.value;
|
||||||
const timeStr = DateTime.fromMillis(props.startedAt + time)
|
const timeStr = DateTime.fromMillis(startedAt + time)
|
||||||
.setZone(tz)
|
.setZone(tz)
|
||||||
.toFormat(`hh:mm:ss a`);
|
.toFormat(`hh:mm:ss a`);
|
||||||
const userTimeStr = timezone
|
const userTimeStr = timezone
|
||||||
? DateTime.fromMillis(props.startedAt + time)
|
? DateTime.fromMillis(startedAt + time)
|
||||||
.setZone(timezone)
|
.setZone(timezone)
|
||||||
.toFormat(`hh:mm:ss a`)
|
.toFormat(`hh:mm:ss a`)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
@ -177,12 +173,4 @@ function Timeline(props: IProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default observer(Timeline);
|
||||||
(state: any) => ({
|
|
||||||
issues: state.getIn(['sessions', 'current']).issues || [],
|
|
||||||
startedAt: state.getIn(['sessions', 'current']).startedAt || 0,
|
|
||||||
timezone: state.getIn(['sessions', 'current']).timezone,
|
|
||||||
tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']),
|
|
||||||
}),
|
|
||||||
{ setTimelinePointer, setTimelineHoverTime }
|
|
||||||
)(observer(Timeline));
|
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,13 @@ import {
|
||||||
iTag,
|
iTag,
|
||||||
tagProps,
|
tagProps,
|
||||||
} from 'App/services/NotesService';
|
} from 'App/services/NotesService';
|
||||||
import { addNote, updateNote } from 'Duck/sessions';
|
|
||||||
import { Button, Checkbox, Icon } from 'UI';
|
import { Button, Checkbox, Icon } from 'UI';
|
||||||
|
|
||||||
import Select from 'Shared/Select';
|
import Select from 'Shared/Select';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
time: number;
|
time: number;
|
||||||
addNote: (note: Note) => void;
|
|
||||||
updateNote: (note: Note) => void;
|
updateNote: (note: Note) => void;
|
||||||
sessionId: string;
|
|
||||||
isEdit?: boolean;
|
isEdit?: boolean;
|
||||||
editNote?: WriteNote;
|
editNote?: WriteNote;
|
||||||
hideModal: () => void;
|
hideModal: () => void;
|
||||||
|
|
@ -29,13 +26,13 @@ interface Props {
|
||||||
|
|
||||||
function CreateNote({
|
function CreateNote({
|
||||||
time,
|
time,
|
||||||
sessionId,
|
|
||||||
isEdit,
|
isEdit,
|
||||||
editNote,
|
editNote,
|
||||||
updateNote,
|
|
||||||
hideModal,
|
hideModal,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { notesStore, integrationsStore } = useStore();
|
const { notesStore, integrationsStore, sessionStore } = useStore();
|
||||||
|
const sessionId = sessionStore.current.sessionId;
|
||||||
|
const updateNote = sessionStore.updateNote;
|
||||||
const slackChannels = integrationsStore.slack.list;
|
const slackChannels = integrationsStore.slack.list;
|
||||||
const fetchSlack = integrationsStore.slack.fetchIntegrations;
|
const fetchSlack = integrationsStore.slack.fetchIntegrations;
|
||||||
const teamsChannels = integrationsStore.msteams.list;
|
const teamsChannels = integrationsStore.msteams.list;
|
||||||
|
|
@ -323,10 +320,4 @@ function CreateNote({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default observer(CreateNote);
|
||||||
(state: any) => {
|
|
||||||
const sessionId = state.getIn(['sessions', 'current']).sessionId;
|
|
||||||
return { sessionId };
|
|
||||||
},
|
|
||||||
{ addNote, updateNote,}
|
|
||||||
)(observer(CreateNote));
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { setAutoplayValues } from 'Duck/sessions';
|
|
||||||
import { withSiteId, session as sessionRoute } from 'App/routes';
|
import { withSiteId, session as sessionRoute } from 'App/routes';
|
||||||
import AutoplayToggle from 'Shared/AutoplayToggle/AutoplayToggle';
|
import AutoplayToggle from 'Shared/AutoplayToggle/AutoplayToggle';
|
||||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
|
|
@ -12,24 +11,21 @@ import { useStore } from 'App/mstore';
|
||||||
const PER_PAGE = 10;
|
const PER_PAGE = 10;
|
||||||
|
|
||||||
interface Props extends RouteComponentProps {
|
interface Props extends RouteComponentProps {
|
||||||
previousId: string;
|
|
||||||
nextId: string;
|
|
||||||
defaultList: any;
|
defaultList: any;
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
total: number;
|
|
||||||
setAutoplayValues: () => void;
|
|
||||||
latestRequestTime: any;
|
latestRequestTime: any;
|
||||||
sessionIds: any;
|
sessionIds: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function QueueControls(props: Props) {
|
function QueueControls(props: Props) {
|
||||||
const { projectsStore } = useStore();
|
const { projectsStore, sessionStore, searchStore } = useStore();
|
||||||
|
const previousId = sessionStore.previousId;
|
||||||
|
const nextId = sessionStore.nextId;
|
||||||
|
const total = sessionStore.total;
|
||||||
|
const sessionIds = sessionStore.sessionIds ?? [];
|
||||||
|
const setAutoplayValues = sessionStore.setAutoplayValues;
|
||||||
const {
|
const {
|
||||||
previousId,
|
|
||||||
nextId,
|
|
||||||
currentPage,
|
currentPage,
|
||||||
total,
|
|
||||||
sessionIds,
|
|
||||||
latestRequestTime,
|
latestRequestTime,
|
||||||
match: {
|
match: {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -37,19 +33,15 @@ function QueueControls(props: Props) {
|
||||||
}
|
}
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { searchStore } = useStore();
|
|
||||||
|
|
||||||
const disabled = sessionIds.length === 0;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (latestRequestTime) {
|
if (latestRequestTime) {
|
||||||
props.setAutoplayValues();
|
setAutoplayValues();
|
||||||
const totalPages = Math.ceil(total / PER_PAGE);
|
const totalPages = Math.ceil(total / PER_PAGE);
|
||||||
const index = sessionIds.indexOf(sessionId);
|
const index = sessionIds.indexOf(sessionId);
|
||||||
|
|
||||||
// check for the last page and load the next
|
// check for the last page and load the next
|
||||||
if (currentPage !== totalPages && index === sessionIds.length - 1) {
|
if (currentPage !== totalPages && index === sessionIds.length - 1) {
|
||||||
searchStore.fetchAutoplaySessions(currentPage + 1).then(props.setAutoplayValues);
|
searchStore.fetchAutoplaySessions(currentPage + 1).then(setAutoplayValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -107,12 +99,7 @@ function QueueControls(props: Props) {
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
(state: any) => ({
|
(state: any) => ({
|
||||||
previousId: state.getIn(['sessions', 'previousId']),
|
|
||||||
nextId: state.getIn(['sessions', 'nextId']),
|
|
||||||
currentPage: state.getIn(['search', 'currentPage']) || 1,
|
currentPage: state.getIn(['search', 'currentPage']) || 1,
|
||||||
total: state.getIn(['sessions', 'total']) || 0,
|
|
||||||
sessionIds: state.getIn(['sessions', 'sessionIds']) || [],
|
|
||||||
latestRequestTime: state.getIn(['search', 'latestRequestTime'])
|
latestRequestTime: state.getIn(['search', 'latestRequestTime'])
|
||||||
}),
|
}),
|
||||||
{ setAutoplayValues }
|
|
||||||
)(withRouter(QueueControls));
|
)(withRouter(QueueControls));
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,21 @@ import { BookmarkCheck, Bookmark as BookmarkIcn, Vault } from 'lucide-react';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { useStore } from 'App/mstore';
|
||||||
import { toggleFavorite } from 'Duck/sessions';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
toggleFavorite: (sessionId: string) => Promise<void>;
|
|
||||||
favorite: boolean;
|
|
||||||
sessionId: any;
|
sessionId: any;
|
||||||
isEnterprise: boolean;
|
isEnterprise: boolean;
|
||||||
noMargin?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Bookmark(props: Props) {
|
function Bookmark(props: Props) {
|
||||||
const { sessionId, favorite, isEnterprise, noMargin } = props;
|
const { sessionStore } = useStore();
|
||||||
|
const favorite = sessionStore.current.favorite;
|
||||||
|
const onToggleFavorite = sessionStore.toggleFavorite;
|
||||||
|
const { sessionId, isEnterprise } = props;
|
||||||
const [isFavorite, setIsFavorite] = useState(favorite);
|
const [isFavorite, setIsFavorite] = useState(favorite);
|
||||||
|
|
||||||
const ADDED_MESSAGE = isEnterprise
|
const ADDED_MESSAGE = isEnterprise
|
||||||
? 'Session added to vault'
|
? 'Session added to vault'
|
||||||
: 'Session added to your bookmarks';
|
: 'Session added to your bookmarks';
|
||||||
|
|
@ -33,7 +34,7 @@ function Bookmark(props: Props) {
|
||||||
}, [favorite]);
|
}, [favorite]);
|
||||||
|
|
||||||
const toggleFavorite = async () => {
|
const toggleFavorite = async () => {
|
||||||
props.toggleFavorite(sessionId).then(() => {
|
onToggleFavorite(sessionId).then(() => {
|
||||||
toast.success(isFavorite ? REMOVED_MESSAGE : ADDED_MESSAGE);
|
toast.success(isFavorite ? REMOVED_MESSAGE : ADDED_MESSAGE);
|
||||||
setIsFavorite(!isFavorite);
|
setIsFavorite(!isFavorite);
|
||||||
});
|
});
|
||||||
|
|
@ -65,8 +66,6 @@ function Bookmark(props: Props) {
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
(state: any) => ({
|
(state: any) => ({
|
||||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||||
favorite: state.getIn(['sessions', 'current']).favorite,
|
}),
|
||||||
}),
|
)(observer(Bookmark));
|
||||||
{ toggleFavorite }
|
|
||||||
)(Bookmark);
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export default class SessionStore {
|
||||||
visitedEvents: Location[] = [];
|
visitedEvents: Location[] = [];
|
||||||
insights: any[] = [];
|
insights: any[] = [];
|
||||||
host = '';
|
host = '';
|
||||||
sessionPath = {};
|
sessionPath: Record<string, any> = {};
|
||||||
lastPlayedSessionId: string = '';
|
lastPlayedSessionId: string = '';
|
||||||
timeLineTooltip = {
|
timeLineTooltip = {
|
||||||
time: 0,
|
time: 0,
|
||||||
|
|
@ -421,16 +421,6 @@ export default class SessionStore {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Note
|
|
||||||
addNote(note: Note) {
|
|
||||||
this.current.notesWithEvents.push(note);
|
|
||||||
this.current.notesWithEvents.sort((a, b) => {
|
|
||||||
const aTs = a.time || a.timestamp;
|
|
||||||
const bTs = b.time || b.timestamp;
|
|
||||||
return aTs - bTs;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Note
|
// Update Note
|
||||||
updateNote(note: Note) {
|
updateNote(note: Note) {
|
||||||
const noteIndex = this.current.notesWithEvents.findIndex((item) => {
|
const noteIndex = this.current.notesWithEvents.findIndex((item) => {
|
||||||
|
|
@ -450,7 +440,6 @@ export default class SessionStore {
|
||||||
this.sessionPath = path;
|
this.sessionPath = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Last Played Session
|
|
||||||
updateLastPlayedSession(sessionId: string) {
|
updateLastPlayedSession(sessionId: string) {
|
||||||
const sIndex = this.list.findIndex((s) => s.sessionId === sessionId);
|
const sIndex = this.list.findIndex((s) => s.sessionId === sessionId);
|
||||||
if (sIndex !== -1) {
|
if (sIndex !== -1) {
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ export default class Session {
|
||||||
duration: Duration;
|
duration: Duration;
|
||||||
durationMs: ISession['durationMs'];
|
durationMs: ISession['durationMs'];
|
||||||
events: ISession['events'];
|
events: ISession['events'];
|
||||||
|
uxtVideo?: any;
|
||||||
stackEvents: ISession['stackEvents'];
|
stackEvents: ISession['stackEvents'];
|
||||||
metadata: ISession['metadata'];
|
metadata: ISession['metadata'];
|
||||||
favorite: ISession['favorite'];
|
favorite: ISession['favorite'];
|
||||||
|
|
@ -228,6 +229,9 @@ export default class Session {
|
||||||
fileKey: ISession['fileKey'];
|
fileKey: ISession['fileKey'];
|
||||||
durationSeconds: number;
|
durationSeconds: number;
|
||||||
liveOnly: boolean;
|
liveOnly: boolean;
|
||||||
|
videoURL: string[]
|
||||||
|
screenWidth?: number
|
||||||
|
screenHeight?: number
|
||||||
|
|
||||||
constructor(plainSession?: ISession) {
|
constructor(plainSession?: ISession) {
|
||||||
const sessionData = plainSession || (emptyValues as unknown as ISession);
|
const sessionData = plainSession || (emptyValues as unknown as ISession);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue