diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/PlayerContent.js
new file mode 100644
index 000000000..8983ca5fc
--- /dev/null
+++ b/frontend/app/components/Session/PlayerContent.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import {
+ connectPlayer,
+} from 'Player';
+import PlayerBlock from '../Session_/PlayerBlock';
+import styles from '../Session_/session.module.css';
+import { countDaysFrom } from 'App/date';
+import cn from 'classnames';
+import RightBlock from './RightBlock';
+
+function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) {
+ const sessionDays = countDaysFrom(session.startedAt);
+ return (
+
+ {hasError ? (
+
+
+
+ {sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'}
+
+
+ {sessionDays > 2
+ ? 'Please check your data retention policy.'
+ : 'Please check it again in a few minutes.'}
+
+
+
+ ) : (
+
+
+ {activeTab !== '' && (
+
+ )}
+
+ )}
+
+ );
+}
+
+function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) {
+ return (
+ !live &&
+ !fullscreen &&
+ );
+}
+
+export default connectPlayer((state) => ({
+ showEvents: !state.showEvents,
+ hasError: state.error,
+}))(PlayerContent);
diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js
deleted file mode 100644
index 6e71d3ea7..000000000
--- a/frontend/app/components/Session/WebPlayer.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { connect } from 'react-redux';
-import { Loader, Modal } from 'UI';
-import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
-import { fetchList } from 'Duck/integrations';
-import { PlayerProvider, injectNotes, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player';
-import cn from 'classnames';
-import RightBlock from './RightBlock';
-import withLocationHandlers from 'HOCs/withLocationHandlers';
-import { useStore } from 'App/mstore'
-import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
-import PlayerBlock from '../Session_/PlayerBlock';
-import styles from '../Session_/session.module.css';
-import { countDaysFrom } from 'App/date';
-import ReadNote from '../Session_/Player/Controls/components/ReadNote';
-import { fetchList as fetchMembers } from 'Duck/member';
-
-const TABS = {
- EVENTS: 'User Steps',
- HEATMAPS: 'Click Map',
-};
-
-const InitLoader = connectPlayer((state) => ({
- loading: !state.initialized,
-}))(Loader);
-
-const PlayerContentConnected = connectPlayer((state) => ({
- showEvents: !state.showEvents,
- hasError: state.error,
-}))(PlayerContent);
-
-function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) {
- const sessionDays = countDaysFrom(session.startedAt);
- return (
-
- {hasError ? (
-
-
-
{sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'}
-
{sessionDays > 2 ? 'Please check your data retention policy.' : 'Please check it again in a few minutes.'}
-
-
- ) : (
-
-
- {activeTab !== '' &&
}
-
- )}
-
- );
-}
-
-function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) {
- return !live && !fullscreen && ;
-}
-
-function WebPlayer(props) {
- const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props;
- const { notesStore } = useStore()
- const [activeTab, setActiveTab] = useState('');
- const [showNoteModal, setShowNote] = useState(false)
- const [noteItem, setNoteItem] = useState(null)
-
- useEffect(() => {
- fetchList('issues');
- initPlayer(session, jwt);
- props.fetchMembers()
-
- notesStore.fetchSessionNotes(session.sessionId).then(r => {
- injectNotes(r)
- const note = props.query.get('note');
- if (note) {
- Controls.pause()
- setNoteItem(notesStore.getNoteById(parseInt(note, 10), r))
- setShowNote(true)
- }
- })
-
- const jumptTime = props.query.get('jumpto');
- if (jumptTime) {
- Controls.jump(parseInt(jumptTime));
- }
-
- return () => cleanPlayer();
- }, [session.sessionId]);
-
- // LAYOUT (TODO: local layout state - useContext or something..)
- useEffect(
- () => () => {
- toggleFullscreen(false);
- closeBottomBlock();
- },
- []
- );
-
- const onNoteClose = () => {setShowNote(false); Controls.togglePlay()}
- return (
-
-
-
-
-
- {showNoteModal ? (
- m.id === noteItem?.userId)?.email || ''}
- note={noteItem}
- onClose={onNoteClose}
- notFound={!noteItem}
- />
- ) : null}
-
-
-
- );
-}
-
-export default connect(
- (state) => ({
- session: state.getIn(['sessions', 'current']),
- jwt: state.get('jwt'),
- fullscreen: state.getIn(['components', 'player', 'fullscreen']),
- showEvents: state.get('showEvents'),
- members: state.getIn(['members', 'list']),
- }),
- {
- toggleFullscreen,
- closeBottomBlock,
- fetchList,
- fetchMembers,
- }
-)(withLocationHandlers()(WebPlayer));
diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx
new file mode 100644
index 000000000..811d6b9ba
--- /dev/null
+++ b/frontend/app/components/Session/WebPlayer.tsx
@@ -0,0 +1,127 @@
+import React, { useEffect, useState } from 'react';
+import { connect } from 'react-redux';
+import { Modal } from 'UI';
+import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
+import { fetchList } from 'Duck/integrations';
+import {
+ PlayerProvider,
+ createWebPlayer,
+} from 'Player';
+
+import withLocationHandlers from 'HOCs/withLocationHandlers';
+import { useStore } from 'App/mstore';
+import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
+import ReadNote from '../Session_/Player/Controls/components/ReadNote';
+import { fetchList as fetchMembers } from 'Duck/member';
+import PlayerContent from './PlayerContent'
+import {
+ IPlayerContext,
+ PlayerContext,
+ defaultContextValue
+} from './playerContext'
+
+const TABS = {
+ EVENTS: 'User Steps',
+ HEATMAPS: 'Click Map',
+};
+
+function WebPlayer(props: any) {
+ const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props;
+ const { notesStore } = useStore();
+ const [activeTab, setActiveTab] = useState('');
+ const [showNoteModal, setShowNote] = useState(false);
+ const [noteItem, setNoteItem] = useState(null);
+ const [contextValue, setContextValue] = useState(defaultContextValue)
+
+ useEffect(() => {
+ fetchList('issues');
+ const [WebPlayerInst, PlayerStore] = createWebPlayer(session, jwt);
+ setContextValue({ player: WebPlayerInst, store: PlayerStore })
+
+ // initPlayer(session, jwt); TODOPlayer
+ props.fetchMembers();
+
+ notesStore.fetchSessionNotes(session.sessionId).then((r) => {
+ // WebPlayerInst.injectNotes(r);
+ // PlayerStore.update({ notes: r })
+ const note = props.query.get('note');
+ if (note) {
+ WebPlayerInst.pause();
+ setNoteItem(notesStore.getNoteById(parseInt(note, 10), r));
+ setShowNote(true);
+ }
+ });
+
+ const jumptTime = props.query.get('jumpto');
+ if (jumptTime) {
+ WebPlayerInst.jump(parseInt(jumptTime));
+ }
+
+ return () => WebPlayerInst.clean();
+ }, [session.sessionId]);
+
+ // LAYOUT (TODO: local layout state - useContext or something..)
+ useEffect(
+ () => () => {
+ toggleFullscreen(false);
+ closeBottomBlock();
+ },
+ []
+ );
+
+ const onNoteClose = () => {
+ setShowNote(false);
+ contextValue.player.togglePlay();
+ };
+
+ if (!contextValue.player) return null;
+
+ return (
+
+
+ <>
+
+ {/* @ts-ignore */}
+
+
+ {showNoteModal ? (
+ ) => m.id === noteItem?.userId)?.email || ''}
+ note={noteItem}
+ onClose={onNoteClose}
+ notFound={!noteItem}
+ />
+ ) : null}
+
+ >
+
+
+ );
+}
+
+export default connect(
+ (state: any) => ({
+ session: state.getIn(['sessions', 'current']),
+ jwt: state.get('jwt'),
+ fullscreen: state.getIn(['components', 'player', 'fullscreen']),
+ showEvents: state.get('showEvents'),
+ members: state.getIn(['members', 'list']),
+ }),
+ {
+ toggleFullscreen,
+ closeBottomBlock,
+ fetchList,
+ fetchMembers,
+ }
+)(withLocationHandlers()(WebPlayer));
diff --git a/frontend/app/components/Session/playerContext.ts b/frontend/app/components/Session/playerContext.ts
new file mode 100644
index 000000000..7068b982f
--- /dev/null
+++ b/frontend/app/components/Session/playerContext.ts
@@ -0,0 +1,12 @@
+import { createContext } from 'react';
+import {
+ IWebPlayer,
+ IStore
+} from 'Player'
+
+export interface IPlayerContext {
+ player: IWebPlayer
+ store: IStore,
+}
+export const defaultContextValue: IPlayerContext = { player: undefined, store: undefined}
+export const PlayerContext = createContext(defaultContextValue);
diff --git a/frontend/app/components/Session_/Console/Console.js b/frontend/app/components/Session_/Console/Console.js
deleted file mode 100644
index 3c4a3752c..000000000
--- a/frontend/app/components/Session_/Console/Console.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import { connectPlayer, jump } from 'Player';
-import ConsoleContent from './ConsoleContent';
-
-@connectPlayer(state => ({
- logs: state.logList,
- // time: state.time,
- livePlay: state.livePlay,
- listNow: state.logListNow,
-}))
-export default class Console extends React.PureComponent {
- render() {
- const { logs, time, listNow } = this.props;
- return (
-
- );
- }
-}
diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js
deleted file mode 100644
index a7482b69e..000000000
--- a/frontend/app/components/Session_/Console/ConsoleContent.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import React from 'react';
-import cn from 'classnames';
-import { getRE } from 'App/utils';
-import { Icon, NoContent, Tabs, Input } from 'UI';
-import { jump } from 'Player';
-import { LEVEL } from 'Types/session/log';
-import Autoscroll from '../Autoscroll';
-import BottomBlock from '../BottomBlock';
-import stl from './console.module.css';
-import ConsoleRow from './ConsoleRow';
-// import { Duration } from 'luxon';
-
-const ALL = 'ALL';
-const INFO = 'INFO';
-const WARNINGS = 'WARNINGS';
-const ERRORS = 'ERRORS';
-
-const LEVEL_TAB = {
- [LEVEL.INFO]: INFO,
- [LEVEL.LOG]: INFO,
- [LEVEL.WARNING]: WARNINGS,
- [LEVEL.ERROR]: ERRORS,
- [LEVEL.EXCEPTION]: ERRORS,
-};
-
-const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab }));
-
-// eslint-disable-next-line complexity
-const getIconProps = (level) => {
- switch (level) {
- case LEVEL.INFO:
- case LEVEL.LOG:
- return {
- name: 'console/info',
- color: 'blue2',
- };
- case LEVEL.WARN:
- case LEVEL.WARNING:
- return {
- name: 'console/warning',
- color: 'red2',
- };
- case LEVEL.ERROR:
- return {
- name: 'console/error',
- color: 'red',
- };
- }
- return null;
-};
-
-function renderWithNL(s = '') {
- if (typeof s !== 'string') return '';
- return s.split('\n').map((line, i) => {line}
);
-}
-
-export default class ConsoleContent extends React.PureComponent {
- state = {
- filter: '',
- activeTab: ALL,
- };
- onTabClick = (activeTab) => this.setState({ activeTab });
- onFilterChange = ({ target: { value } }) => this.setState({ filter: value });
-
- render() {
- const { logs, isResult, additionalHeight, logsNow } = this.props;
- const time = logsNow.length > 0 ? logsNow[logsNow.length - 1].time : undefined;
- const { filter, activeTab, currentError } = this.state;
- const filterRE = getRE(filter, 'i');
- const filtered = logs.filter(({ level, value }) =>
- activeTab === ALL
- ? filterRE.test(value)
- : filterRE.test(value) && LEVEL_TAB[level] === activeTab
- );
-
- const lastIndex = filtered.filter((item) => item.time <= time).length - 1;
-
- return (
- <>
-
-
-
- Console
-
-
-
-
-
-
-
- No Data
-
- }
- size="small"
- show={filtered.length === 0}
- >
-
- {filtered.map((l) => (
-
- ))}
-
-
-
-
- >
- );
- }
-}
diff --git a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx
deleted file mode 100644
index c87ff3f9c..000000000
--- a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, { useState } from 'react';
-import cn from 'classnames';
-import stl from '../console.module.css';
-import { Icon } from 'UI';
-import JumpButton from 'Shared/DevTools/JumpButton';
-
-interface Props {
- log: any;
- iconProps: any;
- jump?: any;
- renderWithNL?: any;
-}
-function ConsoleRow(props: Props) {
- const { log, iconProps, jump, renderWithNL } = props;
- const [expanded, setExpanded] = useState(false);
- const lines = log.value.split('\n').filter((l: any) => !!l);
- const canExpand = lines.length > 1;
- return (
- setExpanded(!expanded)}
- >
-
-
-
- {/*
- {Duration.fromMillis(log.time).toFormat('mm:ss.SSS')}
-
*/}
-
-
- {canExpand && (
-
- )}
- {renderWithNL(lines.pop())}
-
- {canExpand && expanded && lines.map((l: any) =>
{l}
)}
-
-
jump(log.time)} />
-
- );
-}
-
-export default ConsoleRow;
diff --git a/frontend/app/components/Session_/Console/ConsoleRow/index.ts b/frontend/app/components/Session_/Console/ConsoleRow/index.ts
deleted file mode 100644
index c9140d748..000000000
--- a/frontend/app/components/Session_/Console/ConsoleRow/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ConsoleRow';
diff --git a/frontend/app/components/Session_/Console/console.module.css b/frontend/app/components/Session_/Console/console.module.css
deleted file mode 100644
index 2da78f540..000000000
--- a/frontend/app/components/Session_/Console/console.module.css
+++ /dev/null
@@ -1,38 +0,0 @@
-
-.message {
- overflow-x: auto;
- margin-left: 10px;
- font-size: 13px;
- overflow-x: auto;
- &::-webkit-scrollbar {
- height: 2px;
- }
-}
-
-.line {
- font-family: 'Menlo', 'monaco', 'consolas', monospace;
- /* margin-top: -1px; ??? */
- display: flex;
- align-items: flex-start;
- border-bottom: solid thin $gray-light-shade;
- &:hover {
- background-coor: $active-blue !important;
- }
-}
-
-.timestamp {
-
-}
-
-.activeRow {
- background-color: $teal-light !important;
-}
-
-.icon {
- padding-top: 4px;
- margin-right: 7px;
-}
-
-.inactiveRow {
- opacity: 0.5;
-}
\ No newline at end of file
diff --git a/frontend/app/components/Session_/Inspector/index.js b/frontend/app/components/Session_/Inspector/index.js
index f76834fee..38d44149a 100644
--- a/frontend/app/components/Session_/Inspector/index.js
+++ b/frontend/app/components/Session_/Inspector/index.js
@@ -53,10 +53,10 @@ export default function Inspector () {
if (!doc) return null;
return (
-
+
markElement(null) } className={stl.wrapper}>
-
);
-}
\ No newline at end of file
+}
diff --git a/frontend/app/components/Session_/LongTasks/LongTasks.js b/frontend/app/components/Session_/LongTasks/LongTasks.js
index fd3b4cc17..2fa80fd01 100644
--- a/frontend/app/components/Session_/LongTasks/LongTasks.js
+++ b/frontend/app/components/Session_/LongTasks/LongTasks.js
@@ -40,12 +40,12 @@ export default class GraphQL extends React.PureComponent {
const { filter, current } = this.state;
const filterRE = getRE(filter, 'i');
const filtered = list
- .filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) =>
- filterRE.test(containerName) ||
+ .filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) =>
+ filterRE.test(containerName) ||
filterRE.test(containerId) ||
filterRE.test(containerSrc) ||
filterRE.test(CONTEXTS[ context ]) ||
- filterRE.test(CONTAINER_TYPES[ containerType ]));
+ filterRE.test(CONTAINER_TYPES[ containerType ]));
const lastIndex = filtered.filter(item => item.time <= time).length - 1;
return (
@@ -64,7 +64,7 @@ export default class GraphQL extends React.PureComponent {
- Learn more
+ Learn more
about Long Tasks API
>
}
diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js
index 48d881c29..ea3640847 100644
--- a/frontend/app/components/Session_/Player/Player.js
+++ b/frontend/app/components/Session_/Player/Player.js
@@ -20,21 +20,13 @@ import {
OVERVIEW,
} from 'Duck/components/player';
import NetworkPanel from 'Shared/DevTools/NetworkPanel';
-import Console from '../Console/Console';
import StackEvents from '../StackEvents/StackEvents';
import Storage from '../Storage';
-import Profiler from '../Profiler';
import { ConnectedPerformance } from '../Performance';
import GraphQL from '../GraphQL';
import Exceptions from '../Exceptions/Exceptions';
import LongTasks from '../LongTasks';
import Inspector from '../Inspector';
-import {
- attach as attachPlayer,
- Controls as PlayerControls,
- scale as scalePlayerScreen,
- connectPlayer,
-} from 'Player';
import Controls from './Controls';
import Overlay from './Overlay';
import stl from './player.module.css';
@@ -42,70 +34,47 @@ import { updateLastPlayedSession } from 'Duck/sessions';
import OverviewPanel from '../OverviewPanel';
import ConsolePanel from 'Shared/DevTools/ConsolePanel';
import ProfilerPanel from 'Shared/DevTools/ProfilerPanel';
+import { PlayerContext } from 'App/components/Session/playerContext';
-@connectPlayer((state) => ({
- live: state.live,
-}))
-@connect(
- (state) => {
- const isAssist = window.location.pathname.includes('/assist/');
- return {
- fullscreen: state.getIn(['components', 'player', 'fullscreen']),
- nextId: state.getIn(['sessions', 'nextId']),
- sessionId: state.getIn(['sessions', 'current', 'sessionId']),
- closedLive:
- !!state.getIn(['sessions', 'errors']) ||
- (isAssist && !state.getIn(['sessions', 'current', 'live'])),
- };
- },
- {
- hideTargetDefiner,
+function Player(props) {
+ const {
+ className,
+ bottomBlockIsActive,
+ fullscreen,
fullscreenOff,
- updateLastPlayedSession,
- }
-)
-export default class Player extends React.PureComponent {
- screenWrapper = React.createRef();
+ nextId,
+ closedLive,
+ bottomBlock,
+ activeTab,
+ } = props;
+ const playerContext = React.useContext(PlayerContext)
+ const screenWrapper = React.useRef();
- componentDidUpdate(prevProps) {
- if (
- [prevProps.bottomBlock, this.props.bottomBlock].includes(NONE) ||
- prevProps.fullscreen !== this.props.fullscreen
- ) {
- scalePlayerScreen();
+ React.useEffect(() => {
+ props.updateLastPlayedSession(props.sessionId);
+ if (!props.closedLive) {
+ const parentElement = findDOMNode(screenWrapper.current); //TODO: good architecture
+ playerContext.player.attach(parentElement);
}
- }
- componentDidMount() {
- this.props.updateLastPlayedSession(this.props.sessionId);
- if (this.props.closedLive) return;
+ }, [])
- const parentElement = findDOMNode(this.screenWrapper.current); //TODO: good architecture
- attachPlayer(parentElement);
- }
+ React.useEffect(() => {
+ playerContext.player.scale();
+ }, [props.bottomBlock, props.fullscreen, playerContext.player])
- render() {
- const {
- className,
- bottomBlockIsActive,
- fullscreen,
- fullscreenOff,
- nextId,
- closedLive,
- bottomBlock,
- activeTab,
- } = this.props;
+ if (!playerContext.player) return null;
- const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw';
- return (
-
{fullscreen &&
}
{!fullscreen && !!bottomBlock && (
@@ -125,8 +94,25 @@ export default class Player extends React.PureComponent {
{bottomBlock === INSPECTOR && }
)}
-
+
- );
- }
+ )
}
+
+export default connect((state) => {
+ const isAssist = window.location.pathname.includes('/assist/');
+ return {
+ fullscreen: state.getIn(['components', 'player', 'fullscreen']),
+ nextId: state.getIn(['sessions', 'nextId']),
+ sessionId: state.getIn(['sessions', 'current', 'sessionId']),
+ closedLive:
+ !!state.getIn(['sessions', 'errors']) ||
+ (isAssist && !state.getIn(['sessions', 'current', 'live'])),
+ };
+ },
+ {
+ hideTargetDefiner,
+ fullscreenOff,
+ updateLastPlayedSession,
+ }
+)(Player)
diff --git a/frontend/app/components/Session_/Profiler/Profiler.js b/frontend/app/components/Session_/Profiler/Profiler.js
index 398560a3d..9b9e6e42c 100644
--- a/frontend/app/components/Session_/Profiler/Profiler.js
+++ b/frontend/app/components/Session_/Profiler/Profiler.js
@@ -33,7 +33,7 @@ export default class Profiler extends React.PureComponent {
return (
- }
@@ -55,7 +55,7 @@ export default class Profiler extends React.PureComponent {
/>
-
+export type IWebPlayer = WebPlayer
+export type IWebPlayerStore = Store
-export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] {
+export function createWebPlayer(session: Record, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] {
let store: WebPlayerStore = new SimpleStore({
...Player.INITIAL_STATE,
...MM_INITIAL_STATE,
@@ -20,7 +22,7 @@ export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPl
}
-export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] {
+export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] {
let store: WebPlayerStore = new SimpleStore({
...Player.INITIAL_STATE,
...MM_INITIAL_STATE,
@@ -30,4 +32,4 @@ export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?:
}
const player = new WebPlayer(store, session, config, true)
return [player, store]
-}
\ No newline at end of file
+}