@@ -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: './js/src/index.js',
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]`,
},
// module: {
// loaders: [
// { test: /\.json$/, loader: 'json-loader' },
// ],
// },
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(),
],
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: './js/src/index.js',
entry: './_old/js/src/index.js',

output: {
path : resolve(__dirname, 'js', 'dist'),