import { call, delay, put, race, select, take, TakeEffect, takeEvery, takeLatest } from 'redux-saga/effects'; import { MAP_ACTIONS } from './constants'; import { mapAddSticker, mapClicked, mapSet, mapSetAddressOrigin, mapSetProvider, mapSetRoute, mapSetStickers, mapSetTitle, } from './actions'; import { selectUser } from '~/redux/user/selectors'; import { MODES } from '~/constants/modes'; import { editorCaptureHistory, editorChangeMode, editorClearAll, editorSendSaveRequest, editorSetActiveSticker, editorSetChanged, editorSetEditing, editorSetHistory, editorSetReady, editorSetSave, } from '~/redux/editor/actions'; import { getUrlData, pushLoaderState, pushPath } from '~/utils/history'; import { getStoredMap, postMap } from '~/utils/api'; import { Unwrap } from '~/utils/middleware'; import { selectMap, selectMapProvider, selectMapRoute, selectMapStickers } from './selectors'; import { TIPS } from '~/constants/tips'; import { setReadySaga } from '../editor/sagas'; import { selectEditor } from '../editor/selectors'; import { EDITOR_ACTIONS } from '../editor/constants'; import { MainMap } from '~/constants/map'; function* onMapClick({ latlng }: ReturnType) { const { mode, activeSticker: { set, sticker }, }: ReturnType = yield select(selectEditor); switch (mode) { case MODES.STICKERS: yield put(mapAddSticker({ latlng, set, sticker, text: '', angle: 2.11 })); yield put(editorChangeMode(MODES.NONE)); break; default: break; } } export function* replaceAddressIfItsBusy(destination, original) { if (original) { yield put(mapSetAddressOrigin(original)); } pushPath(`/${destination}/edit`); } export function* loadMapSaga(path) { try { const { data: { route, error, random_url, }, }: Unwrap = yield call(getStoredMap, { name: path }); if (route && !error) { yield put( mapSet({ provider: route.provider, route: route.route, stickers: route.stickers, title: route.title, address: route.address, description: route.description, is_public: route.is_public, logo: route.logo, }), ); yield put(editorSetHistory({ records: [{ route: route.route, stickers: route.stickers }] })); return { route, random_url }; } return null; } catch (e) { console.log(e); yield call(startEmptyEditorSaga); } } export function* startEmptyEditorSaga() { yield put(editorSetReady(false)); const { user: { id, random_url }, }: ReturnType = yield select(selectUser); const { path, mode } = getUrlData(); if (!path || !mode || mode !== 'edit') { pushPath(`/${random_url}/edit`); } yield put(editorClearAll()); yield put(mapSet({ owner: { id } })); yield put(editorSetEditing(true)); yield put(editorSetReady(true)); } export function* loadMapFromPath() { const { path, mode } = getUrlData(); yield put(editorSetHistory({ records: [{ route: [], stickers: [] }], position: 0 })); if (path) { const map = yield call(loadMapSaga, path); if (!map) { yield call(startEmptyEditorSaga); return; } yield put(editorSetEditing(!!(mode && mode === 'edit'))); return; } yield call(startEmptyEditorSaga); } export function* mapInitSaga() { pushLoaderState(90); const { hash } = getUrlData(); const provider: ReturnType = yield select(selectMapProvider); yield put(mapSetProvider(provider)); if (hash && /^#map/.test(hash)) { const matches = hash.match(/^#map[:/?!](.*)$/); if (matches && matches[1]) { yield pushPath(`/${matches[1]}`); yield call(setReadySaga); return; } } yield call(loadMapFromPath); yield call(setReadySaga); MainMap.fitVisibleBounds({ animate: false }); pushLoaderState(100); } function* setActiveStickerSaga() { yield put(editorChangeMode(MODES.STICKERS)); } function setTitleSaga({ title }: ReturnType) { if (title) { document.title = `${title} | Редактор маршрутов`; } } function* startEditingSaga() { const { path } = getUrlData(); yield pushPath(`/${path}/edit`); } function* clearPolySaga() { const route: ReturnType = yield select(selectMapRoute); if (!route.length) return; yield put(mapSetRoute([])); } function* clearStickersSaga() { const stickers: ReturnType = yield select(selectMapStickers); if (!stickers.length) return; yield put(mapSetStickers([])); } function* clearAllSaga() { const route: ReturnType = yield select(selectMapRoute); const stickers: ReturnType = yield select(selectMapStickers); if (!stickers.length && !route.length) return; yield put(editorSetChanged(false)); yield put(mapSet({ route: [], stickers: [] })); yield put(editorCaptureHistory()); } function* clearSaga({ type }) { switch (type) { case EDITOR_ACTIONS.CLEAR_POLY: yield call(clearPolySaga); break; case EDITOR_ACTIONS.CLEAR_STICKERS: yield call(clearStickersSaga); break; case EDITOR_ACTIONS.CLEAR_ALL: yield call(clearAllSaga); break; default: break; } const { mode, activeSticker }: ReturnType = yield select(selectEditor); if (activeSticker && activeSticker.set && activeSticker.sticker) { yield put(editorSetActiveSticker({ set: '', sticker: '' })); } if (mode !== MODES.NONE) { yield put(editorChangeMode(MODES.NONE)); } } function* sendSaveRequestSaga({ title, address, force, is_public, description, }: ReturnType) { try { const { route, stickers, provider }: ReturnType = yield select(selectMap); if (!route.length && !stickers.length) { return yield put( editorSetSave({ error: TIPS.SAVE_EMPTY, loading: false, overwriting: false, finished: false }), ); } const { logo }: ReturnType = yield select(selectMap); const { distance }: ReturnType = yield select(selectEditor); yield put(editorSetSave({ loading: true, overwriting: false, finished: false, error: '' })); const { result, timeout, cancel, }: { result: Unwrap; timeout: boolean; cancel: TakeEffect; } = yield race({ result: postMap({ route, stickers, title, force, address, logo, distance, provider, is_public, description, }), timeout: delay(10000), cancel: take(EDITOR_ACTIONS.RESET_SAVE_DIALOG), }); yield put(editorSetSave({ loading: false })); if (cancel) return yield put(editorChangeMode(MODES.NONE)); if (result && result.data.code === 'already_exist') return yield put(editorSetSave({ overwriting: true })); if (result && result.data.code === 'conflict') return yield put( editorSetSave({ error: TIPS.SAVE_EXISTS, loading: false, overwriting: false, finished: false, }), ); if (timeout || !result || !result.data.route || !result.data.route.address) return yield put( editorSetSave({ error: TIPS.SAVE_TIMED_OUT, loading: false, overwriting: false, finished: false, }), ); yield put( mapSet({ address: result.data.route.address, title: result.data.route.title, is_public: result.data.route.is_public, description: result.data.route.description, }), ); yield put(editorSetReady(false)); pushPath(`/${address}/edit`); yield put(editorSetReady(true)); yield put( editorSetSave({ error: TIPS.SAVE_SUCCESS, loading: false, overwriting: false, finished: true, }), ); } catch (e) { console.log(e); } } function* setChanged() { const { changed } = yield select(selectEditor); if (changed) return; yield put(editorSetChanged(true)); } export function* mapSaga() { yield takeEvery( [MAP_ACTIONS.SET_ROUTE, MAP_ACTIONS.SET_STICKER, MAP_ACTIONS.SET_STICKERS, MAP_ACTIONS.ADD_STICKER], setChanged, ); yield takeEvery(EDITOR_ACTIONS.START_EDITING, startEditingSaga); yield takeEvery(EDITOR_ACTIONS.SET_ACTIVE_STICKER, setActiveStickerSaga); yield takeEvery(MAP_ACTIONS.MAP_CLICKED, onMapClick); yield takeEvery(MAP_ACTIONS.SET_TITLE, setTitleSaga); yield takeLatest(EDITOR_ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga); yield takeEvery( [ EDITOR_ACTIONS.CLEAR_POLY, EDITOR_ACTIONS.CLEAR_STICKERS, EDITOR_ACTIONS.CLEAR_ALL, EDITOR_ACTIONS.CLEAR_CANCEL, ], clearSaga, ); }