diff --git a/package-lock.json b/package-lock.json index 28258b9..1523df5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -765,6 +765,11 @@ "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.7.tgz", "integrity": "sha512-rzOhiQ55WzAiFgXRtitP/ZUT8iVNyllEpylJ5zHzR4vArUvMB39GTk+Zon/uAM0JxEFAWnwsxC2gH8s+tZ3Myg==" }, + "@types/node": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.0.tgz", + "integrity": "sha512-ry4DOrC+xenhQbzk1iIPzCZGhhPGEFv7ia7Iu6XXSLVluiJIe9FfG7Iu3mObH9mpxEXCWLCMU4JWbCCR9Oy1Zg==" + }, "@types/prop-types": { "version": "15.5.8", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz", diff --git a/package.json b/package.json index 42a0652..080f0b1 100644 --- a/package.json +++ b/package.json @@ -52,9 +52,9 @@ }, "dependencies": { "@types/classnames": "^2.2.7", + "@types/node": "^11.9.0", "@types/react": "^16.7.20", "@types/react-dom": "^16.0.11", - "typesafe-actions": "^3.0.0", "axios": "^0.18.0", "babel-runtime": "^6.26.0", "bluebird": "^3.5.3", @@ -102,6 +102,7 @@ "styled-theming": "^2.2.0", "tt-react-custom-scrollbars": "^4.2.1-tt2", "typeface-pt-sans": "0.0.54", + "typesafe-actions": "^3.0.0", "webpack-git-hash": "^1.0.2" }, "flow-coverage-report": { diff --git a/src/constants/auth.js b/src/constants/auth.js deleted file mode 100644 index b56a6f7..0000000 --- a/src/constants/auth.js +++ /dev/null @@ -1,21 +0,0 @@ -export const ROLES = { - guest: 'guest', - vk: 'vk', -}; - -export const DEFAULT_USER = { - new_messages: 0, - place_types: {}, - random_url: '', - role: ROLES.guest, - routes: {}, - success: false, - id: null, - token: null, - userdata: { - name: '', - agent: '', - ip: '', - photo: '', - } -}; diff --git a/src/constants/auth.ts b/src/constants/auth.ts new file mode 100644 index 0000000..c4ed2dc --- /dev/null +++ b/src/constants/auth.ts @@ -0,0 +1,45 @@ +type valueof = T[keyof T] + +export interface IRoles { + guest: string, + vk: string, +} + +export interface IUser { + new_messages: number, + place_types: {}, + random_url: string, + role: IRoles[keyof IRoles], + routes: {}, + success: boolean, + id?: string, + token?: string, + userdata: { + name: string, + agent: string, + ip: string, + photo: string, + } +} + +export const ROLES: IRoles = { + guest: 'guest', + vk: 'vk', +}; + +export const DEFAULT_USER: IUser = { + new_messages: 0, + place_types: {}, + random_url: '', + role: ROLES.guest, + routes: {}, + success: false, + id: null, + token: null, + userdata: { + name: '', + agent: '', + ip: '', + photo: '', + } +}; diff --git a/src/constants/dialogs.js b/src/constants/dialogs.js deleted file mode 100644 index cd12143..0000000 --- a/src/constants/dialogs.js +++ /dev/null @@ -1,12 +0,0 @@ -// @flow -export const DIALOGS = ({ - NONE: 'NONE', - MAP_LIST: 'MAP_LIST', - APP_INFO: 'APP_INFO', -}: { [key: String]: String }); - -export const TABS = ({ - mine: 'Мои', - all: 'Общие', - // starred: 'Выбранные', -}: { [key: String]: String }); diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts new file mode 100644 index 0000000..5c8fc6b --- /dev/null +++ b/src/constants/dialogs.ts @@ -0,0 +1,21 @@ +export interface IDialogs { + NONE: string, + MAP_LIST: string, + APP_INFO: string, +} + +export interface IMapTabs { + mine: string, + all: string, +} + +export const DIALOGS: IDialogs = ({ + NONE: 'NONE', + MAP_LIST: 'MAP_LIST', + APP_INFO: 'APP_INFO', +}); + +export const TABS: IMapTabs = ({ + mine: 'Мои', + all: 'Общие', +}); diff --git a/src/constants/logos.js b/src/constants/logos.ts similarity index 90% rename from src/constants/logos.js rename to src/constants/logos.ts index 5e4da7b..4b00bbf 100644 --- a/src/constants/logos.js +++ b/src/constants/logos.ts @@ -1,3 +1,7 @@ +export interface ILogos { + [x: string]: [string, string, string], +} + export const LOGOS = { default: ['Без лого', null, 'bottom-right'], nvs: ['НВС', require('../sprites/logos/lgo.png'), 'bottom-right'], diff --git a/src/constants/modes.js b/src/constants/modes.ts similarity index 76% rename from src/constants/modes.js rename to src/constants/modes.ts index d70bb42..70f5588 100644 --- a/src/constants/modes.js +++ b/src/constants/modes.ts @@ -1,6 +1,8 @@ -// @flow +export interface IModes { + [x: string]: string, +} -export const MODES = ({ +export const MODES: IModes = { POLY: 'POLY', STICKERS: 'STICKERS', STICKERS_SELECT: 'STICKERS_SELECT', @@ -12,6 +14,5 @@ export const MODES = ({ SAVE: 'SAVE', CONFIRM_CANCEL: 'CONFIRM_CANCEL', PROVIDER: 'PROVIDER', - SHOT_PREFETCH: 'SHOT_PREFETCH', -}: { [key: String]: String }); +}; diff --git a/src/constants/providers.js b/src/constants/providers.ts similarity index 67% rename from src/constants/providers.js rename to src/constants/providers.ts index 4ff9790..b365460 100644 --- a/src/constants/providers.js +++ b/src/constants/providers.ts @@ -1,5 +1,24 @@ +export interface IPRovider { + name: string, + url: string, + range: Array, +} + +export interface ITileMaps { + WATERCOLOR: IPRovider, + DGIS: IPRovider, + DEFAULT: IPRovider, + DARQ: IPRovider, + BLANK: IPRovider, + HOT: IPRovider, + YSAT: IPRovider, + YMAP: IPRovider, + SAT: IPRovider, +} + + // Стили карт -const TILEMAPS = { +const TILEMAPS: ITileMaps = { WATERCOLOR: { name: 'Watercolor', url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg', @@ -47,17 +66,19 @@ const TILEMAPS = { }, }; -const ENABLED = ['BLANK', 'DEFAULT', 'DGIS', 'HOT']; +const ENABLED: Array = ['BLANK', 'DEFAULT', 'DGIS', 'HOT']; -export const DEFAULT_PROVIDER = ENABLED[0]; -export const PROVIDERS = ENABLED.reduce((obj, provider) => ({ +export const DEFAULT_PROVIDER: keyof ITileMaps = ENABLED[0]; +export const PROVIDERS: Partial = ENABLED.reduce((obj, provider) => ({ ...obj, [provider]: TILEMAPS[provider], }), {}); export const replaceProviderUrl = (provider, { x, y, zoom }) => { const { url, range } = (PROVIDERS[provider] || PROVIDERS[DEFAULT_PROVIDER]); - const random = (range && range.length >= 2) ? range[Math.round((Math.random() * (range.length - 1)))] : 1; + const random: (number | string) = (range && range.length >= 2) + ? range[Math.round((Math.random() * (range.length - 1)))] + : 1; - return url.replace('{x}', x).replace('{y}', y).replace('{z}', zoom).replace('{s}', random); + return url.replace('{x}', x).replace('{y}', y).replace('{z}', zoom).replace('{s}', String(random)); }; diff --git a/src/constants/stickers.js b/src/constants/stickers.ts similarity index 91% rename from src/constants/stickers.js rename to src/constants/stickers.ts index 28dd5ae..502ee69 100644 --- a/src/constants/stickers.js +++ b/src/constants/stickers.ts @@ -1,8 +1,28 @@ // Стикеры // import L from "leaflet"; +export interface ISticker { + off: number, + title: string, + title_long: string, +} + +export interface IStickerPack { + title: string, + url: string, + size: number, + layers: { + [x: string]: ISticker, + } +} + +export interface IStickers { + base: IStickerPack, + real: IStickerPack, + pin: IStickerPack, +} // export const stickers = ['green', 'basic', 'green-small']; -export const STICKERS = { +export const STICKERS: IStickers = { base: { title: 'Простые', url: require('$sprites/stickers/stickers-base.svg'), diff --git a/src/redux/store.js b/src/redux/store.ts similarity index 75% rename from src/redux/store.js rename to src/redux/store.ts index b8b30b6..fbd8a55 100644 --- a/src/redux/store.js +++ b/src/redux/store.ts @@ -1,4 +1,4 @@ -import { createStore, applyMiddleware, combineReducers, compose } from 'redux'; +import { createStore, applyMiddleware, combineReducers, compose, Store } from 'redux'; import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; @@ -8,8 +8,9 @@ import { userReducer } from '$redux/user/reducer'; import { userSaga } from '$redux/user/sagas'; import { createBrowserHistory } from 'history'; import { locationChanged } from '$redux/user/actions'; +import { PersistConfig, Persistor } from "redux-persist/es/types"; -const userPersistConfig = { +const userPersistConfig: PersistConfig = { key: 'user', whitelist: ['user', 'logo', 'provider', 'speed'], storage, @@ -19,15 +20,11 @@ const userPersistConfig = { export const sagaMiddleware = createSagaMiddleware(); // redux extension composer -/* eslint-disable no-underscore-dangle */ const composeEnhancers = typeof window === 'object' && - window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) + (window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? (window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; -/* eslint-enable no-underscore-dangle */ - -// export const history = createHistory(); export const store = createStore( combineReducers({ @@ -37,7 +34,7 @@ export const store = createStore( composeEnhancers(applyMiddleware(/* routerMiddleware(history), */ sagaMiddleware)) ); -export function configureStore() { +export function configureStore(): { store: Store, persistor: Persistor } { sagaMiddleware.run(userSaga); const persistor = persistStore(store); diff --git a/src/redux/user/actions.js b/src/redux/user/actions.ts similarity index 96% rename from src/redux/user/actions.js rename to src/redux/user/actions.ts index acbcf6c..e820f27 100644 --- a/src/redux/user/actions.js +++ b/src/redux/user/actions.ts @@ -1,9 +1,9 @@ import { ACTIONS } from '$redux/user/constants'; +import { IUser } from "$constants/auth"; -export const setUser = user => ({ type: ACTIONS.SET_USER, user }); +export const setUser = (user: IUser) => ({ type: ACTIONS.SET_USER, user }); export const userLogout = () => ({ type: ACTIONS.USER_LOGOUT }); - export const setEditing = editing => ({ type: ACTIONS.SET_EDITING, editing }); export const setMode = mode => ({ type: ACTIONS.SET_MODE, mode }); export const setDistance = distance => ({ type: ACTIONS.SET_DISTANCE, distance }); @@ -61,4 +61,3 @@ export const searchSetLoading = loading => ({ type: ACTIONS.SEARCH_SET_LOADING, export const searchPutRoutes = payload => ({ type: ACTIONS.SEARCH_PUT_ROUTES, ...payload }); export const setMarkersShown = markers_shown => ({ type: ACTIONS.SET_MARKERS_SHOWN, markers_shown }); - diff --git a/src/redux/user/constants.js b/src/redux/user/constants.ts similarity index 94% rename from src/redux/user/constants.js rename to src/redux/user/constants.ts index 835d78e..7df35e5 100644 --- a/src/redux/user/constants.js +++ b/src/redux/user/constants.ts @@ -1,6 +1,8 @@ -// @flow +export interface IActions { + [x: string]: string, +} -export const ACTIONS = ({ +export const ACTIONS: IActions = { SET_USER: 'SET_USER', USER_LOGOUT: 'USER_LOGOUT', @@ -65,4 +67,4 @@ export const ACTIONS = ({ SET_SPEED: 'SET_SPEED', SET_MARKERS_SHOWN: 'SET_MARKERS_SHOWN', -}: { [key: String]: String }); +}; diff --git a/src/redux/user/reducer.js b/src/redux/user/reducer.js deleted file mode 100644 index abee429..0000000 --- a/src/redux/user/reducer.js +++ /dev/null @@ -1,256 +0,0 @@ -// @flow -import { createReducer } from 'reduxsauce'; -import { ACTIONS } from '$redux/user/constants'; -import { DEFAULT_USER } from '$constants/auth'; -import { MODES } from '$constants/modes'; -import { DEFAULT_LOGO } from '$constants/logos'; -import { TIPS } from '$constants/tips'; -import { DEFAULT_PROVIDER } from '$constants/providers'; -import { DIALOGS, TABS } from '$constants/dialogs'; - -const getEstimated = (distance, speed = 15) => { - const time = (distance && (distance / speed)) || 0; - return (time && parseFloat(time.toFixed(1))); -}; - -const setUser = (state, { user }) => ({ - ...state, - user: { - ...state.user, - ...user, - }, -}); - -const setEditing = (state, { editing }) => ({ ...state, editing }); -const setChanged = (state, { changed }) => ({ ...state, changed }); -const setMode = (state, { mode }) => ({ ...state, mode }); -const setDistance = (state, { distance }) => ({ - ...state, - distance, - estimated: getEstimated(distance, state.speed), -}); - -const setRouterPoints = (state, { routerPoints }) => ({ ...state, routerPoints }); - -const setActiveSticker = (state, { activeSticker }) => ({ - ...state, - activeSticker: activeSticker || { set: null, sticker: null } -}); -const setLogo = (state, { logo }) => ({ ...state, logo }); -const setTitle = (state, { title }) => ({ ...state, title }); -const setAddress = (state, { address }) => ({ ...state, address }); -const setAddressOrigin = (state, { address_origin }) => ({ ...state, address_origin }); - -const sendSaveRequest = state => ({ ...state, save_processing: true, }); -const setSaveError = (state, { save_error }) => ({ - ...state, save_error, save_finished: false, save_processing: false -}); - -const setSaveOverwrite = state => ({ - ...state, - save_overwriting: true, - save_finished: false, - save_processing: false, - save_error: TIPS.SAVE_OVERWRITE, -}); - -const setSaveSuccess = (state, { save_error }) => ({ - ...state, save_overwriting: false, save_finished: true, save_processing: false, save_error -}); - -const resetSaveDialog = state => ({ - ...state, save_overwriting: false, save_finished: false, save_processing: false, save_error: '', -}); - -const showRenderer = state => ({ - ...state, - renderer: { ...state.renderer, renderer_active: true } -}); - -const hideRenderer = state => ({ - ...state, - renderer: { ...state.renderer, renderer_active: false } -}); - -const setRenderer = (state, { payload }) => ({ - ...state, - renderer: { ...state.renderer, ...payload } -}); - -const setProvider = (state, { provider }) => ({ ...state, provider }); - -const setDialog = (state, { dialog }) => ({ - ...state, - dialog, -}); - -const setDialogActive = (state, { dialog_active }) => ({ - ...state, - dialog_active: dialog_active || !state.dialog_active, -}); - -const setReady = (state, { ready = true }) => ({ - ...state, - ready, -}); - -const searchSetTitle = (state, { title = '' }) => ({ - ...state, - routes: { - ...state.routes, - filter: { - ...state.routes.filter, - title, - distance: [0, 10000], - } - } -}); - -const searchSetDistance = (state, { distance = [0, 9999] }) => ({ - ...state, - routes: { - ...state.routes, - filter: { - ...state.routes.filter, - distance, - } - } -}); - -const searchSetTab = (state, { tab = TABS[Object.keys(TABS)[0]] }) => ({ - ...state, - routes: { - ...state.routes, - filter: { - ...state.routes.filter, - tab: Object.keys(TABS).indexOf(tab) >= 0 ? tab : TABS[Object.keys(TABS)[0]], - } - } -}); - -const searchPutRoutes = (state, { list = [], min, max }) => ({ - ...state, - routes: { - ...state.routes, - list, - filter: { - ...state.routes.filter, - distance: (state.routes.filter.min === state.routes.filter.max) - ? [min, max] - : state.routes.filter.distance, - min, - max, - } - } -}); - -const searchSetLoading = (state, { loading = false }) => ({ - ...state, - routes: { - ...state.routes, - loading, - } -}); - -const setPublic = (state, { is_public = false }) => ({ ...state, is_public }); -const setSpeed = (state, { speed = 15 }) => ({ - ...state, - speed, - estimated: getEstimated(state.distance, speed), -}); - -const setMarkersShown = (state, { markers_shown = true }) => ({ ...state, markers_shown }); - -const HANDLERS = ({ - [ACTIONS.SET_USER]: setUser, - [ACTIONS.SET_EDITING]: setEditing, - [ACTIONS.SET_CHANGED]: setChanged, - [ACTIONS.SET_MODE]: setMode, - [ACTIONS.SET_DISTANCE]: setDistance, - [ACTIONS.SET_ROUTER_POINTS]: setRouterPoints, - [ACTIONS.SET_ACTIVE_STICKER]: setActiveSticker, - [ACTIONS.SET_LOGO]: setLogo, - [ACTIONS.SET_TITLE]: setTitle, - [ACTIONS.SET_ADDRESS]: setAddress, - [ACTIONS.SET_ADDRESS_ORIGIN]: setAddressOrigin, - - [ACTIONS.SET_SAVE_ERROR]: setSaveError, - [ACTIONS.SET_SAVE_OVERWRITE]: setSaveOverwrite, - [ACTIONS.SET_SAVE_SUCCESS]: setSaveSuccess, - [ACTIONS.SEND_SAVE_REQUEST]: sendSaveRequest, - [ACTIONS.RESET_SAVE_DIALOG]: resetSaveDialog, - - [ACTIONS.SHOW_RENDERER]: showRenderer, - [ACTIONS.HIDE_RENDERER]: hideRenderer, - [ACTIONS.SET_RENDERER]: setRenderer, - - [ACTIONS.SET_PROVIDER]: setProvider, - - [ACTIONS.SET_DIALOG]: setDialog, - [ACTIONS.SET_DIALOG_ACTIVE]: setDialogActive, - [ACTIONS.SET_READY]: setReady, - - [ACTIONS.SEARCH_SET_TITLE]: searchSetTitle, - [ACTIONS.SEARCH_SET_DISTANCE]: searchSetDistance, - [ACTIONS.SEARCH_SET_TAB]: searchSetTab, - [ACTIONS.SEARCH_PUT_ROUTES]: searchPutRoutes, - [ACTIONS.SEARCH_SET_LOADING]: searchSetLoading, - [ACTIONS.SET_PUBLIC]: setPublic, - [ACTIONS.SET_SPEED]: setSpeed, - - [ACTIONS.SET_MARKERS_SHOWN]: setMarkersShown, -}: { [key: String]: Function }); - -export const INITIAL_STATE = { - ready: false, - user: { ...DEFAULT_USER }, - editing: false, - mode: MODES.NONE, - logo: DEFAULT_LOGO, - routerPoints: 0, - distance: 0, - estimated: 0, - speed: 15, - activeSticker: { set: null, sticker: null }, - title: '', - address: '', - address_origin: '', - changed: false, - provider: DEFAULT_PROVIDER, - is_public: false, - markers_shown: true, - - save_error: '', - save_finished: false, - save_overwriting: false, - save_processing: false, - - dialog: DIALOGS.NONE, - dialog_active: false, - - renderer: { - data: '', - width: 0, - height: 0, - renderer_active: false, - info: '', - progress: 0, - }, - - routes: { - limit: 0, - loading: false, // <-- maybe delete this - list: [], - filter: { - title: '', - starred: false, - distance: [0, 10000], - author: '', - tab: '', - min: 0, - max: 10000, - } - }, -}; - -export const userReducer = createReducer(INITIAL_STATE, HANDLERS); diff --git a/src/redux/user/reducer.ts b/src/redux/user/reducer.ts new file mode 100644 index 0000000..8a11cca --- /dev/null +++ b/src/redux/user/reducer.ts @@ -0,0 +1,353 @@ +// @flow +import { createReducer } from 'reduxsauce'; +import { ACTIONS } from '$redux/user/constants'; +import { DEFAULT_USER, IUser } from '$constants/auth'; +import { MODES } from '$constants/modes'; +import { DEFAULT_LOGO, LOGOS } from '$constants/logos'; +import { TIPS } from '$constants/tips'; +import { DEFAULT_PROVIDER, PROVIDERS } from '$constants/providers'; +import { DIALOGS, IDialogs, TABS } from '$constants/dialogs'; +import * as ActionCreators from '$redux/user/actions'; +import { IStickers } from "$constants/stickers"; + +interface IRootReducer { + ready: boolean, + user: IUser, + editing: boolean, + mode: keyof typeof MODES, + logo: keyof typeof LOGOS, + routerPoints: number, + distance: number, + estimated: number, + speed: number, + activeSticker: { set?: keyof IStickers, sticker?: string }, + title: string, + address: string, + address_origin: string, + changed: boolean, + provider: keyof typeof PROVIDERS, + is_public: boolean, + markers_shown: boolean, + + save_error: string, + save_finished: boolean, + save_overwriting: boolean, + save_processing: boolean, + + dialog: IDialogs[keyof IDialogs], + dialog_active: boolean, + + renderer: { + data: string, + width: number, + height: number + renderer_active: boolean, + info: string, + progress: number, + }, + + routes: { + limit: 0, + loading: boolean, + list: [], + filter: { + title: '', + starred: boolean, + distance: [number, number], + author: '', + tab: '', + min: number, + max: number, + } + }, +} + +export type IRootState = Readonly; +type UnsafeReturnType = T extends (...args: any[]) => infer R ? R : any; + +export interface ActionHandler { + (state: IRootState, payload: UnsafeReturnType): IRootState; +} + +const getEstimated = (distance: number, speed: number = 15): number => { + const time = (distance && (distance / speed)) || 0; + return (time && parseFloat(time.toFixed(1))); +}; + +const setUser: ActionHandler = (state, { user }) => ({ + ...state, + user: { + ...state.user, + ...user, + }, +}); + +const setEditing: ActionHandler = (state, { editing }) => ({ + ...state, editing +}); + +const setChanged: ActionHandler = (state, { changed }) => ({ + ...state, + changed +}); + +const setMode: ActionHandler = (state, { mode }) => ({ + ...state, + mode +}); + +const setDistance: ActionHandler = (state, { distance }) => ({ + ...state, + distance, + estimated: getEstimated(distance, state.speed), +}); + +const setRouterPoints: ActionHandler = (state, { routerPoints }) => ({ + ...state, + routerPoints, +}); + +const setActiveSticker: ActionHandler = (state, { activeSticker }) => ({ + ...state, + activeSticker: activeSticker || { set: null, sticker: null } +}); + +const setLogo: ActionHandler = (state, { logo }) => ({ + ...state, + logo +}); + +const setTitle: ActionHandler = (state, { title }) => ({ + ...state, + title +}); + +const setAddress: ActionHandler = (state, { address }) => ({ + ...state, + address +}); + +const setAddressOrigin: ActionHandler = (state, { address_origin }) => ({ + ...state, + address_origin +}); + +const sendSaveRequest: ActionHandler = (state) => ({ + ...state, + save_processing: true, +}); + +const setSaveError: ActionHandler = (state, { save_error }) => ({ + ...state, save_error, save_finished: false, save_processing: false +}); + +const setSaveOverwrite: ActionHandler = (state) => ({ + ...state, + save_overwriting: true, + save_finished: false, + save_processing: false, + save_error: TIPS.SAVE_OVERWRITE, +}); + +const setSaveSuccess: ActionHandler = (state, { save_error }) => ({ + ...state, save_overwriting: false, save_finished: true, save_processing: false, save_error +}); + +const resetSaveDialog: ActionHandler = (state) => ({ + ...state, save_overwriting: false, save_finished: false, save_processing: false, save_error: '', +}); +// +// const showRenderer: ActionHandler = (state) => ({ +// ...state, +// renderer: { +// ...state.renderer, +// renderer_active: true +// } +// }); + +const hideRenderer: ActionHandler = (state) => ({ + ...state, + renderer: { ...state.renderer, renderer_active: false } +}); + +const setRenderer: ActionHandler = (state, { payload }) => ({ + ...state, + renderer: { ...state.renderer, ...payload } +}); + +const setProvider: ActionHandler = (state, { provider }) => ({ ...state, provider }); + +const setDialog: ActionHandler = (state, { dialog }) => ({ + ...state, + dialog, +}); + +const setDialogActive: ActionHandler = (state, { dialog_active }) => ({ + ...state, + dialog_active: dialog_active || !state.dialog_active, +}); + +const setReady: ActionHandler = (state, { ready = true }) => ({ + ...state, + ready, +}); + +const searchSetTitle: ActionHandler = (state, { title = '' }) => ({ + ...state, + routes: { + ...state.routes, + filter: { + ...state.routes.filter, + title, + distance: [0, 10000], + } + } +}); + +const searchSetDistance: ActionHandler = (state, { distance = [0, 9999] }) => ({ + ...state, + routes: { + ...state.routes, + filter: { + ...state.routes.filter, + distance, + } + } +}); + +const searchSetTab: ActionHandler = (state, { tab = TABS[Object.keys(TABS)[0]] }) => ({ + ...state, + routes: { + ...state.routes, + filter: { + ...state.routes.filter, + tab: Object.keys(TABS).indexOf(tab) >= 0 ? tab : TABS[Object.keys(TABS)[0]], + } + } +}); + +const searchPutRoutes: ActionHandler = (state, { list = [], min, max }) => ({ + ...state, + routes: { + ...state.routes, + list, + filter: { + ...state.routes.filter, + distance: (state.routes.filter.min === state.routes.filter.max) + ? [min, max] + : state.routes.filter.distance, + min, + max, + } + } +}); + +const searchSetLoading: ActionHandler = (state, { loading = false }) => ({ + ...state, + routes: { + ...state.routes, + loading, + } +}); + +const setPublic: ActionHandler = (state, { is_public = false }) => ({ ...state, is_public }); +const setSpeed: ActionHandler = (state, { speed = 15 }) => ({ + ...state, + speed, + estimated: getEstimated(state.distance, speed), +}); + +const setMarkersShown: ActionHandler = (state, { markers_shown = true }) => ({ ...state, markers_shown }); + +const HANDLERS = ({ + [ACTIONS.SET_USER]: setUser, + [ACTIONS.SET_EDITING]: setEditing, + [ACTIONS.SET_CHANGED]: setChanged, + [ACTIONS.SET_MODE]: setMode, + [ACTIONS.SET_DISTANCE]: setDistance, + [ACTIONS.SET_ROUTER_POINTS]: setRouterPoints, + [ACTIONS.SET_ACTIVE_STICKER]: setActiveSticker, + [ACTIONS.SET_LOGO]: setLogo, + [ACTIONS.SET_TITLE]: setTitle, + [ACTIONS.SET_ADDRESS]: setAddress, + [ACTIONS.SET_ADDRESS_ORIGIN]: setAddressOrigin, + + [ACTIONS.SET_SAVE_ERROR]: setSaveError, + [ACTIONS.SET_SAVE_OVERWRITE]: setSaveOverwrite, + [ACTIONS.SET_SAVE_SUCCESS]: setSaveSuccess, + [ACTIONS.SEND_SAVE_REQUEST]: sendSaveRequest, + [ACTIONS.RESET_SAVE_DIALOG]: resetSaveDialog, + + [ACTIONS.HIDE_RENDERER]: hideRenderer, + [ACTIONS.SET_RENDERER]: setRenderer, + + [ACTIONS.SET_PROVIDER]: setProvider, + + [ACTIONS.SET_DIALOG]: setDialog, + [ACTIONS.SET_DIALOG_ACTIVE]: setDialogActive, + [ACTIONS.SET_READY]: setReady, + + [ACTIONS.SEARCH_SET_TITLE]: searchSetTitle, + [ACTIONS.SEARCH_SET_DISTANCE]: searchSetDistance, + [ACTIONS.SEARCH_SET_TAB]: searchSetTab, + [ACTIONS.SEARCH_PUT_ROUTES]: searchPutRoutes, + [ACTIONS.SEARCH_SET_LOADING]: searchSetLoading, + [ACTIONS.SET_PUBLIC]: setPublic, + [ACTIONS.SET_SPEED]: setSpeed, + + [ACTIONS.SET_MARKERS_SHOWN]: setMarkersShown, +}); + +export const INITIAL_STATE: IRootReducer = { + ready: false, + user: { ...DEFAULT_USER }, + editing: false, + mode: MODES.NONE, + logo: DEFAULT_LOGO, + routerPoints: 0, + distance: 0, + estimated: 0, + speed: 15, + activeSticker: { set: null, sticker: null }, + title: '', + address: '', + address_origin: '', + changed: false, + provider: DEFAULT_PROVIDER, + is_public: false, + markers_shown: true, + + save_error: '', + save_finished: false, + save_overwriting: false, + save_processing: false, + + dialog: DIALOGS.NONE, + dialog_active: false, + + renderer: { + data: '', + width: 0, + height: 0, + renderer_active: false, + info: '', + progress: 0, + }, + + routes: { + limit: 0, + loading: false, // <-- maybe delete this + list: [], + filter: { + title: '', + starred: false, + distance: [0, 10000], + author: '', + tab: '', + min: 0, + max: 10000, + } + }, +}; + +export const userReducer = createReducer(INITIAL_STATE, HANDLERS);