| @@ -0,0 +1,212 @@ | ||
| import Immutable from 'seamless-immutable'; | ||
| import * as actionTypes from '../../constants/live/live'; | ||
| // import * as socketActionTypes from '../../constants/common/socket'; | ||
|
|
||
|
|
||
| const initialState = Immutable( | ||
| { | ||
| data: { | ||
| sportsDataById: {}, | ||
| categoriesDataById: {}, | ||
| tournamentsDataById: {}, | ||
| events: [], | ||
| headMarketsDataById: {}, | ||
| outcomesDataById: {}, | ||
| oneEvent: {}, | ||
| }, | ||
| viewState: { | ||
| isFetching: { | ||
| 'left-menu': false, | ||
| 'one-event': false, | ||
| }, | ||
| activeEventId: 0, | ||
| favoriteEvents: [], | ||
| selectedSports: [], | ||
| filters: { | ||
| region: false, | ||
| video: false, | ||
| }, | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
|
|
||
| export default function liveReducer(state = initialState, action = {}) { | ||
| switch (action.type) { | ||
|
|
||
| case actionTypes.LIVE_FETCHING: { | ||
| const { section, status } = action.payload; | ||
| return state.merge({ | ||
| viewState: { | ||
| isFetching: { | ||
| [section]: status, | ||
| }, | ||
| }, | ||
| }, { deep: true }); | ||
| } | ||
|
|
||
| case actionTypes.RECEIVE_LIVE_MENU: { | ||
| const chain = _(action.payload.markets); | ||
| const sortedEventsCollection = getSortedEventsCollection(chain); | ||
|
|
||
| const chainEvents = _(sortedEventsCollection); | ||
| const eventsWithoutHeadMarkets = getEventsWitoutHeadMarkets(chainEvents); | ||
| const headMarketsCollection = getHeadMarketsCollection(chainEvents); | ||
|
|
||
| const chainHeadMarkets = _(headMarketsCollection); | ||
| const normalizedHeadMarkets = getNormalizedHeadMarkets(chainHeadMarkets); | ||
| const normalizedOutcomes = getNormalizedOutcomes(chainHeadMarkets); | ||
|
|
||
| const sportsDataById = getSportsDataById(chainEvents); | ||
| const categoriesDataById = getCategoriesDataById(chainEvents); | ||
| const tournamentsDataById = getTournamentsDataById(chainEvents); | ||
|
|
||
| return state.merge({ | ||
| data: { | ||
| sportsDataById, | ||
| categoriesDataById, | ||
| tournamentsDataById, | ||
| events: eventsWithoutHeadMarkets, | ||
| headMarketsDataById: normalizedHeadMarkets, | ||
| outcomesDataById: normalizedOutcomes, | ||
| }, | ||
| }, { deep: true }); | ||
| } | ||
|
|
||
| case actionTypes.RECEIVE_LIVE_EVENT: { | ||
| const oneEvent = action.payload; | ||
|
|
||
| return state.merge({ | ||
| data: { | ||
| oneEvent, | ||
| }, | ||
| }, { deep: true }); | ||
| } | ||
|
|
||
| case actionTypes.SELECT_LIVE_EVENT: { | ||
| const activeEventId = action.enable ? action.eventId : 0; | ||
|
|
||
| return state.merge({ | ||
| viewState: { | ||
| activeEventId, | ||
| }, | ||
| }, { deep: true }); | ||
| } | ||
|
|
||
| case actionTypes.SELECT_LIVE_SPORT: { | ||
| const selectedSports = state.viewState.selectedSports.asMutable({ deep: true }); | ||
| const enable = _.includes(selectedSports, action.sportId); | ||
| const newSelectedSports = enable | ||
| ? _.without(selectedSports, action.sportId) | ||
| : [...selectedSports, action.sportId]; | ||
|
|
||
| return state.merge({ | ||
| viewState: { | ||
| selectedSports: newSelectedSports, | ||
| }, | ||
| }, { deep: true }); | ||
| } | ||
|
|
||
| case actionTypes.LIVE_TOGGLE_FAVORITE_EVENT: { | ||
| const favoriteEvents = state.viewState.favoriteEvents.asMutable({ deep: true }); | ||
| const enable = _.includes(favoriteEvents, action.eventId); | ||
| const newFavoriteEvents = enable | ||
| ? _.without(favoriteEvents, action.eventId) | ||
| : [...favoriteEvents, action.eventId]; | ||
|
|
||
| return state.merge({ | ||
| viewState: { | ||
| favoriteEvents: newFavoriteEvents, | ||
| }, | ||
| }, { deep: true }); | ||
| } | ||
|
|
||
| case actionTypes.LIVE_TOGGLE_REGION_FILTER: { | ||
| const regionFilter = state.viewState.filters.region; | ||
| return state.setIn(['viewState', 'filters', 'region'], !regionFilter); | ||
| } | ||
|
|
||
| case actionTypes.LIVE_TOGGLE_VIDEO_FILTER: { | ||
| const videoFilter = state.viewState.filters.video; | ||
| return state.setIn(['viewState', 'filters', 'video'], !videoFilter); | ||
| } | ||
|
|
||
| default: | ||
| return state; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /* Helpers */ | ||
|
|
||
| function getSortedEventsCollection(chain) { | ||
| return chain | ||
| .uniqBy('sportId') | ||
| .map(sport => _.map(sport.tournaments, (tournament) => ({...tournament, ...sport}))) | ||
| .flatMap() | ||
| .map(tournament => _.omit(tournament, 'tournaments')) | ||
| .map(tournament => _.map(tournament.events, (event) => ({...event, ...tournament}))) | ||
| .flatMap() | ||
| .map(event => _.omit(event, 'events')) | ||
| .sortBy('sportWeigh', 'categoryWeigh', 'tournamentWeigh', 'eventWeigh', 'eventId') | ||
| .value(); | ||
| } | ||
|
|
||
| function getEventsWitoutHeadMarkets(chainEvents) { | ||
| return chainEvents | ||
| .map(event => ({ ...event, 'headMarketId': event.headMarket.marketId })) | ||
| .map(event => _.omit(event, 'headMarket')) | ||
| .value(); | ||
| } | ||
|
|
||
| function getHeadMarketsCollection(chainEvents) { | ||
| return chainEvents | ||
| .filter(event => !_.isEmpty(event.headMarket)) | ||
| .flatMap(event => event.headMarket) | ||
| .value(); | ||
| } | ||
|
|
||
| function getNormalizedHeadMarkets(chainHeadMarkets) { | ||
| return chainHeadMarkets | ||
| .map(headMarket => ({ ...headMarket, 'outcomeIds': _.map(headMarket.outcomes, outcome => outcome.outcomeId)})) | ||
| .map(headMarket => _.omit(headMarket, 'outcomes')) | ||
| .reduce((acum, headMarket) => { | ||
| return { ...acum, [headMarket.marketId]: headMarket }; // eslint-disable-line no-param-reassign | ||
| }, {}); | ||
| } | ||
|
|
||
| function getNormalizedOutcomes(chainHeadMarkets) { | ||
| return chainHeadMarkets | ||
| .flatMap(headMarket => headMarket.outcomes) | ||
| .compact() | ||
| .reduce((acum, outcome) => { | ||
| return { ...acum, [outcome.outcomeId]: outcome }; // eslint-disable-line no-param-reassign | ||
| }, {}); | ||
| } | ||
|
|
||
| function getSportsDataById(chainEvents) { | ||
| return chainEvents | ||
| .uniqBy('sportId') | ||
| .map(val => _.pick(val, ['sportId', 'sportName', 'sportformId', 'countryId'])) | ||
| .reduce((acum, val) => { | ||
| return { ...acum, [val.sportId]: val }; // eslint-disable-line no-param-reassign | ||
| }, {}); | ||
| } | ||
|
|
||
| function getCategoriesDataById(chainEvents) { | ||
| return chainEvents | ||
| .uniqBy('categoryId') | ||
| .map(val => _.pick(val, ['sportId', 'categoryId', 'categoryName', 'countryId'])) | ||
| .reduce((acum, val) => { | ||
| return { ...acum, [val.categoryId]: val }; | ||
| }, {}); | ||
| } | ||
|
|
||
| function getTournamentsDataById(chainEvents) { | ||
| return chainEvents | ||
| .uniqBy('tournamentId') | ||
| .map(val => _.pick(val, ['sportId', 'tournamentId', 'tournamentName', 'categoryName', 'sportName'])) | ||
| .reduce((acum, val) => { | ||
| return { ...acum, [val.tournamentId]: val }; // eslint-disable-line no-param-reassign | ||
| }, {}); | ||
| } |
| @@ -0,0 +1,91 @@ | ||
| import { takeEvery } from 'redux-saga'; | ||
| import { put, call, fork } from 'redux-saga/effects'; | ||
|
|
||
| import { | ||
| fetchLiveMenu, | ||
| fetchLiveEvent, | ||
| } from '../../api/live/live.js'; | ||
|
|
||
| import { | ||
| FETCH_LIVE_MENU, | ||
| FETCH_LIVE_EVENT, | ||
| RECEIVE_LIVE_MENU, | ||
| RECEIVE_LIVE_EVENT, | ||
| SELECT_LIVE_EVENT, | ||
| LIVE_FETCHING, | ||
| } from '../../constants/live/live.js'; | ||
|
|
||
| import { | ||
| SET_LIVE_SUBSCRIPTION, | ||
| CHANGE_SERVICES_SUBSCRIPTION, | ||
| } from '../../constants/common/socket.js'; | ||
|
|
||
|
|
||
| function getLiveMenu() { | ||
| return fetchLiveMenu() | ||
| .then(response => ({ response })) | ||
| .catch(error => ({ error })); | ||
| } | ||
|
|
||
| function getLiveEvent(eventId) { | ||
| return fetchLiveEvent(eventId) | ||
| .then(response => ({ response })) | ||
| .catch(error => ({ error })); | ||
| } | ||
|
|
||
| function* setLiveMenu(response, error) { | ||
| if (response) { | ||
| yield put({ type: RECEIVE_LIVE_MENU, payload: response }); | ||
| yield put({ type: LIVE_FETCHING, payload: { section: 'left-menu', status: false } }); | ||
| yield put({ type: SET_LIVE_SUBSCRIPTION, eventId: null }); // set/change store subscription | ||
| yield put({ type: CHANGE_SERVICES_SUBSCRIPTION }); // make socket saga reinit subscription | ||
| } | ||
| // Пока что не обрабатываем ошибки получения | ||
| else yield put({ type: 'ERROR_LIVE_MENU', error }); | ||
| } | ||
|
|
||
| function* setLiveEvent(response, error, eventId) { | ||
| if (response) { | ||
| yield put({ type: RECEIVE_LIVE_EVENT, payload: response }); | ||
| yield put({ type: LIVE_FETCHING, payload: { section: 'one-event', status: false } }); | ||
| yield put({ type: SELECT_LIVE_EVENT, eventId }); // set in left menu after receiving | ||
| yield put({ type: SET_LIVE_SUBSCRIPTION, eventId }); // set/change store subscription | ||
| yield put({ type: CHANGE_SERVICES_SUBSCRIPTION }); // make socket saga reinit subscription | ||
| } | ||
| // Пока что не обрабатываем ошибки получения | ||
| else yield put({ type: 'ERROR_LIVE_EVENT', error }); | ||
| } | ||
|
|
||
|
|
||
| /* === WORKERS === */ | ||
| export function* getLiveMenuWorker() { | ||
| yield put({ type: LIVE_FETCHING, payload: { section: 'left-menu', status: true } }); | ||
| const { response, error } = yield call(getLiveMenu); | ||
| yield call(setLiveMenu, response, error); | ||
| } | ||
|
|
||
| export function* getLiveEventWorker(action) { | ||
| const { eventId } = action; | ||
| yield put({ type: LIVE_FETCHING, payload: { section: 'one-event', status: true } }); | ||
| const { response, error } = yield call(getLiveEvent, eventId); | ||
| yield call(setLiveEvent, response, error, eventId); | ||
| } | ||
|
|
||
|
|
||
| /* WATHERS */ | ||
| function* liveMenuWatcher() { | ||
| yield takeEvery(FETCH_LIVE_MENU, getLiveMenuWorker); | ||
| } | ||
|
|
||
| function* liveEventWatcher() { | ||
| yield takeEvery(FETCH_LIVE_EVENT, getLiveEventWorker); | ||
| } | ||
|
|
||
|
|
||
| /* === ROOT === */ | ||
| export default function* liveSaga() { | ||
| yield [ | ||
| fork(liveMenuWatcher), | ||
| fork(liveEventWatcher), | ||
| ]; | ||
| } |
| @@ -0,0 +1,66 @@ | ||
| import { createStore, applyMiddleware, compose } from 'redux'; | ||
| // import { syncHistory } from 'react-router-redux'; | ||
| // import { syncHistory } from 'redux-router-immutable'; | ||
| import createSagaMiddleware from 'redux-saga'; | ||
| import thunk from 'redux-thunk'; | ||
| import promiseMiddleware from '../../middleware/promiseMiddleware'; | ||
| import logger from '../logger'; | ||
| import DevTools from '../../containers/common/DevTools'; | ||
| // import history from './history'; | ||
| import rootReducer from '../../reducers/live'; | ||
| import basketSaga from '../../sagas/common/basketSaga'; | ||
| import startingServiceMenu from '../../sagas/common/service-menu'; | ||
| import liveSaga from '../../sagas/live/live'; | ||
| import hashSaga from '../../sagas/common/hashSaga'; | ||
| // import socketSaga from '../../sagas/common/sockets'; | ||
| import jackpotWinnerSaga from '../../sagas/common/jackpotWinner'; | ||
|
|
||
| // const reduxRouter = syncHistory(history); | ||
| const sagaMiddleware = createSagaMiddleware(); | ||
| export default function configureStore(initialState) { | ||
| let createStoreWithMiddleware; | ||
|
|
||
| if (__DEV__) { | ||
| createStoreWithMiddleware = compose( | ||
| applyMiddleware( | ||
| // reduxRouter, | ||
| // sagaMiddleware(basketSaga), | ||
| sagaMiddleware, | ||
| promiseMiddleware, | ||
| thunk, | ||
| logger, | ||
| ), | ||
| DevTools.instrument(), | ||
| )(createStore); | ||
| } else { | ||
| createStoreWithMiddleware = compose( | ||
| applyMiddleware( | ||
| // reduxRouter, | ||
| sagaMiddleware, | ||
| // sagaMiddleware(basketSaga), | ||
| promiseMiddleware, | ||
| thunk, | ||
| ), | ||
| )(createStore); | ||
| } | ||
|
|
||
| const store = createStoreWithMiddleware(rootReducer, initialState); | ||
| sagaMiddleware.run(basketSaga); | ||
| sagaMiddleware.run(startingServiceMenu); | ||
| sagaMiddleware.run(liveSaga); | ||
| sagaMiddleware.run(hashSaga); | ||
| // sagaMiddleware.run(socketSaga); | ||
| sagaMiddleware.run(jackpotWinnerSaga); | ||
| if (module.hot) { | ||
| // Enable Webpack hot module replacement for reducers | ||
| module.hot.accept('../../reducers/live', () => { | ||
| const nextRootReducer = require('../../reducers/live'); | ||
| store.replaceReducer(nextRootReducer); | ||
| }); | ||
| } | ||
|
|
||
| // Required for replaying actions from devtools to work | ||
| // reduxRouter.listenForReplays(store); | ||
|
|
||
| return store; | ||
| } |
| @@ -9,7 +9,7 @@ const config = { | ||
| watch : true, | ||
| devtool: 'source-map', | ||
|
|
||
| entry: './_old/js/src/index.js', | ||
|
|
||
| output: { | ||
| path : resolve(__dirname, 'js', 'dist'), | ||
| @@ -1,36 +1,33 @@ | ||
| const path = require('path'); | ||
| const webpack = require('webpack'); | ||
|
|
||
|
|
||
| const config = { | ||
| cache: true, | ||
| entry: { | ||
| vendor: [ | ||
| 'react', | ||
| 'redux', | ||
| 'react-redux', | ||
| 'jquery', | ||
| 'lodash', | ||
| 'moment', | ||
| 'classnames', | ||
| ], | ||
| }, | ||
| output: { | ||
| path : path.join(__dirname, 'dist', 'dll'), | ||
| filename: 'dll.[name].js', | ||
| library : '[name]', | ||
| }, | ||
|
|
||
| plugins: [ | ||
| new webpack.DllPlugin({ | ||
| path: path.join(__dirname, 'dist', 'dll', '[name]-manifest.json'), | ||
| name: '[name]', | ||
| }), | ||
| new webpack.optimize.OccurrenceOrderPlugin(), | ||
| ], | ||
| }; | ||
|
|
||
| module.exports = config; |
| @@ -7,7 +7,7 @@ const config = { | ||
| context: __dirname, | ||
| devtool: 'source-map', | ||
|
|
||
| entry: './_old/js/src/index.js', | ||
|
|
||
| output: { | ||
| path : resolve(__dirname, 'js', 'dist'), | ||