diff --git a/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx index 5f4bf2b46..9fd3f8e0e 100644 --- a/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx @@ -1,16 +1,17 @@ import React, { useEffect } from 'react'; import { fetchLiveList } from 'Duck/sessions'; import { connect } from 'react-redux'; -import { NoContent, Loader } from 'UI'; +import { NoContent, Loader, LoadMoreButton } from 'UI'; import { List, Map } from 'immutable'; import SessionItem from 'Shared/SessionItem'; import withPermissions from 'HOCs/withPermissions' import { KEYS } from 'Types/filter/customFilter'; import { applyFilter, addAttribute } from 'Duck/filters'; import { FilterCategory, FilterKey } from 'App/types/filter/filterType'; -import { addFilterByKeyAndValue } from 'Duck/liveSearch'; +import { addFilterByKeyAndValue, updateCurrentPage } from 'Duck/liveSearch'; const AUTOREFRESH_INTERVAL = .5 * 60 * 1000 +const PER_PAGE = 20; interface Props { loading: Boolean, @@ -20,14 +21,20 @@ interface Props { filters: any, addAttribute: (obj) => void, addFilterByKeyAndValue: (key: FilterKey, value: string) => void, + updateCurrentPage: (page: number) => void, + currentPage: number, } function LiveSessionList(props: Props) { - const { loading, filters, list } = props; + const { loading, filters, list, currentPage } = props; var timeoutId; const hasUserFilter = filters.map(i => i.key).includes(KEYS.USERID); const [sessions, setSessions] = React.useState(list); + const displayedCount = Math.min(currentPage * PER_PAGE, sessions.size); + + const addPage = () => props.updateCurrentPage(props.currentPage + 1) + useEffect(() => { if (filters.size === 0) { props.addFilterByKeyAndValue(FilterKey.USERID, ''); @@ -92,7 +99,7 @@ function LiveSessionList(props: Props) { show={ !loading && sessions && sessions.size === 0} > - {sessions && sessions.map(session => ( + {sessions && sessions.take(displayedCount).map(session => ( ))} + + @@ -112,6 +126,7 @@ export default withPermissions(['ASSIST_LIVE', 'SESSION_REPLAY'])(connect( list: state.getIn(['sessions', 'liveSessions']), loading: state.getIn([ 'sessions', 'loading' ]), filters: state.getIn([ 'liveSearch', 'instance', 'filters' ]), + currentPage: state.getIn(["liveSearch", "currentPage"]), }), - { fetchLiveList, applyFilter, addAttribute, addFilterByKeyAndValue } + { fetchLiveList, applyFilter, addAttribute, addFilterByKeyAndValue, updateCurrentPage } )(LiveSessionList)); diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js index 04854a444..c7f282a30 100644 --- a/frontend/app/components/Session/WebPlayer.js +++ b/frontend/app/components/Session/WebPlayer.js @@ -7,9 +7,11 @@ import { connectPlayer, init as initPlayer, clean as cleanPlayer, + Controls, } from 'Player'; import cn from 'classnames' import RightBlock from './RightBlock' +import withLocationHandlers from "HOCs/withLocationHandlers"; import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; @@ -35,9 +37,17 @@ function PlayerContent({ live, fullscreen, showEvents }) { ) } -function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, config }) { +function WebPlayer (props) { + const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, config } = props; + useEffect(() => { initPlayer(session, jwt, config); + + const jumptTime = props.query.get('jumpto'); + if (jumptTime) { + Controls.jump(parseInt(jumptTime)); + } + return () => cleanPlayer() }, [ session.sessionId ]); @@ -56,7 +66,6 @@ function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscr ); } - export default connect(state => ({ session: state.getIn([ 'sessions', 'current' ]), jwt: state.get('jwt'), @@ -65,5 +74,4 @@ export default connect(state => ({ }), { toggleFullscreen, closeBottomBlock, -})(WebPlayer) - +})(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session_/PlayerBlockHeader.js b/frontend/app/components/Session_/PlayerBlockHeader.js index 4f31dfeb7..d78e115c8 100644 --- a/frontend/app/components/Session_/PlayerBlockHeader.js +++ b/frontend/app/components/Session_/PlayerBlockHeader.js @@ -143,6 +143,7 @@ export default class PlayerBlockHeader extends React.PureComponent { { + setCopied(true); + copy(window.location.origin + window.location.pathname + '?jumpto=' + Math.round(time)); + setTimeout(() => { + setCopied(false); + }, 1000); + }; -function SessionCopyLink() { return ( -
- -
Copied to Clipboard
+
+ + { copied &&
Copied to Clipboard
}
) } -export default SessionCopyLink \ No newline at end of file +export default connectPlayer(state => ({ + time: state.time, +}))(SessionCopyLink); \ No newline at end of file diff --git a/frontend/app/components/shared/SharePopup/SharePopup.js b/frontend/app/components/shared/SharePopup/SharePopup.js index 482b04e17..43c960bba 100644 --- a/frontend/app/components/shared/SharePopup/SharePopup.js +++ b/frontend/app/components/shared/SharePopup/SharePopup.js @@ -47,7 +47,7 @@ export default class SharePopup extends React.PureComponent { changeChannel = (e, { value }) => this.setState({ channelId: value }) render() { - const { trigger, loading, channels } = this.props; + const { trigger, loading, channels, showCopyLink = false } = this.props; const { comment, isOpen, channelId } = this.state; const options = channels.map(({ webhookId, name }) => ({ value: webhookId, text: name })).toJS(); @@ -67,9 +67,11 @@ export default class SharePopup extends React.PureComponent {
-
- -
+ { showCopyLink && ( +
+ +
+ )} :
@@ -78,32 +80,34 @@ export default class SharePopup extends React.PureComponent { name="message" id="message" cols="30" - rows="6" + rows="4" resize="none" onChange={ this.editMessage } value={ comment } placeholder="Type here..." className="p-4" /> + +
+ +
+ +
+
- -
- -
-
diff --git a/frontend/app/components/shared/SharePopup/sharePopup.css b/frontend/app/components/shared/SharePopup/sharePopup.css index adbc29ff8..c28285457 100644 --- a/frontend/app/components/shared/SharePopup/sharePopup.css +++ b/frontend/app/components/shared/SharePopup/sharePopup.css @@ -35,13 +35,18 @@ border-radius: 3px; resize: none; } + margin-bottom: 14px; } .footer { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 0; + /* display: flex; */ + /* align-items: center; */ + /* justify-content: space-between; */ + /* padding: 10px 0; */ + border-top: solid thin $gray-light; + margin: 0 -14px; + padding: 0 14px; + /* border-bottom: solid thin $gray-light; */ } textarea { diff --git a/frontend/app/components/shared/TrackingCodeModal/CopyButton/CopyButton.js b/frontend/app/components/shared/TrackingCodeModal/CopyButton/CopyButton.js index d080b1a02..5e74e7b80 100644 --- a/frontend/app/components/shared/TrackingCodeModal/CopyButton/CopyButton.js +++ b/frontend/app/components/shared/TrackingCodeModal/CopyButton/CopyButton.js @@ -17,7 +17,7 @@ function CopyButton({ content, className }) { className={ className } onClick={ copyHandler } > - { copied ? 'copied' : 'copy' } + { copied ? 'Copied' : 'Copy' } ) } diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index 9aa1cbde1..38f90d35b 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -16,11 +16,13 @@ const FETCH = fetchType(name); const EDIT = editType(name); const CLEAR_SEARCH = `${name}/CLEAR_SEARCH`; const APPLY = `${name}/APPLY`; +const UPDATE_CURRENT_PAGE = `${name}/UPDATE_CURRENT_PAGE`; const initialState = Map({ list: List(), instance: new Filter({ filters: [] }), filterSearchList: {}, + currentPage: 1, }); @@ -28,6 +30,8 @@ function reducer(state = initialState, action = {}) { switch (action.type) { case EDIT: return state.mergeIn(['instance'], action.instance); + case UPDATE_CURRENT_PAGE: + return state.set('currentPage', action.page); } return state; } @@ -90,3 +94,10 @@ export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => { defaultFilter.value = value; dispatch(addFilter(defaultFilter)); } + +export function updateCurrentPage(page) { + return { + type: UPDATE_CURRENT_PAGE, + page, + }; +} \ No newline at end of file