diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 7aa4aa0..356833c 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -8,7 +8,7 @@ const iframe_vk = require('./auth/iframe/vk'); const router = express.Router(); router.get('/', check); -router.get('/list', list); +// router.get('/list', list); router.get('/guest', guest); router.get('/social/vk', vk); router.get('/iframe/vk', iframe_vk); diff --git a/backend/routes/auth/check.js b/backend/routes/auth/check.js index 76c07f3..fe74a0a 100644 --- a/backend/routes/auth/check.js +++ b/backend/routes/auth/check.js @@ -5,14 +5,6 @@ module.exports = async (req, res) => { const { id, token } = req.query; 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(); diff --git a/backend/routes/auth/social/vk.js b/backend/routes/auth/social/vk.js index 2a518ba..47cfd61 100644 --- a/backend/routes/auth/social/vk.js +++ b/backend/routes/auth/social/vk.js @@ -16,7 +16,7 @@ const fetchUserData = async (req, res) => { client_id: CONFIG.SOCIAL.VK.APP_ID, client_secret: CONFIG.SOCIAL.VK.SECRET, code, - redirect_uri: `${proto}://${host}/auth/social/vk`, + redirect_uri: `${proto}://${host}/api/auth/social/vk`, } } ).catch(() => { diff --git a/src/components/dialogs/MapListDialog.tsx b/src/components/dialogs/MapListDialog.tsx index 872db3c..5a30c87 100644 --- a/src/components/dialogs/MapListDialog.tsx +++ b/src/components/dialogs/MapListDialog.tsx @@ -40,8 +40,8 @@ export interface IMapListDialogProps extends IRootState { } export interface IMapListDialogState { - menu_target: IRouteListItem['_id'], - editor_target: IRouteListItem['_id'], + menu_target: IRouteListItem['address'], + editor_target: IRouteListItem['address'], is_editing: boolean, is_dropping: boolean, @@ -56,14 +56,14 @@ class Component extends React.Component this.setState({ + startEditing = (editor_target: IRouteListItem['address']): void => this.setState({ editor_target, menu_target: null, is_editing: true, is_dropping: false, }); - showMenu = (menu_target: IRouteListItem['_id']): void => this.setState({ + showMenu = (menu_target: IRouteListItem['address']): void => this.setState({ menu_target, }); @@ -71,7 +71,7 @@ class Component extends React.Component this.setState({ + showDropCard = (editor_target: IRouteListItem['address']): void => this.setState({ editor_target, menu_target: null, is_editing: false, @@ -211,13 +211,13 @@ class Component extends React.Component )) diff --git a/src/components/maps/RouteRowView.tsx b/src/components/maps/RouteRowView.tsx index 8bbb75f..42027dd 100644 --- a/src/components/maps/RouteRowView.tsx +++ b/src/components/maps/RouteRowView.tsx @@ -30,7 +30,7 @@ export const RouteRowView = ({ title, distance, _id, openRoute, tab, startEditing, showMenu, showDropCard, hideMenu, is_admin, is_starred, toggleStarred, }: Props): ReactElement => (
{ (tab === 'all' || tab === 'starred') && is_admin && @@ -48,7 +48,7 @@ export const RouteRowView = ({ >
{ - (tab === 'mine' || !is_admin) && is_starred && + (tab === 'my' || !is_admin) && is_starred &&
} @@ -68,7 +68,7 @@ export const RouteRowView = ({
{ - tab === 'mine' && + tab === 'my' &&
{ setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened }); openMapsDialog = () => { - this.props.openMapDialog('mine'); + this.props.openMapDialog('my'); }; openAppInfoDialog = () => { @@ -81,7 +81,7 @@ export class Component extends React.PureComponent { const left = (width - 700) / 2; 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', `location=no,width=700,height=370,scrollbars=no,top=${top},left=${left},resizable=no` ); diff --git a/src/components/user/UserButton.tsx b/src/components/user/UserButton.tsx index 2c5e26b..e444e00 100644 --- a/src/components/user/UserButton.tsx +++ b/src/components/user/UserButton.tsx @@ -1,28 +1,24 @@ // @flow -import * as React from 'react'; -import { UserPicture } from '$components/user/UserPicture'; +import * as React from "react"; +import { UserPicture } from "$components/user/UserPicture"; import { IUser } from "$constants/auth"; interface Props { - user: IUser, - setMenuOpened: () => void, + user: IUser; + setMenuOpened: () => void; } export const UserButton = ({ setMenuOpened, - user: { - id, - photo, - first_name, - } + user: { uid, photo, name } }: Props) => (
-
{(first_name || id || '...')}
-
{(id || 'пользователь')}
+
{name || uid || "..."}
+
{uid || "пользователь"}
diff --git a/src/constants/api.ts b/src/constants/api.ts index 4771533..fa343c7 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -1,16 +1,16 @@ import { CLIENT } from '$config/frontend'; export const API: { [x: string]: string } = { - GET_GUEST: `${CLIENT.API_ADDR}/auth`, - CHECK_TOKEN: `${CLIENT.API_ADDR}/auth`, - IFRAME_LOGIN_VK: `${CLIENT.API_ADDR}/auth/iframe/vk`, - GET_MAP: `${CLIENT.API_ADDR}/route`, - POST_MAP: `${CLIENT.API_ADDR}/route`, - GET_ROUTE_LIST: `${CLIENT.API_ADDR}/route/list`, + GET_GUEST: `${CLIENT.API_ADDR}/api/auth/`, + CHECK_TOKEN: `${CLIENT.API_ADDR}/api/auth/`, + IFRAME_LOGIN_VK: `${CLIENT.API_ADDR}/api/auth/vk/`, + GET_MAP: `${CLIENT.API_ADDR}/api/route/`, + POST_MAP: `${CLIENT.API_ADDR}/api/route/`, + GET_ROUTE_LIST: `${CLIENT.API_ADDR}/api/route/list/`, - DROP_ROUTE: `${CLIENT.API_ADDR}/route`, - MODIFY_ROUTE: `${CLIENT.API_ADDR}/route/modify`, - SET_STARRED: `${CLIENT.API_ADDR}/route/star`, + DROP_ROUTE: `${CLIENT.API_ADDR}/api/route/`, + MODIFY_ROUTE: `${CLIENT.API_ADDR}/api/route/`, + SET_STARRED: `${CLIENT.API_ADDR}/api/route/publish/`, }; export const API_RETRY_INTERVAL = 10; diff --git a/src/constants/auth.ts b/src/constants/auth.ts index 2b1e41e..6f44ea0 100644 --- a/src/constants/auth.ts +++ b/src/constants/auth.ts @@ -1,42 +1,39 @@ export interface IRoles { - guest: string, - vk: string, - admin: string, + guest: string; + vk: string; + admin: string; } export interface IUser { - new_messages: number, - place_types: {}, - random_url: string, - role: IRoles[keyof IRoles], - routes: {}, - success: boolean, - id?: string, - token?: string, - photo: string, - first_name: string, - // userdata: { - // name: string, - // agent: string, - // ip: string, - // } + new_messages: number; + place_types: {}; + random_url: string; + role: IRoles[keyof IRoles]; + routes: {}; + success: boolean; + id?: number; + uid: string; + token?: string; + photo: string; + name: string; } export const ROLES: IRoles = { - guest: 'guest', - vk: 'vk', - admin: 'admin', + guest: "guest", + vk: "vk", + admin: "admin" }; export const DEFAULT_USER: IUser = { new_messages: 0, place_types: {}, - random_url: '', + random_url: "", role: ROLES.guest, routes: {}, success: false, id: null, token: null, photo: null, - first_name: null, + name: null, + uid: null, }; diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts index 17b08bb..93cec51 100644 --- a/src/constants/dialogs.ts +++ b/src/constants/dialogs.ts @@ -5,7 +5,7 @@ export interface IDialogs { } export interface IMapTabs { - mine: string, + my: string, all: string, starred: string, } @@ -17,7 +17,7 @@ export const DIALOGS: IDialogs = ({ }); export const TABS: IMapTabs = ({ - mine: 'Мои', + my: 'Мои', all: 'Заявки', starred: 'Каталог', }); diff --git a/src/constants/stickers.ts b/src/constants/stickers.ts index 44e02c9..0652928 100644 --- a/src/constants/stickers.ts +++ b/src/constants/stickers.ts @@ -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 { base: IStickerPack, real: IStickerPack, diff --git a/src/modules/Editor.ts b/src/modules/Editor.ts index 77af4c1..7f97c62 100644 --- a/src/modules/Editor.ts +++ b/src/modules/Editor.ts @@ -34,11 +34,11 @@ interface IEditor { router: Router; logo: keyof ILogos; - owner: { id: string }; + owner: number; initialData: { version: number, title: IRootState['title'], - owner: { id: string }, + owner: number, address: IRootState['address'], path: any, route: any, @@ -322,11 +322,11 @@ export class Editor { is_public, is_starred, description, - }: IEditor['initialData']): void => { + }: Partial): void => { this.setTitle(title || ''); const { id } = this.getUser(); - if (address && id && owner && id === owner.id) { + if (address && id && owner && id === owner) { this.setAddress(address); } @@ -389,7 +389,7 @@ export class Editor { version: 2, title: this.getTitle(), owner: this.owner, - address: (this.owner.id === id ? path : null), + address: (this.owner === id ? path : null), path, route, stickers, @@ -405,7 +405,7 @@ export class Editor { const { id } = this.getUser(); this.setInitialData(); - this.owner = { id }; + this.owner = id; this.poly.enableEditor(); this.stickers.startEditing(); diff --git a/src/redux/store.ts b/src/redux/store.ts index e7aba7f..1a5d11a 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -4,7 +4,7 @@ import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; 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 { createBrowserHistory } from 'history'; import { locationChanged } from '$redux/user/actions'; @@ -16,6 +16,9 @@ const userPersistConfig: PersistConfig = { storage, }; +export interface IState { + user: IRootReducer +} // create the saga middleware export const sagaMiddleware = createSagaMiddleware(); diff --git a/src/redux/user/actions.ts b/src/redux/user/actions.ts index ae42b87..b98c392 100644 --- a/src/redux/user/actions.ts +++ b/src/redux/user/actions.ts @@ -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 setIsRouting = (is_routing: boolean) => ({ type: ACTIONS.SET_IS_ROUTING, is_routing }); -export const dropRoute = (_id: string) => ({ type: ACTIONS.DROP_ROUTE, _id }); -export const modifyRoute = (_id: string, { title, is_public }: { title: string, is_public: boolean }) => ({ - type: ACTIONS.MODIFY_ROUTE, _id, title, is_public +export const dropRoute = (address: string) => ({ type: ACTIONS.DROP_ROUTE, address }); +export const modifyRoute = (address: string, { title, is_public }: { title: string, is_public: boolean }) => ({ + type: ACTIONS.MODIFY_ROUTE, address, title, is_public }); -export const toggleRouteStarred = (_id: string) => ({ type: ACTIONS.TOGGLE_ROUTE_STARRED, _id }); -export const setRouteStarred = (_id: string, is_starred: boolean) => ({ type: ACTIONS.SET_ROUTE_STARRED, _id, is_starred }); +export const toggleRouteStarred = (address: string) => ({ type: ACTIONS.TOGGLE_ROUTE_STARRED, address }); +export const setRouteStarred = (address: string, is_starred: boolean) => ({ type: ACTIONS.SET_ROUTE_STARRED, address, is_starred }); diff --git a/src/redux/user/reducer.ts b/src/redux/user/reducer.ts index 00d2aaa..335ce6a 100644 --- a/src/redux/user/reducer.ts +++ b/src/redux/user/reducer.ts @@ -7,10 +7,27 @@ 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"; +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 { - _id: string, + address: string, title: string, distance: number, is_public: boolean, @@ -318,13 +335,13 @@ const setIsRouting: ActionHandler = (state, is_routing, }); -const setRouteStarred: ActionHandler = (state, { _id, is_starred }) => ({ +const setRouteStarred: ActionHandler = (state, { address, is_starred }) => ({ ...state, routes: { ...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 => ( (state.routes.filter.tab === 'starred' && el.is_starred) || (state.routes.filter.tab === 'all' && !el.is_starred) diff --git a/src/redux/user/sagas.ts b/src/redux/user/sagas.ts index c5717db..d48b9ae 100644 --- a/src/redux/user/sagas.ts +++ b/src/redux/user/sagas.ts @@ -1,13 +1,27 @@ -import { REHYDRATE } from 'redux-persist'; -import { delay, SagaIterator } from 'redux-saga'; -import { takeLatest, select, call, put, takeEvery, race, take } from 'redux-saga/effects'; +import { REHYDRATE } from "redux-persist"; +import { delay, SagaIterator } from "redux-saga"; import { - checkIframeToken, checkOSRMService, - checkUserToken, dropRoute, - getGuestToken, getRouteList, - getStoredMap, modifyRoute, - postMap, sendRouteStarred -} from '$utils/api'; + takeLatest, + select, + call, + put, + takeEvery, + race, + take, + TakeEffect +} from "redux-saga/effects"; +import { + checkIframeToken, + checkOSRMService, + checkUserToken, + dropRoute, + getGuestToken, + getRouteList, + getStoredMap, + modifyRoute, + postMap, + sendRouteStarred +} from "$utils/api"; import { hideRenderer, searchPutRoutes, @@ -32,54 +46,79 @@ import { setProvider, changeProvider, setSaveLoading, - mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, setRouteStarred, setDescription, -} from '$redux/user/actions'; -import { 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 } from '$constants/auth'; -import { TIPS } from '$constants/tips'; + mapsSetShift, + searchChangeDistance, + clearAll, + setFeature, + searchSetTitle, + setRouteStarred, + setDescription +} from "$redux/user/actions"; 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, - composePoly, composeStickers, downloadCanvas, + composePoly, + composeStickers, + downloadCanvas, fetchImages, - getPolyPlacement, getStickersPlacement, + getPolyPlacement, + getStickersPlacement, getTilePlacement, imageFetcher -} from '$utils/renderer'; -import { ILogos, LOGOS } from '$constants/logos'; -import { DEFAULT_PROVIDER } from '$constants/providers'; -import { DIALOGS, TABS } from '$constants/dialogs'; +} from "$utils/renderer"; +import { LOGOS } from "$constants/logos"; +import { DEFAULT_PROVIDER } from "$constants/providers"; +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 { downloadGPXTrack, getGPXString } from "$utils/gpx"; +import { Unwrap } from "$utils/middleware"; +import { IState } from "$redux/store"; -const getUser = state => (state.user.user); -const getState = state => (state.user); +const getUser = (state: IState) => state.user.user; +const getState = (state: IState) => state.user; const hideLoader = () => { - document.getElementById('loader').style.opacity = String(0); - document.getElementById('loader').style.pointerEvents = 'none'; + document.getElementById("loader").style.opacity = String(0); + document.getElementById("loader").style.pointerEvents = "none"; return true; }; function* generateGuestSaga() { - const user = yield call(getGuestToken); - yield put(setUser(user)); + const { + data: { user, random_url } + }: Unwrap = yield call(getGuestToken); - return user; + yield put(setUser({ ...user, random_url })); + + return { ...user, random_url }; } 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`); - editor.owner = { id }; + editor.owner = id; editor.setProvider(provider); editor.startEditing(); @@ -95,9 +134,7 @@ function* startEditingSaga() { } function* stopEditingSaga() { - const { - changed, editing, mode, address_origin - } = yield select(getState); + const { changed, editing, mode, address_origin } = yield select(getState); const { path } = getUrlData(); if (!editing) return; @@ -111,22 +148,24 @@ function* stopEditingSaga() { yield put(setChanged(false)); 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) { - const map = yield call(getStoredMap, { name: path }); + const { data: { route, error, random_url } }: Unwrap = yield call(getStoredMap, { name: path }); - if (map) { + if (route && !error) { yield editor.clearAll(); - yield editor.setData(map); + yield editor.setData(route); yield editor.fitDrawing(); yield editor.setInitialData(); yield put(setChanged(false)); + + return { route, random_url }; } - return map; + return null } function* replaceAddressIfItsBusy(destination, original) { @@ -150,14 +189,17 @@ function* setReadySaga() { hideLoader(); yield call(checkOSRMServiceSaga); - yield put(searchSetTab('mine')); + yield put(searchSetTab("my")); } function* mapInitSaga() { pushLoaderState(90); const { path, mode, hash } = getUrlData(); - const { provider, user: { id } } = yield select(getState); + const { + provider, + user: { id } + } = yield select(getState); editor.map.setProvider(provider); yield put(changeProvider(provider)); @@ -174,13 +216,13 @@ function* mapInitSaga() { if (path) { const map = yield call(loadMapSaga, path); - if (map) { - if (mode && mode === 'edit') { - if (map && map.owner && mode === 'edit' && map.owner.id !== id) { + if (map && map.route) { + if (mode && mode === "edit") { + if (map && map.route && map.route.owner && mode === "edit" && map.route.owner !== id) { yield call(setReadySaga); yield call(replaceAddressIfItsBusy, map.random_url, map.address); } else { - yield put(setAddressOrigin('')); + yield put(setAddressOrigin("")); } yield put(setEditing(true)); @@ -226,10 +268,15 @@ function* authCheckSaga() { } if (id && token) { - const user = yield call(checkUserToken, { id, token }); + const { + data: { user, random_url } + }: Unwrap = yield call(checkUserToken, { + id, + token + }); if (user) { - yield put(setUser(user)); + yield put(setUser({ ...user, random_url })); pushLoaderState(99); @@ -251,14 +298,19 @@ function* setModeSaga({ mode }: ReturnType) { // console.log('change', mode); } -function* setActiveStickerSaga({ activeSticker }: { type: string, activeSticker: IRootState['activeSticker'] }) { - yield editor.activeSticker = activeSticker; +function* setActiveStickerSaga({ + activeSticker +}: { + type: string; + activeSticker: IRootState["activeSticker"]; +}) { + yield (editor.activeSticker = activeSticker); yield put(setMode(MODES.STICKERS)); return true; } -function* setLogoSaga({ logo }: { type: string, logo: string }) { +function* setLogoSaga({ logo }: { type: string; logo: string }) { const { mode } = yield select(getState); editor.logo = logo; @@ -299,7 +351,8 @@ function* clearSaga({ type }) { yield put(setChanged(false)); break; - default: break; + default: + break; } yield put(setActiveSticker(null)); @@ -307,48 +360,75 @@ function* clearSaga({ type }) { } function* sendSaveRequestSaga({ - title, address, force, is_public, description, + title, + address, + force, + is_public, + description }: ReturnType) { if (editor.isEmpty) return yield put(setSaveError(TIPS.SAVE_EMPTY)); const { route, stickers, provider } = editor.dumpData(); const { logo, distance } = yield select(getState); - const { id, token } = yield select(getUser); + const { token } = yield select(getUser); yield put(setSaveLoading(true)); - const { result, timeout, cancel } = yield race({ + const { + result, + timeout, + cancel + }: { + result: Unwrap; + timeout: boolean; + cancel: TakeEffect; + } = yield race({ 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), - cancel: take(ACTIONS.RESET_SAVE_DIALOG), + cancel: take(ACTIONS.RESET_SAVE_DIALOG) }); yield put(setSaveLoading(false)); if (cancel) return yield put(setMode(MODES.NONE)); - if (result && result.mode === 'overwriting') return yield put(setSaveOverwrite()); - 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)); + if (result && result.mode === "overwriting") + return yield put(setSaveOverwrite()); + 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({ - address: result.address, - title: result.title, - is_public: result.is_public, - description: result.description, + return yield put( + setSaveSuccess({ + address: result.address, + title: result.title, + is_public: result.is_public, + description: result.description, - save_error: TIPS.SAVE_SUCCESS, - })); + save_error: TIPS.SAVE_SUCCESS + }) + ); } function* getRenderData() { - yield put(setRenderer({ info: 'Загрузка тайлов', progress: 0.1 })); + yield put(setRenderer({ info: "Загрузка тайлов", progress: 0.1 })); - const canvas = document.getElementById('renderer'); + const canvas = document.getElementById("renderer"); canvas.width = window.innerWidth; canvas.height = window.innerHeight; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext("2d"); const geometry = getTilePlacement(); const points = getPolyPlacement(); @@ -359,7 +439,7 @@ function* getRenderData() { 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 composePoly({ points, ctx }); @@ -367,9 +447,9 @@ function* getRenderData() { yield composeDistMark({ ctx, points, distance }); 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() { @@ -377,50 +457,62 @@ function* takeAShotSaga() { const { result, timeout } = yield race({ result: worker, - timeout: delay(500), + timeout: delay(500) }); 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(setRenderer({ - data, renderer_active: true, width: window.innerWidth, height: window.innerHeight - })); + yield put( + setRenderer({ + data, + renderer_active: true, + width: window.innerWidth, + height: window.innerHeight + }) + ); } -function* getCropData({ - x, y, width, height -}) { - const { logo, renderer: { data } } = yield select(getState); - const canvas = document.getElementById('renderer'); +function* getCropData({ x, y, width, height }) { + const { + logo, + renderer: { data } + } = yield select(getState); + const canvas = document.getElementById("renderer"); canvas.width = width; canvas.height = height; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext("2d"); const image = yield imageFetcher(data); ctx.drawImage(image, -x, -y); if (logo && 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) { const { title, address } = yield select(getState); 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()); } -function* changeProviderSaga({ provider }: ReturnType) { +function* changeProviderSaga({ + provider +}: ReturnType) { const { provider: current_provider } = yield select(getState); yield put(setProvider(provider)); @@ -435,8 +527,15 @@ function* changeProviderSaga({ provider }: ReturnType) { - const { address, ready, user: { id, random_url }, is_public } = yield select(getState); +function* locationChangeSaga({ + location +}: ReturnType) { + const { + address, + ready, + user: { id, random_url }, + is_public + } = yield select(getState); if (!ready) return; @@ -445,16 +544,16 @@ function* locationChangeSaga({ location }: ReturnType) { - const data = yield call(checkUserToken, user); - yield put(setUser(data)); +function* gotVkUserSaga({ + user: u +}: ReturnType) { + const { + data: { user, random_url } + }: Unwrap = yield call(checkUserToken, u); + + yield put(setUser({ ...user, random_url })); } -function* keyPressedSaga({ key, target }: ReturnType): any { - if ( - target === 'INPUT' || - target === 'TEXTAREA' - ) { +function* keyPressedSaga({ + key, + target +}: ReturnType): any { + if (target === "INPUT" || target === "TEXTAREA") { return; } - if (key === 'Escape') { - const { dialog_active, mode, renderer: { renderer_active } } = yield select(getState); + if (key === "Escape") { + const { + dialog_active, + mode, + renderer: { renderer_active } + } = yield select(getState); if (renderer_active) return yield put(hideRenderer()); if (dialog_active) return yield put(setDialogActive(false)); if (mode !== MODES.NONE) return yield put(setMode(MODES.NONE)); - } else if (key === 'Delete') { - const { user: { editing } } = yield select(); + } else if (key === "Delete") { + const { + user: { editing } + } = yield select(); if (!editing) return; @@ -494,47 +604,63 @@ function* keyPressedSaga({ key, target }: ReturnType = yield getRouteList({ token, - title, - distance, + search: title, + min: distance[0], + max: distance[1], step, shift, - author: tab === 'mine' ? id : '', - starred: tab === 'starred' ? 1 : 0, + tab }); + + return result; } function* searchSetSagaWorker() { - const { routes: { filter } } = yield select(getState); + const { + routes: { filter } + }: ReturnType = 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 = 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 if ( (filter.min > min && filter.distance[0] <= filter.min) || (filter.max < max && filter.distance[1] >= filter.max) ) { - yield put(searchChangeDistance([ - (filter.min > min && filter.distance[0] <= filter.min) - ? min - : filter.distance[0], - (filter.max < max && filter.distance[1] >= filter.max) - ? max - : filter.distance[1], - ])); + yield put( + searchChangeDistance([ + filter.min > min && filter.distance[0] <= filter.min + ? min + : filter.distance[0], + filter.max < max && filter.distance[1] >= filter.max + ? max + : filter.distance[1] + ]) + ); } return yield put(searchSetLoading(false)); @@ -547,14 +673,22 @@ function* searchSetSaga() { yield call(searchSetSagaWorker); } -function* openMapDialogSaga({ tab }: ReturnType) { - const { dialog_active, routes: { filter: { tab: current } } } = yield select(getState); +function* openMapDialogSaga({ + tab +}: ReturnType) { + const { + dialog_active, + routes: { + filter: { tab: current } + } + } = yield select(getState); if (dialog_active && tab === current) { 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)); } @@ -566,13 +700,18 @@ function* openMapDialogSaga({ tab }: ReturnType) { const { id } = yield select(getUser); const { dialog_active } = yield select(getState); @@ -585,7 +724,7 @@ function* setSaveSuccessSaga({ yield put(setDescription(description)); yield put(setChanged(false)); - yield editor.owner = { id }; + editor.owner = id; if (dialog_active) { yield call(searchSetSagaWorker); @@ -594,7 +733,7 @@ function* setSaveSuccessSaga({ return yield editor.setInitialData(); } -function* userLogoutSaga():SagaIterator { +function* userLogoutSaga(): SagaIterator { yield put(setUser(DEFAULT_USER)); yield call(generateGuestSaga); } @@ -607,8 +746,12 @@ function* setUserSaga() { return true; } -function* setTitleSaga({ title }: ReturnType):SagaIterator { - if (title) { document.title = `${title} | Редактор маршрутов`; } +function* setTitleSaga({ + title +}: ReturnType): SagaIterator { + if (title) { + document.title = `${title} | Редактор маршрутов`; + } } function* getGPXTrackSaga(): SagaIterator { @@ -617,13 +760,15 @@ function* getGPXTrackSaga(): SagaIterator { 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 }); } 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; @@ -638,73 +783,109 @@ function* mapsLoadMoreSaga() { (filter.min > result.min && filter.distance[0] <= filter.min) || (filter.max < result.max && filter.distance[1] >= filter.max) ) { - yield put(searchChangeDistance([ - (filter.min > result.min && filter.distance[0] <= filter.min) - ? result.min - : filter.distance[0], - (filter.max < result.max && filter.distance[1] >= filter.max) - ? result.max - : filter.distance[1], - ])); + yield put( + searchChangeDistance([ + filter.min > result.min && filter.distance[0] <= filter.min + ? result.min + : filter.distance[0], + filter.max < result.max && filter.distance[1] >= filter.max + ? result.max + : filter.distance[1] + ]) + ); } yield put(searchPutRoutes({ ...result, list: [...list, ...result.list] })); yield put(searchSetLoading(false)); } -function* dropRouteSaga({ _id }: ReturnType): SagaIterator { - const { id, token } = yield select(getUser); +function* dropRouteSaga({ + address +}: ReturnType): SagaIterator { + const { token } = yield select(getUser); const { - routes: { list, step, shift, limit, filter: { min, max } } + routes: { + list, + step, + shift, + limit, + filter: { min, max } + } } = yield select(getState); - const index = list.findIndex(el => el._id === _id); + const index = list.findIndex(el => el.address === address); if (index >= 0) { - yield put(searchPutRoutes({ - list: list.filter(el => el._id !== _id), - min, - max, - step, - shift: (shift > 0) ? shift - 1 : 0, - limit: (limit > 0) ? limit - 1 : limit, - })); + yield put( + searchPutRoutes({ + list: list.filter(el => el.address !== address), + min, + max, + step, + 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): SagaIterator { - const { id, token } = yield select(getUser); +function* modifyRouteSaga({ + address, + title, + is_public +}: ReturnType): SagaIterator { + const { token } = yield select(getUser); const { - routes: { list, step, shift, limit, filter: { min, max } } - } = yield select(getState); + routes: { + list, + step, + shift, + limit, + filter: { min, max } + } + }: ReturnType = yield select(getState); - const index = list.findIndex(el => el._id === _id); + const index = list.findIndex(el => el.address === address); if (index >= 0) { - yield put(searchPutRoutes({ - list: list.map(el => (el._id !== _id ? el : { ...el, title, is_public })), - min, - max, - step, - shift: (shift > 0) ? shift - 1 : 0, - limit: (limit > 0) ? limit - 1 : limit, - })); + yield put( + searchPutRoutes({ + list: list.map(el => + el.address !== address ? el : { ...el, title, is_public } + ), + min, + 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) { - const { routes: { list } } = yield select(getState); - const route = list.find(el => el._id === _id); +function* toggleRouteStarredSaga({ + address +}: ReturnType) { + const { + routes: { list } + }: IState["user"] = yield select(getState); + + const route = list.find(el => el.address === address); const { id, token } = yield select(getUser); - yield put(setRouteStarred(_id, !route.is_starred)); - const result = yield sendRouteStarred({ id, token, _id, is_starred: !route.is_starred }); + yield put(setRouteStarred(address, !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() { @@ -720,12 +901,15 @@ export function* userSaga() { yield takeEvery(ACTIONS.ROUTER_CANCEL, routerCancelSaga); yield takeEvery(ACTIONS.ROUTER_SUBMIT, routerSubmitSaga); - yield takeEvery([ - ACTIONS.CLEAR_POLY, - ACTIONS.CLEAR_STICKERS, - ACTIONS.CLEAR_ALL, - ACTIONS.CLEAR_CANCEL, - ], clearSaga); + yield takeEvery( + [ + ACTIONS.CLEAR_POLY, + ACTIONS.CLEAR_STICKERS, + ACTIONS.CLEAR_ALL, + ACTIONS.CLEAR_CANCEL + ], + clearSaga + ); yield takeLatest(ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga); yield takeLatest(ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga); @@ -740,10 +924,10 @@ export function* userSaga() { yield takeLatest(ACTIONS.SET_TITLE, setTitleSaga); - yield takeLatest([ - ACTIONS.SEARCH_SET_TITLE, - ACTIONS.SEARCH_SET_DISTANCE, - ], searchSetSaga); + yield takeLatest( + [ACTIONS.SEARCH_SET_TITLE, ACTIONS.SEARCH_SET_DISTANCE], + searchSetSaga + ); yield takeLatest(ACTIONS.OPEN_MAP_DIALOG, openMapDialogSaga); yield takeLatest(ACTIONS.SEARCH_SET_TAB, searchSetTabSaga); diff --git a/src/utils/api.ts b/src/utils/api.ts index d8528a6..269cd29 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,126 +1,222 @@ -import axios, { AxiosPromise } from 'axios/index'; -import { API } from '$constants/api'; -import { IRootState } from "$redux/user/reducer"; +import axios, { AxiosPromise } from "axios/index"; +import { API } from "$constants/api"; +import { IRootState, IRouteListItem, IRoute } from "$redux/user/reducer"; import { IUser } from "$constants/auth"; import { ILatLng } from "$modules/Stickers"; import { IStickerDump } from "$modules/Sticker"; -import { CLIENT } from '$config/frontend'; +import { CLIENT } from "$config/frontend"; 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 }), {}); - -interface IPostMap { - title: IRootState['title'], - address: IRootState['address'], - route: Array, - stickers: Array, - 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'], -} +const arrayToObject = (array: any[], key: string): {} => + array.reduce((obj, el) => ({ ...obj, [el[key]]: el }), {}); interface IGetRouteList { - title: IRootState['title'], - distance: IRootState['distance'], - author: IRootState['routes']['filter']['author'], - step: IRootState['routes']['step'], - shift: IRootState['routes']['step'], - starred: number, - id: IRootState['user']['id'], - token: IRootState['user']['token'], + min: number; + max: number; + tab: string; + search: string; + step: IRootState["routes"]["step"]; + shift: IRootState["routes"]["step"]; + token: IRootState["user"]["token"]; } interface IGetRouteListResult { - min: IRootState['routes']['filter']['min'], - max: IRootState['routes']['filter']['max'], - limit: IRootState['routes']['limit'], - step: IRootState['routes']['step'], - shift: IRootState['routes']['shift'], - list: IRootState['routes']['list'], + min: IRootState["routes"]["filter"]["min"]; + max: IRootState["routes"]["filter"]["max"]; + limit: IRootState["routes"]["limit"]; + step: IRootState["routes"]["step"]; + shift: IRootState["routes"]["shift"]; + list: IRootState["routes"]["list"]; } -export const checkUserToken = ( - { id, token }: - { id: IRootState['user']['id'], token: IRootState['user']['token']} -):AxiosPromise => axios.get(API.CHECK_TOKEN, { - params: { id, token } -}).then(result => (result && result.data && { - ...result.data, +export const checkUserToken = ({ id, - token, - routes: (result.data.routes && result.data.routes.length > 0 && arrayToObject(result.data.routes, '_id')) || {}, -})).catch(() => null); + token +}: { + id: IRootState["user"]["id"]; + token: IRootState["user"]["token"]; +}): Promise> => + axios + .get(API.CHECK_TOKEN, { + params: { id, token } + }) + .then(resultMiddleware) + .catch(errorMiddleware); -export const getGuestToken = ():AxiosPromise => axios.get(API.GET_GUEST).then(result => (result && result.data)); +export const getGuestToken = (): Promise> => + axios + .get(API.GET_GUEST) + .then(resultMiddleware) + .catch(errorMiddleware); -export const getStoredMap = ( - { name }: { name: IRootState['address'] } -) => axios.get(API.GET_MAP, { - params: { name } -}) - .then(result => ( - result && result.data && result.data.success && result.data - )); +export const getStoredMap = ({ + name +}: { + name: IRootState["address"]; +}): Promise> => + axios + .get(API.GET_MAP, { + params: { name } + }) + .then(resultMiddleware) + .catch(errorMiddleware); export const postMap = ({ - title, address, route, stickers, id, token, force, logo, distance, provider, is_public, description, -}: IPostMap) => axios.post(API.POST_MAP, { title, address, route, stickers, - id, - token, force, logo, distance, provider, is_public, description, -}).then(result => (result && result.data && result.data)); + token +}: Partial & { 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 = ( - { viewer_id, auth_key }: - { viewer_id: string, 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 checkIframeToken = ({ + viewer_id, + auth_key +}: { + viewer_id: string; + 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 = ({ - title, distance, author, starred, id, token, step, shift, -}: IGetRouteList): AxiosPromise => axios.get(API.GET_ROUTE_LIST, { - params: { - title, distance, author, starred, id, token, step, shift - } -}).then(result => (result && result.data && result.data.success && result.data)) - .catch(() => ({ list: [], min: 0, max: 0, limit: 0, step: 20, shift: 20 })); + search, + min, + max, + tab, + token, + step, + shift +}: IGetRouteList): Promise> => + 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 => ( - CLIENT && CLIENT.OSRM_URL && axios.get(CLIENT.OSRM_TEST_URL(bounds)).then(() => true).catch(() => false) -); - -export const dropRoute = ({ address, id, token }: { address: string, id: string, token: string }): AxiosPromise => ( - 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 => ( - 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 => ( - axios.post(API.SET_STARRED, { id, token, address: _id, is_starred }) +export const checkOSRMService = (bounds: LatLngLiteral[]): Promise => + CLIENT && + CLIENT.OSRM_URL && + axios + .get(CLIENT.OSRM_TEST_URL(bounds)) .then(() => true) - .catch(() => true) -); + .catch(() => false); +export const dropRoute = ({ + address, + token +}: { + address: string; + token: string; +}): Promise => + 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> => + 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> => + axios + .post(API.SET_STARRED, { id, token, address, is_starred }) + .then(resultMiddleware) + .catch(errorMiddleware); diff --git a/src/utils/middleware.ts b/src/utils/middleware.ts new file mode 100644 index 0000000..1a2fa1f --- /dev/null +++ b/src/utils/middleware.ts @@ -0,0 +1,50 @@ +import { AxiosRequestConfig } from "axios"; + +export type Unwrap = T extends (...args: any[]) => Promise ? U : T; + +export interface IApiErrorResult { + detail?: string; + code?: string; +} + +export interface IResultWithStatus { + status: any; + data?: Partial & 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 = (({ + status, + data, +}: { + status: number; + data: T; +}): { status: number; data: T } => ({ status, data })); + +export const errorMiddleware = (debug): IResultWithStatus => (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}` }, +}); \ No newline at end of file