feat(ui) - autoplay with pagination and checking for latest sessions, also a fix
This commit is contained in:
parent
3048d12c7c
commit
0e99cd7c59
8 changed files with 232 additions and 70 deletions
|
|
@ -4,13 +4,52 @@ import { setAutoplayValues } from 'Duck/sessions';
|
|||
import { session as sessionRoute } from 'App/routes';
|
||||
import { Link, Icon, Toggler, Tooltip } from 'UI';
|
||||
import { Controls as PlayerControls, connectPlayer } from 'Player';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import cn from 'classnames';
|
||||
import { fetchAutoplaySessions } from 'Duck/search';
|
||||
|
||||
function Autoplay(props) {
|
||||
const { previousId, nextId, autoplay, disabled } = props;
|
||||
const PER_PAGE = 10;
|
||||
|
||||
interface Props extends RouteComponentProps {
|
||||
previousId: string;
|
||||
nextId: string;
|
||||
autoplay: boolean;
|
||||
defaultList: any;
|
||||
currentPage: number;
|
||||
total: number;
|
||||
setAutoplayValues?: () => void;
|
||||
toggleAutoplay?: () => void;
|
||||
latestRequestTime: any;
|
||||
sessionIds: any;
|
||||
fetchAutoplaySessions?: (page: number) => Promise<void>;
|
||||
}
|
||||
function Autoplay(props: Props) {
|
||||
const {
|
||||
previousId,
|
||||
nextId,
|
||||
currentPage,
|
||||
total,
|
||||
autoplay,
|
||||
sessionIds,
|
||||
latestRequestTime,
|
||||
match: {
|
||||
// @ts-ignore
|
||||
params: { siteId, sessionId },
|
||||
},
|
||||
} = props;
|
||||
const disabled = sessionIds.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
props.setAutoplayValues();
|
||||
if (latestRequestTime) {
|
||||
props.setAutoplayValues();
|
||||
const totalPages = Math.ceil(total / PER_PAGE);
|
||||
const index = sessionIds.indexOf(sessionId);
|
||||
|
||||
// check for the last page and load the next
|
||||
if (currentPage !== totalPages && index === sessionIds.length - 1) {
|
||||
props.fetchAutoplaySessions(currentPage + 1).then(props.setAutoplayValues);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
@ -62,21 +101,23 @@ function Autoplay(props) {
|
|||
);
|
||||
}
|
||||
|
||||
const connectAutoplay = connect(
|
||||
(state) => ({
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
previousId: state.getIn(['sessions', 'previousId']),
|
||||
nextId: state.getIn(['sessions', 'nextId']),
|
||||
currentPage: state.getIn(['search', 'currentPage']) || 1,
|
||||
total: state.getIn(['sessions', 'total']) || 0,
|
||||
sessionIds: state.getIn(['sessions', 'sessionIds']) || [],
|
||||
latestRequestTime: state.getIn(['search', 'latestRequestTime']),
|
||||
}),
|
||||
{ setAutoplayValues }
|
||||
);
|
||||
|
||||
export default connectAutoplay(
|
||||
{ setAutoplayValues, fetchAutoplaySessions }
|
||||
)(
|
||||
connectPlayer(
|
||||
(state) => ({
|
||||
(state: any) => ({
|
||||
autoplay: state.autoplay,
|
||||
}),
|
||||
{
|
||||
toggleAutoplay: PlayerControls.toggleAutoplay,
|
||||
}
|
||||
)(Autoplay)
|
||||
)(withRouter(Autoplay))
|
||||
);
|
||||
|
|
@ -4,6 +4,7 @@ import SessionHeader from './components/SessionHeader';
|
|||
import NotesList from './components/Notes/NoteList';
|
||||
import { connect } from 'react-redux';
|
||||
import { fetchList as fetchMembers } from 'Duck/member';
|
||||
import LatestSessionsMessage from './components/LatestSessionsMessage';
|
||||
|
||||
function SessionListContainer({
|
||||
activeTab,
|
||||
|
|
@ -21,6 +22,7 @@ function SessionListContainer({
|
|||
<div className="widget-wrapper">
|
||||
<SessionHeader />
|
||||
<div className="border-b" />
|
||||
<LatestSessionsMessage />
|
||||
{activeTab !== 'notes' ? <SessionList /> : <NotesList members={members} />}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { updateCurrentPage } from 'Duck/search';
|
||||
import { numberWithCommas } from 'App/utils'
|
||||
|
||||
interface Props {
|
||||
latestSessions: any;
|
||||
updateCurrentPage: (page: number) => void;
|
||||
}
|
||||
function LatestSessionsMessage(props: Props) {
|
||||
const { latestSessions = [] } = props;
|
||||
const count = latestSessions.length;
|
||||
return count > 0 ? (
|
||||
<div
|
||||
className="bg-amber-50 p-1 flex w-full border-b text-center justify-center link"
|
||||
style={{ backgroundColor: 'rgb(255 251 235)' }}
|
||||
onClick={() => props.updateCurrentPage(1)}
|
||||
>
|
||||
Show {numberWithCommas(count)} new Sessions
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
latestSessions: state.getIn(['search', 'latestList']),
|
||||
}),
|
||||
{ updateCurrentPage }
|
||||
)(LatestSessionsMessage);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './LatestSessionsMessage';
|
||||
|
|
@ -9,24 +9,25 @@ import {
|
|||
addFilterByKeyAndValue,
|
||||
updateCurrentPage,
|
||||
setScrollPosition,
|
||||
checkForLatestSessions,
|
||||
} from 'Duck/search';
|
||||
import useTimeout from 'App/hooks/useTimeout';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
import { fetchListActive as fetchMetadata } from 'Duck/customField';
|
||||
|
||||
enum NoContentType {
|
||||
Bookmarked,
|
||||
Vaulted,
|
||||
ToDate,
|
||||
Bookmarked,
|
||||
Vaulted,
|
||||
ToDate,
|
||||
}
|
||||
|
||||
const AUTOREFRESH_INTERVAL = 5 * 60 * 1000;
|
||||
const PER_PAGE = 10;
|
||||
let sessionTimeOut: any = null;
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
list: any;
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
filters: any;
|
||||
lastPlayedSessionId: string;
|
||||
|
|
@ -39,13 +40,15 @@ interface Props {
|
|||
fetchMetadata: () => void;
|
||||
activeTab: any;
|
||||
isEnterprise?: boolean;
|
||||
checkForLatestSessions: () => void;
|
||||
}
|
||||
function SessionList(props: Props) {
|
||||
const [noContentType, setNoContentType] = React.useState<NoContentType>(NoContentType.ToDate)
|
||||
const [noContentType, setNoContentType] = React.useState<NoContentType>(NoContentType.ToDate);
|
||||
const {
|
||||
loading,
|
||||
list,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
filters,
|
||||
lastPlayedSessionId,
|
||||
|
|
@ -60,19 +63,19 @@ function SessionList(props: Props) {
|
|||
const isVault = isBookmark && isEnterprise;
|
||||
const NO_CONTENT = React.useMemo(() => {
|
||||
if (isBookmark && !isEnterprise) {
|
||||
setNoContentType(NoContentType.Bookmarked)
|
||||
setNoContentType(NoContentType.Bookmarked);
|
||||
return {
|
||||
icon: ICONS.NO_BOOKMARKS,
|
||||
message: 'No sessions bookmarked.',
|
||||
};
|
||||
} else if (isVault) {
|
||||
setNoContentType(NoContentType.Vaulted)
|
||||
setNoContentType(NoContentType.Vaulted);
|
||||
return {
|
||||
icon: ICONS.NO_SESSIONS_IN_VAULT,
|
||||
message: 'No sessions found in vault.',
|
||||
};
|
||||
}
|
||||
setNoContentType(NoContentType.ToDate)
|
||||
setNoContentType(NoContentType.ToDate);
|
||||
return {
|
||||
icon: ICONS.NO_SESSIONS,
|
||||
message: 'No relevant sessions found for the selected time period.',
|
||||
|
|
@ -81,7 +84,7 @@ function SessionList(props: Props) {
|
|||
|
||||
useTimeout(() => {
|
||||
if (!document.hidden) {
|
||||
props.fetchSessions(null, true);
|
||||
props.checkForLatestSessions();
|
||||
}
|
||||
}, AUTOREFRESH_INTERVAL);
|
||||
|
||||
|
|
@ -107,7 +110,7 @@ function SessionList(props: Props) {
|
|||
|
||||
sessionTimeOut = setTimeout(function () {
|
||||
if (!document.hidden) {
|
||||
props.fetchSessions(null, true);
|
||||
props.checkForLatestSessions();
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
|
@ -182,15 +185,15 @@ function SessionList(props: Props) {
|
|||
{total > 0 && (
|
||||
<div className="flex items-center justify-between p-5">
|
||||
<div>
|
||||
Showing <span className="font-medium">{(currentPage - 1) * PER_PAGE + 1}</span> to{' '}
|
||||
<span className="font-medium">{(currentPage - 1) * PER_PAGE + list.size}</span> of{' '}
|
||||
Showing <span className="font-medium">{(currentPage - 1) * pageSize + 1}</span> to{' '}
|
||||
<span className="font-medium">{(currentPage - 1) * pageSize + list.size}</span> of{' '}
|
||||
<span className="font-medium">{numberWithCommas(total)}</span> sessions.
|
||||
</div>
|
||||
<Pagination
|
||||
page={currentPage}
|
||||
totalPages={Math.ceil(total / PER_PAGE)}
|
||||
totalPages={Math.ceil(total / pageSize)}
|
||||
onPageChange={(page) => props.updateCurrentPage(page)}
|
||||
limit={PER_PAGE}
|
||||
limit={pageSize}
|
||||
debounceRequest={1000}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -210,7 +213,15 @@ export default connect(
|
|||
total: state.getIn(['sessions', 'total']) || 0,
|
||||
scrollY: state.getIn(['search', 'scrollY']),
|
||||
activeTab: state.getIn(['search', 'activeTab']),
|
||||
pageSize: state.getIn(['search', 'pageSize']),
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
}),
|
||||
{ updateCurrentPage, addFilterByKeyAndValue, setScrollPosition, fetchSessions, fetchMetadata }
|
||||
{
|
||||
updateCurrentPage,
|
||||
addFilterByKeyAndValue,
|
||||
setScrollPosition,
|
||||
fetchSessions,
|
||||
fetchMetadata,
|
||||
checkForLatestSessions,
|
||||
}
|
||||
)(SessionList);
|
||||
|
|
|
|||
|
|
@ -5,16 +5,18 @@ import { array, success, createListUpdater, mergeReducers } from './funcTools/to
|
|||
import Filter from 'Types/filter';
|
||||
import SavedFilter from 'Types/filter/savedFilter';
|
||||
import { errors as errorsRoute, isRoute } from 'App/routes';
|
||||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { fetchList as fetchSessionList, fetchAutoplayList } from './sessions';
|
||||
import { fetchList as fetchErrorsList } from './errors';
|
||||
import { FilterCategory, FilterKey } from 'Types/filter/filterType';
|
||||
import { filtersMap, liveFiltersMap, generateFilterOptions } from 'Types/filter/newFilter';
|
||||
import { DURATION_FILTER } from 'App/constants/storageKeys';
|
||||
import Period, { CUSTOM_RANGE } from 'Types/app/period';
|
||||
|
||||
const ERRORS_ROUTE = errorsRoute();
|
||||
|
||||
const name = 'search';
|
||||
const idKey = 'searchId';
|
||||
const PER_PAGE = 10;
|
||||
|
||||
const FETCH_LIST = fetchListType(name);
|
||||
const FETCH_FILTER_SEARCH = fetchListType(`${name}/FILTER_SEARCH`);
|
||||
|
|
@ -33,25 +35,30 @@ const SET_ACTIVE_TAB = `${name}/SET_ACTIVE_TAB`;
|
|||
const SET_SCROLL_POSITION = `${name}/SET_SCROLL_POSITION`;
|
||||
|
||||
const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS';
|
||||
const CHECK_LATEST = fetchListType(`${name}/CHECK_LATEST`);
|
||||
const UPDATE_LATEST_REQUEST_TIME = 'filters/UPDATE_LATEST_REQUEST_TIME'
|
||||
|
||||
function chartWrapper(chart = []) {
|
||||
return chart.map((point) => ({ ...point, count: Math.max(point.count, 0) }));
|
||||
}
|
||||
// function chartWrapper(chart = []) {
|
||||
// return chart.map((point) => ({ ...point, count: Math.max(point.count, 0) }));
|
||||
// }
|
||||
|
||||
const savedSearchIdKey = 'searchId';
|
||||
const updateItemInList = createListUpdater(savedSearchIdKey);
|
||||
const updateInstance = (state, instance) =>
|
||||
state.getIn(['savedSearch', savedSearchIdKey]) === instance[savedSearchIdKey] ? state.mergeIn(['savedSearch'], SavedFilter(instance)) : state;
|
||||
// const savedSearchIdKey = 'searchId';
|
||||
// const updateItemInList = createListUpdater(savedSearchIdKey);
|
||||
// const updateInstance = (state, instance) =>
|
||||
// state.getIn(['savedSearch', savedSearchIdKey]) === instance[savedSearchIdKey] ? state.mergeIn(['savedSearch'], SavedFilter(instance)) : state;
|
||||
|
||||
const initialState = Map({
|
||||
filterList: generateFilterOptions(filtersMap),
|
||||
filterListLive: generateFilterOptions(liveFiltersMap),
|
||||
list: List(),
|
||||
latestRequestTime: null,
|
||||
latestList: List(),
|
||||
alertMetricId: null,
|
||||
instance: new Filter({ filters: [] }),
|
||||
savedSearch: new SavedFilter({}),
|
||||
filterSearchList: {},
|
||||
currentPage: 1,
|
||||
pageSize: PER_PAGE,
|
||||
activeTab: { name: 'All', type: 'all' },
|
||||
scrollY: 0,
|
||||
});
|
||||
|
|
@ -73,6 +80,10 @@ function reducer(state = initialState, action = {}) {
|
|||
'list',
|
||||
List(data.map(SavedFilter)).sortBy((i) => i.searchId)
|
||||
);
|
||||
case UPDATE_LATEST_REQUEST_TIME:
|
||||
return state.set('latestRequestTime', Date.now()).set('latestList', [])
|
||||
case success(CHECK_LATEST):
|
||||
return state.set('latestList', action.data)
|
||||
case success(FETCH_FILTER_SEARCH):
|
||||
const groupedList = action.data.reduce((acc, item) => {
|
||||
const { projectId, type, value } = item;
|
||||
|
|
@ -131,52 +142,69 @@ export const filterMap = ({ category, value, key, operator, sourceOperator, sour
|
|||
filters: filters ? filters.map(filterMap) : [],
|
||||
});
|
||||
|
||||
|
||||
const getFilters = (state) => {
|
||||
const filter = state.getIn(['search', 'instance']).toData();
|
||||
const activeTab = state.getIn(['search', 'activeTab']);
|
||||
if (activeTab.type !== 'all' && activeTab.type !== 'bookmark' && activeTab.type !== 'vault') {
|
||||
const tmpFilter = filtersMap[FilterKey.ISSUE];
|
||||
tmpFilter.value = [activeTab.type];
|
||||
filter.filters = filter.filters.concat(tmpFilter);
|
||||
}
|
||||
|
||||
if (activeTab.type === 'bookmark' || activeTab.type === 'vault') {
|
||||
filter.bookmarked = true;
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.map(filterMap);
|
||||
|
||||
// duration filter from local storage
|
||||
if (!filter.filters.find((f) => f.type === FilterKey.DURATION)) {
|
||||
const durationFilter = JSON.parse(localStorage.getItem(DURATION_FILTER) || '{"count": 0}');
|
||||
let durationValue = parseInt(durationFilter.count);
|
||||
if (durationValue > 0) {
|
||||
const value = [0];
|
||||
durationValue = durationFilter.countType === 'min' ? durationValue * 60 * 1000 : durationValue * 1000;
|
||||
if (durationFilter.operator === '<') {
|
||||
value[0] = durationValue;
|
||||
} else if (durationFilter.operator === '>') {
|
||||
value[1] = durationValue;
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.concat({
|
||||
type: FilterKey.DURATION,
|
||||
operator: 'is',
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
export const reduceThenFetchResource =
|
||||
(actionCreator) =>
|
||||
(...args) =>
|
||||
(dispatch, getState) => {
|
||||
dispatch(actionCreator(...args));
|
||||
const filter = getState().getIn(['search', 'instance']).toData();
|
||||
|
||||
const activeTab = getState().getIn(['search', 'activeTab']);
|
||||
|
||||
if (activeTab.type === 'notes') return;
|
||||
if (activeTab.type !== 'all' && activeTab.type !== 'bookmark' && activeTab.type !== 'vault') {
|
||||
const tmpFilter = filtersMap[FilterKey.ISSUE];
|
||||
tmpFilter.value = [activeTab.type];
|
||||
filter.filters = filter.filters.concat(tmpFilter);
|
||||
}
|
||||
|
||||
if (activeTab.type === 'bookmark' || activeTab.type === 'vault') {
|
||||
filter.bookmarked = true;
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.map(filterMap);
|
||||
filter.limit = 10;
|
||||
|
||||
const filter = getFilters(getState());
|
||||
filter.limit = PER_PAGE;
|
||||
filter.page = getState().getIn(['search', 'currentPage']);
|
||||
|
||||
const forceFetch = filter.filters.length === 0 || args[1] === true;
|
||||
|
||||
// duration filter from local storage
|
||||
if (!filter.filters.find((f) => f.type === FilterKey.DURATION)) {
|
||||
const durationFilter = JSON.parse(localStorage.getItem(DURATION_FILTER) || '{"count": 0}');
|
||||
let durationValue = parseInt(durationFilter.count);
|
||||
if (durationValue > 0) {
|
||||
const value = [0];
|
||||
durationValue = durationFilter.countType === 'min' ? durationValue * 60 * 1000 : durationValue * 1000;
|
||||
if (durationFilter.operator === '<') {
|
||||
value[0] = durationValue;
|
||||
} else if (durationFilter.operator === '>') {
|
||||
value[1] = durationValue;
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.concat({
|
||||
type: FilterKey.DURATION,
|
||||
operator: 'is',
|
||||
value,
|
||||
});
|
||||
}
|
||||
// reset the timestamps to latest
|
||||
if (filter.rangeValue !== CUSTOM_RANGE) {
|
||||
const period = new Period({ rangeName: filter.rangeValue })
|
||||
const newTimestamps = period.toJSON();
|
||||
filter.startDate = newTimestamps.startDate
|
||||
filter.endDate = newTimestamps.endDate
|
||||
}
|
||||
|
||||
dispatch(updateLatestRequestTime())
|
||||
return isRoute(ERRORS_ROUTE, window.location.pathname) ? dispatch(fetchErrorsList(filter)) : dispatch(fetchSessionList(filter, forceFetch));
|
||||
};
|
||||
|
||||
|
|
@ -353,3 +381,33 @@ export const setScrollPosition = (scrollPosition) => {
|
|||
scrollPosition,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateLatestRequestTime = () => {
|
||||
return {
|
||||
type: UPDATE_LATEST_REQUEST_TIME
|
||||
}
|
||||
}
|
||||
|
||||
export const checkForLatestSessions = () => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const filter = getFilters(state);
|
||||
const latestRequestTime = state.getIn(['search', 'latestRequestTime'])
|
||||
if (!!latestRequestTime) {
|
||||
const period = new Period({ rangeName: CUSTOM_RANGE, start: latestRequestTime, end: Date.now() })
|
||||
const newTimestamps = period.toJSON();
|
||||
filter.startDate = newTimestamps.startDate
|
||||
filter.endDate = newTimestamps.endDate
|
||||
}
|
||||
|
||||
return dispatch({
|
||||
types: array(CHECK_LATEST),
|
||||
call: (client) => client.post(`/sessions/search/ids`, filter),
|
||||
});
|
||||
}
|
||||
|
||||
export const fetchAutoplaySessions = (page) => (dispatch, getState) => {
|
||||
const filter = getFilters(getState());
|
||||
filter.page = page;
|
||||
filter.limit = PER_PAGE;
|
||||
return dispatch(fetchAutoplayList(filter));
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import { getDateRangeFromValue } from 'App/dateRange';
|
|||
const name = 'sessions';
|
||||
const INIT = 'sessions/INIT';
|
||||
const FETCH_LIST = new RequestTypes('sessions/FETCH_LIST');
|
||||
const FETCH_AUTOPLAY_LIST = new RequestTypes('sessions/FETCH_AUTOPLAY_LIST');
|
||||
const FETCH = new RequestTypes('sessions/FETCH');
|
||||
const FETCH_FAVORITE_LIST = new RequestTypes('sessions/FETCH_FAVORITE_LIST');
|
||||
const FETCH_LIVE_LIST = new RequestTypes('sessions/FETCH_LIVE_LIST');
|
||||
|
|
@ -96,6 +97,10 @@ const reducer = (state = initialState, action = {}) => {
|
|||
list.filter(({ favorite }) => favorite)
|
||||
)
|
||||
.set('total', total);
|
||||
case FETCH_AUTOPLAY_LIST.SUCCESS:
|
||||
let sessionIds = state.get('sessionIds');
|
||||
sessionIds = sessionIds.concat(action.data.map(i => i.sessionId + ''))
|
||||
return state.set('sessionIds', sessionIds.filter((i, index) => sessionIds.indexOf(i) === index ))
|
||||
case SET_AUTOPLAY_VALUES: {
|
||||
const sessionIds = state.get('sessionIds');
|
||||
const currentSessionId = state.get('current').sessionId;
|
||||
|
|
@ -257,7 +262,7 @@ function init(session) {
|
|||
|
||||
export const fetchList =
|
||||
(params = {}, force = false) =>
|
||||
(dispatch, getState) => {
|
||||
(dispatch) => {
|
||||
if (!force) { // compare with the last fetched filter
|
||||
const oldFilters = getSessionFilter();
|
||||
if (compareJsonObjects(oldFilters, cleanSessionFilters(params))) {
|
||||
|
|
@ -273,6 +278,19 @@ export const fetchList =
|
|||
});
|
||||
};
|
||||
|
||||
export const fetchAutoplayList =
|
||||
(params = {}) =>
|
||||
(dispatch) => {
|
||||
setSessionFilter(cleanSessionFilters(params));
|
||||
return dispatch({
|
||||
types: FETCH_AUTOPLAY_LIST.toArray(),
|
||||
call: (client) => client.post('/sessions/search/ids', params),
|
||||
params: cleanParams(params),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
export function fetchErrorStackList(sessionId, errorId) {
|
||||
return {
|
||||
types: FETCH_ERROR_STACK.toArray(),
|
||||
|
|
@ -436,4 +454,4 @@ export function updateLastPlayedSession(sessionId) {
|
|||
type: LAST_PLAYED_SESSION_ID,
|
||||
sessionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue