fixed loading route and maps

This commit is contained in:
Fedor Katurov 2019-12-12 13:52:30 +07:00
parent 1f774a8299
commit 62cb8d8e18
18 changed files with 715 additions and 369 deletions

View file

@ -8,7 +8,7 @@ const iframe_vk = require('./auth/iframe/vk');
const router = express.Router(); const router = express.Router();
router.get('/', check); router.get('/', check);
router.get('/list', list); // router.get('/list', list);
router.get('/guest', guest); router.get('/guest', guest);
router.get('/social/vk', vk); router.get('/social/vk', vk);
router.get('/iframe/vk', iframe_vk); router.get('/iframe/vk', iframe_vk);

View file

@ -5,14 +5,6 @@ module.exports = async (req, res) => {
const { id, token } = req.query; const { id, token } = req.query;
const user = await User.findOne({ _id: id, token }); const user = await User.findOne({ _id: id, token });
// .populate({
// path: 'routes',
// select: '_id title distance owner updated_at',
// options: {
// limit: 200,
// sort: { updated_at: -1 },
// }
// })
const random_url = await generateRandomUrl(); const random_url = await generateRandomUrl();

View file

@ -16,7 +16,7 @@ const fetchUserData = async (req, res) => {
client_id: CONFIG.SOCIAL.VK.APP_ID, client_id: CONFIG.SOCIAL.VK.APP_ID,
client_secret: CONFIG.SOCIAL.VK.SECRET, client_secret: CONFIG.SOCIAL.VK.SECRET,
code, code,
redirect_uri: `${proto}://${host}/auth/social/vk`, redirect_uri: `${proto}://${host}/api/auth/social/vk`,
} }
} }
).catch(() => { ).catch(() => {

View file

@ -40,8 +40,8 @@ export interface IMapListDialogProps extends IRootState {
} }
export interface IMapListDialogState { export interface IMapListDialogState {
menu_target: IRouteListItem['_id'], menu_target: IRouteListItem['address'],
editor_target: IRouteListItem['_id'], editor_target: IRouteListItem['address'],
is_editing: boolean, is_editing: boolean,
is_dropping: boolean, is_dropping: boolean,
@ -56,14 +56,14 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
is_dropping: false, is_dropping: false,
}; };
startEditing = (editor_target: IRouteListItem['_id']): void => this.setState({ startEditing = (editor_target: IRouteListItem['address']): void => this.setState({
editor_target, editor_target,
menu_target: null, menu_target: null,
is_editing: true, is_editing: true,
is_dropping: false, is_dropping: false,
}); });
showMenu = (menu_target: IRouteListItem['_id']): void => this.setState({ showMenu = (menu_target: IRouteListItem['address']): void => this.setState({
menu_target, menu_target,
}); });
@ -71,7 +71,7 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
menu_target: null, menu_target: null,
}); });
showDropCard = (editor_target: IRouteListItem['_id']): void => this.setState({ showDropCard = (editor_target: IRouteListItem['address']): void => this.setState({
editor_target, editor_target,
menu_target: null, menu_target: null,
is_editing: false, is_editing: false,
@ -211,13 +211,13 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
<RouteRowWrapper <RouteRowWrapper
title={route.title} title={route.title}
distance={route.distance} distance={route.distance}
_id={route._id} _id={route.address}
is_public={route.is_public} is_public={route.is_public}
is_starred={route.is_starred} is_starred={route.is_starred}
tab={tab} tab={tab}
is_editing_mode={is_dropping ? 'drop' : 'edit'} is_editing_mode={is_dropping ? 'drop' : 'edit'}
is_editing_target={editor_target === route._id} is_editing_target={editor_target === route.address}
is_menu_target={menu_target === route._id} is_menu_target={menu_target === route.address}
openRoute={this.openRoute} openRoute={this.openRoute}
startEditing={this.startEditing} startEditing={this.startEditing}
stopEditing={this.stopEditing} stopEditing={this.stopEditing}
@ -227,7 +227,7 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
dropRoute={this.dropRoute} dropRoute={this.dropRoute}
modifyRoute={this.modifyRoute} modifyRoute={this.modifyRoute}
toggleStarred={this.toggleStarred} toggleStarred={this.toggleStarred}
key={route._id} key={route.address}
is_admin={role === ROLES.admin} is_admin={role === ROLES.admin}
/> />
)) ))

View file

@ -30,7 +30,7 @@ export const RouteRowView = ({
title, distance, _id, openRoute, tab, startEditing, showMenu, showDropCard, hideMenu, is_admin, is_starred, toggleStarred, title, distance, _id, openRoute, tab, startEditing, showMenu, showDropCard, hideMenu, is_admin, is_starred, toggleStarred,
}: Props): ReactElement<Props, null> => ( }: Props): ReactElement<Props, null> => (
<div <div
className={classnames('route-row-view', { has_menu: (tab === 'mine') })} className={classnames('route-row-view', { has_menu: (tab === 'my') })}
> >
{ {
(tab === 'all' || tab === 'starred') && is_admin && (tab === 'all' || tab === 'starred') && is_admin &&
@ -48,7 +48,7 @@ export const RouteRowView = ({
> >
<div className="route-title"> <div className="route-title">
{ {
(tab === 'mine' || !is_admin) && is_starred && (tab === 'my' || !is_admin) && is_starred &&
<div className="route-row-corner"><Icon icon="icon-star-fill" size={18} /></div> <div className="route-row-corner"><Icon icon="icon-star-fill" size={18} /></div>
} }
<span> <span>
@ -68,7 +68,7 @@ export const RouteRowView = ({
</div> </div>
</div> </div>
{ {
tab === 'mine' && tab === 'my' &&
<React.Fragment> <React.Fragment>
<div <div
className="route-row-edit-button pointer" className="route-row-edit-button pointer"

View file

@ -65,7 +65,7 @@ export class Component extends React.PureComponent<Props, State> {
setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened }); setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened });
openMapsDialog = () => { openMapsDialog = () => {
this.props.openMapDialog('mine'); this.props.openMapDialog('my');
}; };
openAppInfoDialog = () => { openAppInfoDialog = () => {
@ -81,7 +81,7 @@ export class Component extends React.PureComponent<Props, State> {
const left = (width - 700) / 2; const left = (width - 700) / 2;
window.open( window.open(
`https://oauth.vk.com/authorize?client_id=5987644&scope=&redirect_uri=${CLIENT.API_ADDR}/auth/social/vk`, `https://oauth.vk.com/authorize?client_id=5987644&scope=&redirect_uri=${CLIENT.API_ADDR}/api/auth/vk`,
'socialPopupWindow', 'socialPopupWindow',
`location=no,width=700,height=370,scrollbars=no,top=${top},left=${left},resizable=no` `location=no,width=700,height=370,scrollbars=no,top=${top},left=${left},resizable=no`
); );

View file

@ -1,28 +1,24 @@
// @flow // @flow
import * as React from 'react'; import * as React from "react";
import { UserPicture } from '$components/user/UserPicture'; import { UserPicture } from "$components/user/UserPicture";
import { IUser } from "$constants/auth"; import { IUser } from "$constants/auth";
interface Props { interface Props {
user: IUser, user: IUser;
setMenuOpened: () => void, setMenuOpened: () => void;
} }
export const UserButton = ({ export const UserButton = ({
setMenuOpened, setMenuOpened,
user: { user: { uid, photo, name }
id,
photo,
first_name,
}
}: Props) => ( }: Props) => (
<div className="control-bar user-bar"> <div className="control-bar user-bar">
<div className="user-button" onClick={setMenuOpened}> <div className="user-button" onClick={setMenuOpened}>
<UserPicture photo={photo} /> <UserPicture photo={photo} />
<div className="user-button-fields"> <div className="user-button-fields">
<div className="user-button-name">{(first_name || id || '...')}</div> <div className="user-button-name">{name || uid || "..."}</div>
<div className="user-button-text">{(id || 'пользователь')}</div> <div className="user-button-text">{uid || "пользователь"}</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,16 +1,16 @@
import { CLIENT } from '$config/frontend'; import { CLIENT } from '$config/frontend';
export const API: { [x: string]: string } = { export const API: { [x: string]: string } = {
GET_GUEST: `${CLIENT.API_ADDR}/auth`, GET_GUEST: `${CLIENT.API_ADDR}/api/auth/`,
CHECK_TOKEN: `${CLIENT.API_ADDR}/auth`, CHECK_TOKEN: `${CLIENT.API_ADDR}/api/auth/`,
IFRAME_LOGIN_VK: `${CLIENT.API_ADDR}/auth/iframe/vk`, IFRAME_LOGIN_VK: `${CLIENT.API_ADDR}/api/auth/vk/`,
GET_MAP: `${CLIENT.API_ADDR}/route`, GET_MAP: `${CLIENT.API_ADDR}/api/route/`,
POST_MAP: `${CLIENT.API_ADDR}/route`, POST_MAP: `${CLIENT.API_ADDR}/api/route/`,
GET_ROUTE_LIST: `${CLIENT.API_ADDR}/route/list`, GET_ROUTE_LIST: `${CLIENT.API_ADDR}/api/route/list/`,
DROP_ROUTE: `${CLIENT.API_ADDR}/route`, DROP_ROUTE: `${CLIENT.API_ADDR}/api/route/`,
MODIFY_ROUTE: `${CLIENT.API_ADDR}/route/modify`, MODIFY_ROUTE: `${CLIENT.API_ADDR}/api/route/`,
SET_STARRED: `${CLIENT.API_ADDR}/route/star`, SET_STARRED: `${CLIENT.API_ADDR}/api/route/publish/`,
}; };
export const API_RETRY_INTERVAL = 10; export const API_RETRY_INTERVAL = 10;

View file

@ -1,42 +1,39 @@
export interface IRoles { export interface IRoles {
guest: string, guest: string;
vk: string, vk: string;
admin: string, admin: string;
} }
export interface IUser { export interface IUser {
new_messages: number, new_messages: number;
place_types: {}, place_types: {};
random_url: string, random_url: string;
role: IRoles[keyof IRoles], role: IRoles[keyof IRoles];
routes: {}, routes: {};
success: boolean, success: boolean;
id?: string, id?: number;
token?: string, uid: string;
photo: string, token?: string;
first_name: string, photo: string;
// userdata: { name: string;
// name: string,
// agent: string,
// ip: string,
// }
} }
export const ROLES: IRoles = { export const ROLES: IRoles = {
guest: 'guest', guest: "guest",
vk: 'vk', vk: "vk",
admin: 'admin', admin: "admin"
}; };
export const DEFAULT_USER: IUser = { export const DEFAULT_USER: IUser = {
new_messages: 0, new_messages: 0,
place_types: {}, place_types: {},
random_url: '', random_url: "",
role: ROLES.guest, role: ROLES.guest,
routes: {}, routes: {},
success: false, success: false,
id: null, id: null,
token: null, token: null,
photo: null, photo: null,
first_name: null, name: null,
uid: null,
}; };

View file

@ -5,7 +5,7 @@ export interface IDialogs {
} }
export interface IMapTabs { export interface IMapTabs {
mine: string, my: string,
all: string, all: string,
starred: string, starred: string,
} }
@ -17,7 +17,7 @@ export const DIALOGS: IDialogs = ({
}); });
export const TABS: IMapTabs = ({ export const TABS: IMapTabs = ({
mine: 'Мои', my: 'Мои',
all: 'Заявки', all: 'Заявки',
starred: 'Каталог', starred: 'Каталог',
}); });

View file

@ -15,6 +15,17 @@ export interface IStickerPack {
} }
} }
export interface ISticker {
angle: number;
set: string;
sticker: string;
text: string;
latlngs: {
lat: number;
lng: number;
}
}
export interface IStickers { export interface IStickers {
base: IStickerPack, base: IStickerPack,
real: IStickerPack, real: IStickerPack,

View file

@ -34,11 +34,11 @@ interface IEditor {
router: Router; router: Router;
logo: keyof ILogos; logo: keyof ILogos;
owner: { id: string }; owner: number;
initialData: { initialData: {
version: number, version: number,
title: IRootState['title'], title: IRootState['title'],
owner: { id: string }, owner: number,
address: IRootState['address'], address: IRootState['address'],
path: any, path: any,
route: any, route: any,
@ -322,11 +322,11 @@ export class Editor {
is_public, is_public,
is_starred, is_starred,
description, description,
}: IEditor['initialData']): void => { }: Partial<IEditor['initialData']>): void => {
this.setTitle(title || ''); this.setTitle(title || '');
const { id } = this.getUser(); const { id } = this.getUser();
if (address && id && owner && id === owner.id) { if (address && id && owner && id === owner) {
this.setAddress(address); this.setAddress(address);
} }
@ -389,7 +389,7 @@ export class Editor {
version: 2, version: 2,
title: this.getTitle(), title: this.getTitle(),
owner: this.owner, owner: this.owner,
address: (this.owner.id === id ? path : null), address: (this.owner === id ? path : null),
path, path,
route, route,
stickers, stickers,
@ -405,7 +405,7 @@ export class Editor {
const { id } = this.getUser(); const { id } = this.getUser();
this.setInitialData(); this.setInitialData();
this.owner = { id }; this.owner = id;
this.poly.enableEditor(); this.poly.enableEditor();
this.stickers.startEditing(); this.stickers.startEditing();

View file

@ -4,7 +4,7 @@ import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; import storage from 'redux-persist/lib/storage';
import createSagaMiddleware from 'redux-saga'; import createSagaMiddleware from 'redux-saga';
import { userReducer } from '$redux/user/reducer'; import { userReducer, IRootReducer } from '$redux/user/reducer';
import { userSaga } from '$redux/user/sagas'; import { userSaga } from '$redux/user/sagas';
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import { locationChanged } from '$redux/user/actions'; import { locationChanged } from '$redux/user/actions';
@ -16,6 +16,9 @@ const userPersistConfig: PersistConfig = {
storage, storage,
}; };
export interface IState {
user: IRootReducer
}
// create the saga middleware // create the saga middleware
export const sagaMiddleware = createSagaMiddleware(); export const sagaMiddleware = createSagaMiddleware();

View file

@ -94,9 +94,9 @@ export const mapsSetShift = (shift: number) => ({ type: ACTIONS.MAPS_SET_SHIFT,
export const setFeature = (features: { [x: string]: boolean }) => ({ type: ACTIONS.SET_FEATURE, features }); export const setFeature = (features: { [x: string]: boolean }) => ({ type: ACTIONS.SET_FEATURE, features });
export const setIsRouting = (is_routing: boolean) => ({ type: ACTIONS.SET_IS_ROUTING, is_routing }); export const setIsRouting = (is_routing: boolean) => ({ type: ACTIONS.SET_IS_ROUTING, is_routing });
export const dropRoute = (_id: string) => ({ type: ACTIONS.DROP_ROUTE, _id }); export const dropRoute = (address: string) => ({ type: ACTIONS.DROP_ROUTE, address });
export const modifyRoute = (_id: string, { title, is_public }: { title: string, is_public: boolean }) => ({ export const modifyRoute = (address: string, { title, is_public }: { title: string, is_public: boolean }) => ({
type: ACTIONS.MODIFY_ROUTE, _id, title, is_public type: ACTIONS.MODIFY_ROUTE, address, title, is_public
}); });
export const toggleRouteStarred = (_id: string) => ({ type: ACTIONS.TOGGLE_ROUTE_STARRED, _id }); export const toggleRouteStarred = (address: string) => ({ type: ACTIONS.TOGGLE_ROUTE_STARRED, address });
export const setRouteStarred = (_id: string, is_starred: boolean) => ({ type: ACTIONS.SET_ROUTE_STARRED, _id, is_starred }); export const setRouteStarred = (address: string, is_starred: boolean) => ({ type: ACTIONS.SET_ROUTE_STARRED, address, is_starred });

View file

@ -7,10 +7,27 @@ import { TIPS } from '$constants/tips';
import { DEFAULT_PROVIDER, PROVIDERS } from '$constants/providers'; import { DEFAULT_PROVIDER, PROVIDERS } from '$constants/providers';
import { DIALOGS, IDialogs, TABS } from '$constants/dialogs'; import { DIALOGS, IDialogs, TABS } from '$constants/dialogs';
import * as ActionCreators from '$redux/user/actions'; import * as ActionCreators from '$redux/user/actions';
import { IStickers } from "$constants/stickers"; import { IStickers, ISticker } from "$constants/stickers";
import { IRoutePoint } from '$utils/gpx';
import { IStickerDump } from '$modules/Sticker';
export interface IRoute {
version: number,
title: IRootState['title'],
owner: number,
address: IRootState['address'],
route: IRoutePoint[],
stickers: IStickerDump[],
provider: IRootState['provider'],
is_public: IRootState['is_public'],
is_published: IRootState['is_starred'],
description: IRootState['description'],
logo: IRootState['logo'],
distance: IRootState['distance']
}
export interface IRouteListItem { export interface IRouteListItem {
_id: string, address: string,
title: string, title: string,
distance: number, distance: number,
is_public: boolean, is_public: boolean,
@ -318,13 +335,13 @@ const setIsRouting: ActionHandler<typeof ActionCreators.setIsRouting> = (state,
is_routing, is_routing,
}); });
const setRouteStarred: ActionHandler<typeof ActionCreators.setRouteStarred> = (state, { _id, is_starred }) => ({ const setRouteStarred: ActionHandler<typeof ActionCreators.setRouteStarred> = (state, { address, is_starred }) => ({
...state, ...state,
routes: { routes: {
...state.routes, ...state.routes,
list: ( list: (
state.routes.list state.routes.list
.map(el => el._id === _id ? { ...el, is_starred } : el) .map(el => el.address === address ? { ...el, is_starred } : el)
.filter(el => ( .filter(el => (
(state.routes.filter.tab === 'starred' && el.is_starred) || (state.routes.filter.tab === 'starred' && el.is_starred) ||
(state.routes.filter.tab === 'all' && !el.is_starred) (state.routes.filter.tab === 'all' && !el.is_starred)

View file

@ -1,13 +1,27 @@
import { REHYDRATE } from 'redux-persist'; import { REHYDRATE } from "redux-persist";
import { delay, SagaIterator } from 'redux-saga'; import { delay, SagaIterator } from "redux-saga";
import { takeLatest, select, call, put, takeEvery, race, take } from 'redux-saga/effects';
import { import {
checkIframeToken, checkOSRMService, takeLatest,
checkUserToken, dropRoute, select,
getGuestToken, getRouteList, call,
getStoredMap, modifyRoute, put,
postMap, sendRouteStarred takeEvery,
} from '$utils/api'; race,
take,
TakeEffect
} from "redux-saga/effects";
import {
checkIframeToken,
checkOSRMService,
checkUserToken,
dropRoute,
getGuestToken,
getRouteList,
getStoredMap,
modifyRoute,
postMap,
sendRouteStarred
} from "$utils/api";
import { import {
hideRenderer, hideRenderer,
searchPutRoutes, searchPutRoutes,
@ -32,54 +46,79 @@ import {
setProvider, setProvider,
changeProvider, changeProvider,
setSaveLoading, setSaveLoading,
mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, setRouteStarred, setDescription, mapsSetShift,
} from '$redux/user/actions'; searchChangeDistance,
import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history'; clearAll,
import { editor } from '$modules/Editor'; setFeature,
import { ACTIONS } from '$redux/user/constants'; searchSetTitle,
import { MODES } from '$constants/modes'; setRouteStarred,
import { DEFAULT_USER } from '$constants/auth'; setDescription
import { TIPS } from '$constants/tips'; } from "$redux/user/actions";
import { import {
composeArrows, composeDistMark, getUrlData,
parseQuery,
pushLoaderState,
pushNetworkInitError,
pushPath,
replacePath
} from "$utils/history";
import { editor } from "$modules/Editor";
import { ACTIONS } from "$redux/user/constants";
import { MODES } from "$constants/modes";
import { DEFAULT_USER, IUser } from "$constants/auth";
import { TIPS } from "$constants/tips";
import {
composeArrows,
composeDistMark,
composeImages, composeImages,
composePoly, composeStickers, downloadCanvas, composePoly,
composeStickers,
downloadCanvas,
fetchImages, fetchImages,
getPolyPlacement, getStickersPlacement, getPolyPlacement,
getStickersPlacement,
getTilePlacement, getTilePlacement,
imageFetcher imageFetcher
} from '$utils/renderer'; } from "$utils/renderer";
import { ILogos, LOGOS } from '$constants/logos'; import { LOGOS } from "$constants/logos";
import { DEFAULT_PROVIDER } from '$constants/providers'; import { DEFAULT_PROVIDER } from "$constants/providers";
import { DIALOGS, TABS } from '$constants/dialogs'; import { DIALOGS } from "$constants/dialogs";
import * as ActionCreators from '$redux/user/actions'; import * as ActionCreators from "$redux/user/actions";
import { IRootState } from "$redux/user/reducer"; import { IRootState } from "$redux/user/reducer";
import { downloadGPXTrack, getGPXString } from "$utils/gpx"; import { downloadGPXTrack, getGPXString } from "$utils/gpx";
import { Unwrap } from "$utils/middleware";
import { IState } from "$redux/store";
const getUser = state => (state.user.user); const getUser = (state: IState) => state.user.user;
const getState = state => (state.user); const getState = (state: IState) => state.user;
const hideLoader = () => { const hideLoader = () => {
document.getElementById('loader').style.opacity = String(0); document.getElementById("loader").style.opacity = String(0);
document.getElementById('loader').style.pointerEvents = 'none'; document.getElementById("loader").style.pointerEvents = "none";
return true; return true;
}; };
function* generateGuestSaga() { function* generateGuestSaga() {
const user = yield call(getGuestToken); const {
yield put(setUser(user)); data: { user, random_url }
}: Unwrap<typeof getGuestToken> = yield call(getGuestToken);
return user; yield put(setUser({ ...user, random_url }));
return { ...user, random_url };
} }
function* startEmptyEditorSaga() { function* startEmptyEditorSaga() {
const { user: { id, random_url }, provider = DEFAULT_PROVIDER } = yield select(getState); const {
user: { id, random_url },
provider = DEFAULT_PROVIDER
} = yield select(getState);
pushPath(`/${random_url}/edit`); pushPath(`/${random_url}/edit`);
editor.owner = { id }; editor.owner = id;
editor.setProvider(provider); editor.setProvider(provider);
editor.startEditing(); editor.startEditing();
@ -95,9 +134,7 @@ function* startEditingSaga() {
} }
function* stopEditingSaga() { function* stopEditingSaga() {
const { const { changed, editing, mode, address_origin } = yield select(getState);
changed, editing, mode, address_origin
} = yield select(getState);
const { path } = getUrlData(); const { path } = getUrlData();
if (!editing) return; if (!editing) return;
@ -111,22 +148,24 @@ function* stopEditingSaga() {
yield put(setChanged(false)); yield put(setChanged(false));
yield put(setEditing(editor.hasEmptyHistory)); // don't close editor if no previous map yield put(setEditing(editor.hasEmptyHistory)); // don't close editor if no previous map
yield pushPath(`/${(address_origin || path)}/`); yield pushPath(`/${address_origin || path}/`);
} }
function* loadMapSaga(path) { function* loadMapSaga(path) {
const map = yield call(getStoredMap, { name: path }); const { data: { route, error, random_url } }: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
if (map) { if (route && !error) {
yield editor.clearAll(); yield editor.clearAll();
yield editor.setData(map); yield editor.setData(route);
yield editor.fitDrawing(); yield editor.fitDrawing();
yield editor.setInitialData(); yield editor.setInitialData();
yield put(setChanged(false)); yield put(setChanged(false));
return { route, random_url };
} }
return map; return null
} }
function* replaceAddressIfItsBusy(destination, original) { function* replaceAddressIfItsBusy(destination, original) {
@ -150,14 +189,17 @@ function* setReadySaga() {
hideLoader(); hideLoader();
yield call(checkOSRMServiceSaga); yield call(checkOSRMServiceSaga);
yield put(searchSetTab('mine')); yield put(searchSetTab("my"));
} }
function* mapInitSaga() { function* mapInitSaga() {
pushLoaderState(90); pushLoaderState(90);
const { path, mode, hash } = getUrlData(); const { path, mode, hash } = getUrlData();
const { provider, user: { id } } = yield select(getState); const {
provider,
user: { id }
} = yield select(getState);
editor.map.setProvider(provider); editor.map.setProvider(provider);
yield put(changeProvider(provider)); yield put(changeProvider(provider));
@ -174,13 +216,13 @@ function* mapInitSaga() {
if (path) { if (path) {
const map = yield call(loadMapSaga, path); const map = yield call(loadMapSaga, path);
if (map) { if (map && map.route) {
if (mode && mode === 'edit') { if (mode && mode === "edit") {
if (map && map.owner && mode === 'edit' && map.owner.id !== id) { if (map && map.route && map.route.owner && mode === "edit" && map.route.owner !== id) {
yield call(setReadySaga); yield call(setReadySaga);
yield call(replaceAddressIfItsBusy, map.random_url, map.address); yield call(replaceAddressIfItsBusy, map.random_url, map.address);
} else { } else {
yield put(setAddressOrigin('')); yield put(setAddressOrigin(""));
} }
yield put(setEditing(true)); yield put(setEditing(true));
@ -226,10 +268,15 @@ function* authCheckSaga() {
} }
if (id && token) { if (id && token) {
const user = yield call(checkUserToken, { id, token }); const {
data: { user, random_url }
}: Unwrap<typeof checkUserToken> = yield call(checkUserToken, {
id,
token
});
if (user) { if (user) {
yield put(setUser(user)); yield put(setUser({ ...user, random_url }));
pushLoaderState(99); pushLoaderState(99);
@ -251,14 +298,19 @@ function* setModeSaga({ mode }: ReturnType<typeof ActionCreators.setMode>) {
// console.log('change', mode); // console.log('change', mode);
} }
function* setActiveStickerSaga({ activeSticker }: { type: string, activeSticker: IRootState['activeSticker'] }) { function* setActiveStickerSaga({
yield editor.activeSticker = activeSticker; activeSticker
}: {
type: string;
activeSticker: IRootState["activeSticker"];
}) {
yield (editor.activeSticker = activeSticker);
yield put(setMode(MODES.STICKERS)); yield put(setMode(MODES.STICKERS));
return true; return true;
} }
function* setLogoSaga({ logo }: { type: string, logo: string }) { function* setLogoSaga({ logo }: { type: string; logo: string }) {
const { mode } = yield select(getState); const { mode } = yield select(getState);
editor.logo = logo; editor.logo = logo;
@ -299,7 +351,8 @@ function* clearSaga({ type }) {
yield put(setChanged(false)); yield put(setChanged(false));
break; break;
default: break; default:
break;
} }
yield put(setActiveSticker(null)); yield put(setActiveSticker(null));
@ -307,48 +360,75 @@ function* clearSaga({ type }) {
} }
function* sendSaveRequestSaga({ function* sendSaveRequestSaga({
title, address, force, is_public, description, title,
address,
force,
is_public,
description
}: ReturnType<typeof ActionCreators.sendSaveRequest>) { }: ReturnType<typeof ActionCreators.sendSaveRequest>) {
if (editor.isEmpty) return yield put(setSaveError(TIPS.SAVE_EMPTY)); if (editor.isEmpty) return yield put(setSaveError(TIPS.SAVE_EMPTY));
const { route, stickers, provider } = editor.dumpData(); const { route, stickers, provider } = editor.dumpData();
const { logo, distance } = yield select(getState); const { logo, distance } = yield select(getState);
const { id, token } = yield select(getUser); const { token } = yield select(getUser);
yield put(setSaveLoading(true)); yield put(setSaveLoading(true));
const { result, timeout, cancel } = yield race({ const {
result,
timeout,
cancel
}: {
result: Unwrap<typeof postMap>;
timeout: boolean;
cancel: TakeEffect;
} = yield race({
result: postMap({ result: postMap({
id, token, route, stickers, title, force, address, logo, distance, provider, is_public, description, token,
route,
stickers,
title,
force,
address,
logo,
distance,
provider,
is_public,
description
}), }),
timeout: delay(10000), timeout: delay(10000),
cancel: take(ACTIONS.RESET_SAVE_DIALOG), cancel: take(ACTIONS.RESET_SAVE_DIALOG)
}); });
yield put(setSaveLoading(false)); yield put(setSaveLoading(false));
if (cancel) return yield put(setMode(MODES.NONE)); if (cancel) return yield put(setMode(MODES.NONE));
if (result && result.mode === 'overwriting') return yield put(setSaveOverwrite()); if (result && result.mode === "overwriting")
if (result && result.mode === 'exists') return yield put(setSaveError(TIPS.SAVE_EXISTS)); return yield put(setSaveOverwrite());
if (timeout || !result || !result.success || !result.address) return yield put(setSaveError(TIPS.SAVE_TIMED_OUT)); if (result && result.mode === "exists")
return yield put(setSaveError(TIPS.SAVE_EXISTS));
if (timeout || !result || !result.success || !result.address)
return yield put(setSaveError(TIPS.SAVE_TIMED_OUT));
return yield put(setSaveSuccess({ return yield put(
address: result.address, setSaveSuccess({
title: result.title, address: result.address,
is_public: result.is_public, title: result.title,
description: result.description, is_public: result.is_public,
description: result.description,
save_error: TIPS.SAVE_SUCCESS, save_error: TIPS.SAVE_SUCCESS
})); })
);
} }
function* getRenderData() { function* getRenderData() {
yield put(setRenderer({ info: 'Загрузка тайлов', progress: 0.1 })); yield put(setRenderer({ info: "Загрузка тайлов", progress: 0.1 }));
const canvas = <HTMLCanvasElement>document.getElementById('renderer'); const canvas = <HTMLCanvasElement>document.getElementById("renderer");
canvas.width = window.innerWidth; canvas.width = window.innerWidth;
canvas.height = window.innerHeight; canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d");
const geometry = getTilePlacement(); const geometry = getTilePlacement();
const points = getPolyPlacement(); const points = getPolyPlacement();
@ -359,7 +439,7 @@ function* getRenderData() {
const images = yield fetchImages(ctx, geometry); const images = yield fetchImages(ctx, geometry);
yield put(setRenderer({ info: 'Отрисовка', progress: 0.5 })); yield put(setRenderer({ info: "Отрисовка", progress: 0.5 }));
yield composeImages({ geometry, images, ctx }); yield composeImages({ geometry, images, ctx });
yield composePoly({ points, ctx }); yield composePoly({ points, ctx });
@ -367,9 +447,9 @@ function* getRenderData() {
yield composeDistMark({ ctx, points, distance }); yield composeDistMark({ ctx, points, distance });
yield composeStickers({ stickers, ctx }); yield composeStickers({ stickers, ctx });
yield put(setRenderer({ info: 'Готово', progress: 1 })); yield put(setRenderer({ info: "Готово", progress: 1 }));
return yield canvas.toDataURL('image/jpeg'); return yield canvas.toDataURL("image/jpeg");
} }
function* takeAShotSaga() { function* takeAShotSaga() {
@ -377,50 +457,62 @@ function* takeAShotSaga() {
const { result, timeout } = yield race({ const { result, timeout } = yield race({
result: worker, result: worker,
timeout: delay(500), timeout: delay(500)
}); });
if (timeout) yield put(setMode(MODES.SHOT_PREFETCH)); if (timeout) yield put(setMode(MODES.SHOT_PREFETCH));
const data = yield (result || worker); const data = yield result || worker;
yield put(setMode(MODES.NONE)); yield put(setMode(MODES.NONE));
yield put(setRenderer({ yield put(
data, renderer_active: true, width: window.innerWidth, height: window.innerHeight setRenderer({
})); data,
renderer_active: true,
width: window.innerWidth,
height: window.innerHeight
})
);
} }
function* getCropData({ function* getCropData({ x, y, width, height }) {
x, y, width, height const {
}) { logo,
const { logo, renderer: { data } } = yield select(getState); renderer: { data }
const canvas = <HTMLCanvasElement>document.getElementById('renderer'); } = yield select(getState);
const canvas = <HTMLCanvasElement>document.getElementById("renderer");
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d");
const image = yield imageFetcher(data); const image = yield imageFetcher(data);
ctx.drawImage(image, -x, -y); ctx.drawImage(image, -x, -y);
if (logo && LOGOS[logo][1]) { if (logo && LOGOS[logo][1]) {
const logoImage = yield imageFetcher(LOGOS[logo][1]); const logoImage = yield imageFetcher(LOGOS[logo][1]);
ctx.drawImage(logoImage, width - logoImage.width, height - logoImage.height); ctx.drawImage(
logoImage,
width - logoImage.width,
height - logoImage.height
);
} }
return yield canvas.toDataURL('image/jpeg'); return yield canvas.toDataURL("image/jpeg");
} }
function* cropAShotSaga(params) { function* cropAShotSaga(params) {
const { title, address } = yield select(getState); const { title, address } = yield select(getState);
yield call(getCropData, params); yield call(getCropData, params);
const canvas = document.getElementById('renderer') as HTMLCanvasElement; const canvas = document.getElementById("renderer") as HTMLCanvasElement;
downloadCanvas(canvas, (title || address).replace(/\./ig, ' ')); downloadCanvas(canvas, (title || address).replace(/\./gi, " "));
return yield put(hideRenderer()); return yield put(hideRenderer());
} }
function* changeProviderSaga({ provider }: ReturnType<typeof ActionCreators.changeProvider>) { function* changeProviderSaga({
provider
}: ReturnType<typeof ActionCreators.changeProvider>) {
const { provider: current_provider } = yield select(getState); const { provider: current_provider } = yield select(getState);
yield put(setProvider(provider)); yield put(setProvider(provider));
@ -435,8 +527,15 @@ function* changeProviderSaga({ provider }: ReturnType<typeof ActionCreators.chan
return put(setMode(MODES.NONE)); return put(setMode(MODES.NONE));
} }
function* locationChangeSaga({ location }: ReturnType<typeof ActionCreators.locationChanged>) { function* locationChangeSaga({
const { address, ready, user: { id, random_url }, is_public } = yield select(getState); location
}: ReturnType<typeof ActionCreators.locationChanged>) {
const {
address,
ready,
user: { id, random_url },
is_public
} = yield select(getState);
if (!ready) return; if (!ready) return;
@ -445,16 +544,16 @@ function* locationChangeSaga({ location }: ReturnType<typeof ActionCreators.loca
if (address !== path) { if (address !== path) {
const map = yield call(loadMapSaga, path); const map = yield call(loadMapSaga, path);
if (map && map.owner && mode === 'edit' && map.owner.id !== id) { if (map && map.route && map.route.owner && mode === "edit" && map.route.owner !== id) {
return yield call(replaceAddressIfItsBusy, map.random_url, map.address); return yield call(replaceAddressIfItsBusy, map.random_url, map.address);
} }
} else if (mode === 'edit' && editor.owner.id !== id) { } else if (mode === "edit" && editor.owner !== id) {
return yield call(replaceAddressIfItsBusy, random_url, address); return yield call(replaceAddressIfItsBusy, random_url, address);
} else { } else {
yield put(setAddressOrigin('')); yield put(setAddressOrigin(""));
} }
if (mode !== 'edit') { if (mode !== "edit") {
yield put(setEditing(false)); yield put(setEditing(false));
editor.stopEditing(); editor.stopEditing();
} else { } else {
@ -463,27 +562,38 @@ function* locationChangeSaga({ location }: ReturnType<typeof ActionCreators.loca
} }
} }
function* gotVkUserSaga({ user }: ReturnType<typeof ActionCreators.gotVkUser>) { function* gotVkUserSaga({
const data = yield call(checkUserToken, user); user: u
yield put(setUser(data)); }: ReturnType<typeof ActionCreators.gotVkUser>) {
const {
data: { user, random_url }
}: Unwrap<typeof checkUserToken> = yield call(checkUserToken, u);
yield put(setUser({ ...user, random_url }));
} }
function* keyPressedSaga({ key, target }: ReturnType<typeof ActionCreators.keyPressed>): any { function* keyPressedSaga({
if ( key,
target === 'INPUT' || target
target === 'TEXTAREA' }: ReturnType<typeof ActionCreators.keyPressed>): any {
) { if (target === "INPUT" || target === "TEXTAREA") {
return; return;
} }
if (key === 'Escape') { if (key === "Escape") {
const { dialog_active, mode, renderer: { renderer_active } } = yield select(getState); const {
dialog_active,
mode,
renderer: { renderer_active }
} = yield select(getState);
if (renderer_active) return yield put(hideRenderer()); if (renderer_active) return yield put(hideRenderer());
if (dialog_active) return yield put(setDialogActive(false)); if (dialog_active) return yield put(setDialogActive(false));
if (mode !== MODES.NONE) return yield put(setMode(MODES.NONE)); if (mode !== MODES.NONE) return yield put(setMode(MODES.NONE));
} else if (key === 'Delete') { } else if (key === "Delete") {
const { user: { editing } } = yield select(); const {
user: { editing }
} = yield select();
if (!editing) return; if (!editing) return;
@ -494,47 +604,63 @@ function* keyPressedSaga({ key, target }: ReturnType<typeof ActionCreators.keyPr
} else { } else {
yield put(setMode(MODES.TRASH)); yield put(setMode(MODES.TRASH));
} }
} }
} }
function* searchGetRoutes() { function* searchGetRoutes() {
const { id, token } = yield select(getUser); const { id, token } = yield select(getUser);
const { routes: { step, shift, filter: { title, distance, tab } } } = yield select(getState); const {
routes: {
step,
shift,
filter: { title, distance, tab }
}
} = yield select(getState);
return yield call(getRouteList, { const result: Unwrap<typeof getRouteList> = yield getRouteList({
id,
token, token,
title, search: title,
distance, min: distance[0],
max: distance[1],
step, step,
shift, shift,
author: tab === 'mine' ? id : '', tab
starred: tab === 'starred' ? 1 : 0,
}); });
return result;
} }
function* searchSetSagaWorker() { function* searchSetSagaWorker() {
const { routes: { filter } } = yield select(getState); const {
routes: { filter }
}: ReturnType<typeof getState> = yield select(getState);
const { list, min, max, limit, shift, step } = yield call(searchGetRoutes); const {
data: {
routes,
limits: { min, max, count: limit },
filter: { shift, step }
}
}: Unwrap<typeof getRouteList> = yield call(searchGetRoutes);
yield put(searchPutRoutes({ list, min, max, limit, shift, step })); yield put(searchPutRoutes({ list: routes, min, max, limit, shift, step }));
// change distange range if needed and load additional data // change distange range if needed and load additional data
if ( if (
(filter.min > min && filter.distance[0] <= filter.min) || (filter.min > min && filter.distance[0] <= filter.min) ||
(filter.max < max && filter.distance[1] >= filter.max) (filter.max < max && filter.distance[1] >= filter.max)
) { ) {
yield put(searchChangeDistance([ yield put(
(filter.min > min && filter.distance[0] <= filter.min) searchChangeDistance([
? min filter.min > min && filter.distance[0] <= filter.min
: filter.distance[0], ? min
(filter.max < max && filter.distance[1] >= filter.max) : filter.distance[0],
? max filter.max < max && filter.distance[1] >= filter.max
: filter.distance[1], ? max
])); : filter.distance[1]
])
);
} }
return yield put(searchSetLoading(false)); return yield put(searchSetLoading(false));
@ -547,14 +673,22 @@ function* searchSetSaga() {
yield call(searchSetSagaWorker); yield call(searchSetSagaWorker);
} }
function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDialog>) { function* openMapDialogSaga({
const { dialog_active, routes: { filter: { tab: current } } } = yield select(getState); tab
}: ReturnType<typeof ActionCreators.openMapDialog>) {
const {
dialog_active,
routes: {
filter: { tab: current }
}
} = yield select(getState);
if (dialog_active && tab === current) { if (dialog_active && tab === current) {
return yield put(setDialogActive(false)); return yield put(setDialogActive(false));
} }
if (tab !== current) { // if tab wasnt changed just update data if (tab !== current) {
// if tab wasnt changed just update data
yield put(searchSetTab(tab)); yield put(searchSetTab(tab));
} }
@ -566,13 +700,18 @@ function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDia
function* searchSetTabSaga() { function* searchSetTabSaga() {
yield put(searchChangeDistance([0, 10000])); yield put(searchChangeDistance([0, 10000]));
yield put(searchPutRoutes({ list: [], min: 0, max: 10000, step: 20, shift: 0 })); yield put(
searchPutRoutes({ list: [], min: 0, max: 10000, step: 20, shift: 0 })
);
yield put(searchSetTitle('')); yield put(searchSetTitle(""));
} }
function* setSaveSuccessSaga({ function* setSaveSuccessSaga({
address, title, is_public, description, address,
title,
is_public,
description
}: ReturnType<typeof ActionCreators.setSaveSuccess>) { }: ReturnType<typeof ActionCreators.setSaveSuccess>) {
const { id } = yield select(getUser); const { id } = yield select(getUser);
const { dialog_active } = yield select(getState); const { dialog_active } = yield select(getState);
@ -585,7 +724,7 @@ function* setSaveSuccessSaga({
yield put(setDescription(description)); yield put(setDescription(description));
yield put(setChanged(false)); yield put(setChanged(false));
yield editor.owner = { id }; editor.owner = id;
if (dialog_active) { if (dialog_active) {
yield call(searchSetSagaWorker); yield call(searchSetSagaWorker);
@ -594,7 +733,7 @@ function* setSaveSuccessSaga({
return yield editor.setInitialData(); return yield editor.setInitialData();
} }
function* userLogoutSaga():SagaIterator { function* userLogoutSaga(): SagaIterator {
yield put(setUser(DEFAULT_USER)); yield put(setUser(DEFAULT_USER));
yield call(generateGuestSaga); yield call(generateGuestSaga);
} }
@ -607,8 +746,12 @@ function* setUserSaga() {
return true; return true;
} }
function* setTitleSaga({ title }: ReturnType<typeof ActionCreators.setTitle>):SagaIterator { function* setTitleSaga({
if (title) { document.title = `${title} | Редактор маршрутов`; } title
}: ReturnType<typeof ActionCreators.setTitle>): SagaIterator {
if (title) {
document.title = `${title} | Редактор маршрутов`;
}
} }
function* getGPXTrackSaga(): SagaIterator { function* getGPXTrackSaga(): SagaIterator {
@ -617,13 +760,15 @@ function* getGPXTrackSaga(): SagaIterator {
if (!route || route.length <= 0) return; if (!route || route.length <= 0) return;
const track = getGPXString({ route, stickers, title: (title || address) }); const track = getGPXString({ route, stickers, title: title || address });
return downloadGPXTrack({ track, title }); return downloadGPXTrack({ track, title });
} }
function* mapsLoadMoreSaga() { function* mapsLoadMoreSaga() {
const { routes: { limit, list, shift, step, loading, filter } } = yield select(getState); const {
routes: { limit, list, shift, step, loading, filter }
} = yield select(getState);
if (loading || list.length >= limit || limit === 0) return; if (loading || list.length >= limit || limit === 0) return;
@ -638,73 +783,109 @@ function* mapsLoadMoreSaga() {
(filter.min > result.min && filter.distance[0] <= filter.min) || (filter.min > result.min && filter.distance[0] <= filter.min) ||
(filter.max < result.max && filter.distance[1] >= filter.max) (filter.max < result.max && filter.distance[1] >= filter.max)
) { ) {
yield put(searchChangeDistance([ yield put(
(filter.min > result.min && filter.distance[0] <= filter.min) searchChangeDistance([
? result.min filter.min > result.min && filter.distance[0] <= filter.min
: filter.distance[0], ? result.min
(filter.max < result.max && filter.distance[1] >= filter.max) : filter.distance[0],
? result.max filter.max < result.max && filter.distance[1] >= filter.max
: filter.distance[1], ? result.max
])); : filter.distance[1]
])
);
} }
yield put(searchPutRoutes({ ...result, list: [...list, ...result.list] })); yield put(searchPutRoutes({ ...result, list: [...list, ...result.list] }));
yield put(searchSetLoading(false)); yield put(searchSetLoading(false));
} }
function* dropRouteSaga({ _id }: ReturnType<typeof ActionCreators.dropRoute>): SagaIterator { function* dropRouteSaga({
const { id, token } = yield select(getUser); address
}: ReturnType<typeof ActionCreators.dropRoute>): SagaIterator {
const { token } = yield select(getUser);
const { const {
routes: { list, step, shift, limit, filter: { min, max } } routes: {
list,
step,
shift,
limit,
filter: { min, max }
}
} = yield select(getState); } = yield select(getState);
const index = list.findIndex(el => el._id === _id); const index = list.findIndex(el => el.address === address);
if (index >= 0) { if (index >= 0) {
yield put(searchPutRoutes({ yield put(
list: list.filter(el => el._id !== _id), searchPutRoutes({
min, list: list.filter(el => el.address !== address),
max, min,
step, max,
shift: (shift > 0) ? shift - 1 : 0, step,
limit: (limit > 0) ? limit - 1 : limit, shift: shift > 0 ? shift - 1 : 0,
})); limit: limit > 0 ? limit - 1 : limit
})
);
} }
return yield call(dropRoute, { address: _id, id, token }); return yield call(dropRoute, { address, token });
} }
function* modifyRouteSaga({ _id, title, is_public }: ReturnType<typeof ActionCreators.modifyRoute>): SagaIterator { function* modifyRouteSaga({
const { id, token } = yield select(getUser); address,
title,
is_public
}: ReturnType<typeof ActionCreators.modifyRoute>): SagaIterator {
const { token } = yield select(getUser);
const { const {
routes: { list, step, shift, limit, filter: { min, max } } routes: {
} = yield select(getState); list,
step,
shift,
limit,
filter: { min, max }
}
}: ReturnType<typeof getState> = yield select(getState);
const index = list.findIndex(el => el._id === _id); const index = list.findIndex(el => el.address === address);
if (index >= 0) { if (index >= 0) {
yield put(searchPutRoutes({ yield put(
list: list.map(el => (el._id !== _id ? el : { ...el, title, is_public })), searchPutRoutes({
min, list: list.map(el =>
max, el.address !== address ? el : { ...el, title, is_public }
step, ),
shift: (shift > 0) ? shift - 1 : 0, min,
limit: (limit > 0) ? limit - 1 : limit, max,
})); step,
shift: shift > 0 ? shift - 1 : 0,
limit: limit > 0 ? limit - 1 : limit
})
);
} }
return yield call(modifyRoute, { address: _id, id, token, title, is_public }); return yield call(modifyRoute, { address, token, title, is_public });
} }
function* toggleRouteStarredSaga({ _id }: ReturnType<typeof ActionCreators.toggleRouteStarred>) { function* toggleRouteStarredSaga({
const { routes: { list } } = yield select(getState); address
const route = list.find(el => el._id === _id); }: ReturnType<typeof ActionCreators.toggleRouteStarred>) {
const {
routes: { list }
}: IState["user"] = yield select(getState);
const route = list.find(el => el.address === address);
const { id, token } = yield select(getUser); const { id, token } = yield select(getUser);
yield put(setRouteStarred(_id, !route.is_starred)); yield put(setRouteStarred(address, !route.is_starred));
const result = yield sendRouteStarred({ id, token, _id, is_starred: !route.is_starred }); const result = yield sendRouteStarred({
id,
token,
address,
is_starred: !route.is_starred
});
if (!result) return yield put(setRouteStarred(_id, route.is_starred)); if (!result) return yield put(setRouteStarred(address, route.is_starred));
} }
export function* userSaga() { export function* userSaga() {
@ -720,12 +901,15 @@ export function* userSaga() {
yield takeEvery(ACTIONS.ROUTER_CANCEL, routerCancelSaga); yield takeEvery(ACTIONS.ROUTER_CANCEL, routerCancelSaga);
yield takeEvery(ACTIONS.ROUTER_SUBMIT, routerSubmitSaga); yield takeEvery(ACTIONS.ROUTER_SUBMIT, routerSubmitSaga);
yield takeEvery([ yield takeEvery(
ACTIONS.CLEAR_POLY, [
ACTIONS.CLEAR_STICKERS, ACTIONS.CLEAR_POLY,
ACTIONS.CLEAR_ALL, ACTIONS.CLEAR_STICKERS,
ACTIONS.CLEAR_CANCEL, ACTIONS.CLEAR_ALL,
], clearSaga); ACTIONS.CLEAR_CANCEL
],
clearSaga
);
yield takeLatest(ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga); yield takeLatest(ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga);
yield takeLatest(ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga); yield takeLatest(ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga);
@ -740,10 +924,10 @@ export function* userSaga() {
yield takeLatest(ACTIONS.SET_TITLE, setTitleSaga); yield takeLatest(ACTIONS.SET_TITLE, setTitleSaga);
yield takeLatest([ yield takeLatest(
ACTIONS.SEARCH_SET_TITLE, [ACTIONS.SEARCH_SET_TITLE, ACTIONS.SEARCH_SET_DISTANCE],
ACTIONS.SEARCH_SET_DISTANCE, searchSetSaga
], searchSetSaga); );
yield takeLatest(ACTIONS.OPEN_MAP_DIALOG, openMapDialogSaga); yield takeLatest(ACTIONS.OPEN_MAP_DIALOG, openMapDialogSaga);
yield takeLatest(ACTIONS.SEARCH_SET_TAB, searchSetTabSaga); yield takeLatest(ACTIONS.SEARCH_SET_TAB, searchSetTabSaga);

View file

@ -1,126 +1,222 @@
import axios, { AxiosPromise } from 'axios/index'; import axios, { AxiosPromise } from "axios/index";
import { API } from '$constants/api'; import { API } from "$constants/api";
import { IRootState } from "$redux/user/reducer"; import { IRootState, IRouteListItem, IRoute } from "$redux/user/reducer";
import { IUser } from "$constants/auth"; import { IUser } from "$constants/auth";
import { ILatLng } from "$modules/Stickers"; import { ILatLng } from "$modules/Stickers";
import { IStickerDump } from "$modules/Sticker"; import { IStickerDump } from "$modules/Sticker";
import { CLIENT } from '$config/frontend'; import { CLIENT } from "$config/frontend";
import { LatLngLiteral } from "leaflet"; import { LatLngLiteral } from "leaflet";
import {
resultMiddleware,
errorMiddleware,
IResultWithStatus,
configWithToken
} from "./middleware";
const arrayToObject = (array: any[], key: string): {} => array.reduce((obj, el) => ({ ...obj, [el[key]]: el }), {}); const arrayToObject = (array: any[], key: string): {} =>
array.reduce((obj, el) => ({ ...obj, [el[key]]: el }), {});
interface IPostMap {
title: IRootState['title'],
address: IRootState['address'],
route: Array<ILatLng>,
stickers: Array<IStickerDump>,
id: IRootState['user']['id'],
token: IRootState['user']['token'],
force: boolean,
logo: IRootState['logo'],
distance: IRootState['distance'],
provider: IRootState['provider'],
is_public: IRootState['is_public'],
description: IRootState['description'],
}
interface IGetRouteList { interface IGetRouteList {
title: IRootState['title'], min: number;
distance: IRootState['distance'], max: number;
author: IRootState['routes']['filter']['author'], tab: string;
step: IRootState['routes']['step'], search: string;
shift: IRootState['routes']['step'], step: IRootState["routes"]["step"];
starred: number, shift: IRootState["routes"]["step"];
id: IRootState['user']['id'], token: IRootState["user"]["token"];
token: IRootState['user']['token'],
} }
interface IGetRouteListResult { interface IGetRouteListResult {
min: IRootState['routes']['filter']['min'], min: IRootState["routes"]["filter"]["min"];
max: IRootState['routes']['filter']['max'], max: IRootState["routes"]["filter"]["max"];
limit: IRootState['routes']['limit'], limit: IRootState["routes"]["limit"];
step: IRootState['routes']['step'], step: IRootState["routes"]["step"];
shift: IRootState['routes']['shift'], shift: IRootState["routes"]["shift"];
list: IRootState['routes']['list'], list: IRootState["routes"]["list"];
} }
export const checkUserToken = ( export const checkUserToken = ({
{ id, token }:
{ id: IRootState['user']['id'], token: IRootState['user']['token']}
):AxiosPromise<IUser> => axios.get(API.CHECK_TOKEN, {
params: { id, token }
}).then(result => (result && result.data && {
...result.data,
id, id,
token, token
routes: (result.data.routes && result.data.routes.length > 0 && arrayToObject(result.data.routes, '_id')) || {}, }: {
})).catch(() => null); id: IRootState["user"]["id"];
token: IRootState["user"]["token"];
}): Promise<IResultWithStatus<{
user: IUser;
random_url: string;
routes: IRouteListItem[];
}>> =>
axios
.get(API.CHECK_TOKEN, {
params: { id, token }
})
.then(resultMiddleware)
.catch(errorMiddleware);
export const getGuestToken = ():AxiosPromise<IUser> => axios.get(API.GET_GUEST).then(result => (result && result.data)); export const getGuestToken = (): Promise<IResultWithStatus<{
user: IUser;
random_url: string;
}>> =>
axios
.get(API.GET_GUEST)
.then(resultMiddleware)
.catch(errorMiddleware);
export const getStoredMap = ( export const getStoredMap = ({
{ name }: { name: IRootState['address'] } name
) => axios.get(API.GET_MAP, { }: {
params: { name } name: IRootState["address"];
}) }): Promise<IResultWithStatus<{
.then(result => ( route: IRoute;
result && result.data && result.data.success && result.data error?: string;
)); random_url: string;
}>> =>
axios
.get(API.GET_MAP, {
params: { name }
})
.then(resultMiddleware)
.catch(errorMiddleware);
export const postMap = ({ export const postMap = ({
title, address, route, stickers, id, token, force, logo, distance, provider, is_public, description,
}: IPostMap) => axios.post(API.POST_MAP, {
title, title,
address, address,
route, route,
stickers, stickers,
id,
token,
force, force,
logo, logo,
distance, distance,
provider, provider,
is_public, is_public,
description, description,
}).then(result => (result && result.data && result.data)); token
}: Partial<IRoute> & { force: boolean; token: string }) =>
axios
.post(
API.POST_MAP,
{
title,
address,
route,
stickers,
force,
logo,
distance,
provider,
is_public,
description
},
configWithToken(token)
)
.then(result => result && result.data && result.data);
export const checkIframeToken = ( export const checkIframeToken = ({
{ viewer_id, auth_key }: viewer_id,
{ viewer_id: string, auth_key: string } auth_key
) => axios.get(API.IFRAME_LOGIN_VK, { }: {
params: { viewer_id, auth_key } viewer_id: string;
}).then(result => (result && result.data && result.data.success && result.data.user)).catch(() => (false)); auth_key: string;
}) =>
axios
.get(API.IFRAME_LOGIN_VK, {
params: { viewer_id, auth_key }
})
.then(
result => result && result.data && result.data.success && result.data.user
)
.catch(() => false);
export const getRouteList = ({ export const getRouteList = ({
title, distance, author, starred, id, token, step, shift, search,
}: IGetRouteList): AxiosPromise<IGetRouteListResult> => axios.get(API.GET_ROUTE_LIST, { min,
params: { max,
title, distance, author, starred, id, token, step, shift tab,
} token,
}).then(result => (result && result.data && result.data.success && result.data)) step,
.catch(() => ({ list: [], min: 0, max: 0, limit: 0, step: 20, shift: 20 })); shift
}: IGetRouteList): Promise<IResultWithStatus<{
routes: IRoute[];
limits: {
min: number;
max: number;
count: number;
};
filter: {
min: number;
max: number;
shift: number;
step: number;
};
}>> =>
axios
.get(
API.GET_ROUTE_LIST,
configWithToken(token, {
params: {
search,
min,
max,
tab,
token,
step,
shift
}
})
)
.then(resultMiddleware)
.catch(errorMiddleware);
export const checkOSRMService = (bounds: LatLngLiteral[]): Promise<boolean> => ( export const checkOSRMService = (bounds: LatLngLiteral[]): Promise<boolean> =>
CLIENT && CLIENT.OSRM_URL && axios.get(CLIENT.OSRM_TEST_URL(bounds)).then(() => true).catch(() => false) CLIENT &&
); CLIENT.OSRM_URL &&
axios
export const dropRoute = ({ address, id, token }: { address: string, id: string, token: string }): AxiosPromise<any> => ( .get(CLIENT.OSRM_TEST_URL(bounds))
axios.delete(API.DROP_ROUTE, { data: { address, id, token } })
);
export const modifyRoute = (
{ address, id, token, title, is_public }:
{ address: string, id: string, token: string, title: string, is_public: boolean }
): AxiosPromise<any> => (
axios.patch(API.DROP_ROUTE, { address, id, token, title, is_public })
);
export const sendRouteStarred = (
{ id, token, _id, is_starred }:
{ id: string, token: string, _id: string, is_starred: boolean }
): Promise<boolean> => (
axios.post(API.SET_STARRED, { id, token, address: _id, is_starred })
.then(() => true) .then(() => true)
.catch(() => true) .catch(() => false);
);
export const dropRoute = ({
address,
token
}: {
address: string;
token: string;
}): Promise<any> =>
axios
.delete(API.DROP_ROUTE, configWithToken(token, { data: { address } }))
.then(resultMiddleware)
.catch(errorMiddleware);
export const modifyRoute = ({
address,
token,
title,
is_public
}: {
address: string;
token: string;
title: string;
is_public: boolean;
}): Promise<IResultWithStatus<{
route: IRoute;
}>> =>
axios.patch(
API.MODIFY_ROUTE,
{ address, token, is_public, title },
configWithToken(token)
);
export const sendRouteStarred = ({
id,
token,
address,
is_starred
}: {
id: string;
token: string;
address: string;
is_starred: boolean;
}): Promise<IResultWithStatus<{ route: IRoute }>> =>
axios
.post(API.SET_STARRED, { id, token, address, is_starred })
.then(resultMiddleware)
.catch(errorMiddleware);

50
src/utils/middleware.ts Normal file
View file

@ -0,0 +1,50 @@
import { AxiosRequestConfig } from "axios";
export type Unwrap<T> = T extends (...args: any[]) => Promise<infer U> ? U : T;
export interface IApiErrorResult {
detail?: string;
code?: string;
}
export interface IResultWithStatus<T> {
status: any;
data?: Partial<T> & IApiErrorResult;
error?: string;
debug?: string;
}
export const HTTP_RESPONSES = {
SUCCESS: 200,
CREATED: 201,
CONNECTION_REFUSED: 408,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
NOT_FOUND: 404,
TOO_MANY_REQUESTS: 429,
};
export const resultMiddleware = (<T extends {}>({
status,
data,
}: {
status: number;
data: T;
}): { status: number; data: T } => ({ status, data }));
export const errorMiddleware = <T extends any>(debug): IResultWithStatus<T> => (debug && debug.response
? debug.response
: {
status: HTTP_RESPONSES.CONNECTION_REFUSED,
data: {},
debug,
error: 'Ошибка сети',
});
export const configWithToken = (
token: string,
config: AxiosRequestConfig = {},
): AxiosRequestConfig => ({
...config,
headers: { ...(config.headers || {}), Authorization: `${token}` },
});