diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..789ac2e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 100, + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/src/components/UserLocation.tsx b/src/components/UserLocation.tsx index 191ad42..7dd0e7f 100644 --- a/src/components/UserLocation.tsx +++ b/src/components/UserLocation.tsx @@ -1,78 +1,81 @@ import * as React from 'react'; -import { marker } from 'leaflet'; -import { DomMarker } from '$utils/DomMarker'; -import { Icon } from '$components/panels/Icon'; -import { editor } from '$modules/Editor'; +// import { marker } from 'leaflet'; +// import { DomMarker } from '$utils/DomMarker'; +// import { Icon } from '$components/panels/Icon'; +// import { editor } from '$modules/Editor'; interface Props { } export class UserLocation extends React.Component { - constructor(props) { - super(props); - - const element = document.createElement('div'); - this.icon = new DomMarker({ element, className: 'location-marker' }); - - this.map = editor.map.map; - this.location = []; - } - - icon; - mark = null; - map; - location; - - componentDidMount() { - this.getUserLocation(this.updateLocationMark); - } - - getUserLocation = callback => { - // todo: TO SAGAS - if (!window.navigator || !window.navigator.geolocation) return; - - window.navigator.geolocation.getCurrentPosition(position => { - if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude) return; - - const { latitude, longitude } = position.coords; - - callback(latitude, longitude); - }); - }; - - centerMapOnLocation = () => { - if (this.location && this.location.length === 2) { - this.panMapTo(this.location[0], this.location[1]); - } else { - this.getUserLocation(this.panMapTo); - } - - this.getUserLocation(this.updateLocationMark); - }; - - panMapTo = (latitude, longitude) => { - if (!latitude || !longitude) return; - - this.map.panTo([latitude, longitude]); - }; - - updateLocationMark = (latitude, longitude) => { - if (!latitude || !longitude) return; - - if (this.mark) this.map.removeLayer(this.mark); - - this.location = [latitude, longitude]; - this.mark = marker(this.location, { icon: this.icon }).addTo(this.map); - }; - render() { - return ( -
-
- -
-
- ); + return null } + // constructor(props) { + // super(props); + + // const element = document.createElement('div'); + // this.icon = new DomMarker({ element, className: 'location-marker' }); + + // // this.map = editor.map.map; + // this.location = []; + // } + + // icon; + // mark = null; + // map; + // location; + + // componentDidMount() { + // this.getUserLocation(this.updateLocationMark); + // } + + // getUserLocation = callback => { + // // todo: TO SAGAS + // if (!window.navigator || !window.navigator.geolocation) return; + + // window.navigator.geolocation.getCurrentPosition(position => { + // if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude) return; + + // const { latitude, longitude } = position.coords; + + // callback(latitude, longitude); + // }); + // }; + + // centerMapOnLocation = () => { + // if (this.location && this.location.length === 2) { + // this.panMapTo(this.location[0], this.location[1]); + // } else { + // this.getUserLocation(this.panMapTo); + // } + + // this.getUserLocation(this.updateLocationMark); + // }; + + // panMapTo = (latitude, longitude) => { + // if (!latitude || !longitude) return; + + // this.map.panTo([latitude, longitude]); + // }; + + // updateLocationMark = (latitude, longitude) => { + // if (!latitude || !longitude) return; + + // if (this.mark) this.map.removeLayer(this.mark); + + // this.location = [latitude, longitude]; + // this.mark = marker(this.location, { icon: this.icon }).addTo(this.map); + // }; + + // render() { + // return ( + //
+ //
+ // + //
+ //
+ // ); + // } } diff --git a/src/containers/map/Map/index.tsx b/src/containers/map/Map/index.tsx index 2aa1a64..fc493ff 100644 --- a/src/containers/map/Map/index.tsx +++ b/src/containers/map/Map/index.tsx @@ -14,6 +14,8 @@ import { TileLayer } from "$containers/map/TileLayer"; import { Stickers } from "$containers/map/Stickers"; import { selectUserEditing } from '$redux/user/selectors' +import 'leaflet/dist/leaflet.css'; + const mapStateToProps = state => ({ provider: selectMapProvider(state), route: selectMapRoute(state), @@ -32,6 +34,8 @@ type IProps = React.HTMLAttributes & ReturnType & typeof mapDispatchToProps & {}; +export let MainMap = map(document.getElementById('canvas')).setView([55.0153275, 82.9071235], 13); + const MapUnconnected: React.FC = ({ provider, route, @@ -53,8 +57,8 @@ const MapUnconnected: React.FC = ({ React.useEffect(() => { if (!ref.current) return; - setLayer(map(ref.current).setView([55.0153275, 82.9071235], 13)); - }, [ref]); + setLayer(MainMap); + }, []); React.useEffect(() => { if (!layer) return; diff --git a/src/index.html b/src/index.html index 3acea9a..0917b3d 100644 --- a/src/index.html +++ b/src/index.html @@ -131,8 +131,7 @@ -
-
+
diff --git a/src/redux/map/actions.ts b/src/redux/map/actions.ts index c45885b..13a8966 100644 --- a/src/redux/map/actions.ts +++ b/src/redux/map/actions.ts @@ -24,6 +24,11 @@ export const mapSetSticker = (index: number, sticker: IStickerDump) => ({ sticker, }); +export const mapAddSticker = (sticker: IStickerDump) => ({ + type: MAP_ACTIONS.ADD_STICKER, + sticker, +}); + export const mapDropSticker = (index: number) => ({ type: MAP_ACTIONS.DROP_STICKER, index, @@ -33,3 +38,28 @@ export const mapClicked = (latlng: ILatLng) => ({ type: MAP_ACTIONS.MAP_CLICKED, latlng, }); + +export const mapSetTitle = (title: string) => ({ + type: MAP_ACTIONS.SET_TITLE, + title, +}); + +export const mapSetDescription = (description: string) => ({ + type: MAP_ACTIONS.SET_DESCRIPTION, + description, +}); + +export const mapSetAddress = (address: string) => ({ + type: MAP_ACTIONS.SET_ADDRESS, + address, +}); + +export const mapSetOwner = (owner: IMapReducer['owner']) => ({ + type: MAP_ACTIONS.SET_DESCRIPTION, + owner, +}); + +export const mapSetPublic = (is_public: IMapReducer['is_public']) => ({ + type: MAP_ACTIONS.SET_PUBLIC, + is_public, +}); diff --git a/src/redux/map/constants.ts b/src/redux/map/constants.ts index e9317fc..9bfb6bf 100644 --- a/src/redux/map/constants.ts +++ b/src/redux/map/constants.ts @@ -4,7 +4,13 @@ export const MAP_ACTIONS = { SET_MAP: `${P}-SET_MAP`, SET_PROVIDER: `${P}-SET_PROVIDER`, SET_ROUTE: `${P}-SET_ROUTE`, + SET_TITLE: `${P}-SET_TILE`, + SET_DESCRIPTION: `${P}-SETDESCRIPTION`, + SET_ADDRESS: `${P}-SET_ADDRESS`, + SET_OWNER: `${P}-SET_OWNER`, + SET_PUBLIC: `${P}-SET_PUBLIC`, + ADD_STICKER: `${P}-ADD_STICKER`, SET_STICKER: `${P}-SET_STICKER`, DROP_STICKER: `${P}-DROP_STICKER`, diff --git a/src/redux/map/handlers.ts b/src/redux/map/handlers.ts index af98fa9..dbfb5d7 100644 --- a/src/redux/map/handlers.ts +++ b/src/redux/map/handlers.ts @@ -1,13 +1,21 @@ -import { MAP_ACTIONS } from "./constants"; -import { IMapReducer } from "."; -import { mapSet, mapSetProvider, mapSetRoute, mapSetSticker } from "./actions"; +import { MAP_ACTIONS } from './constants'; +import { IMapReducer } from '.'; +import { + mapSet, + mapSetProvider, + mapSetRoute, + mapSetSticker, + mapAddSticker, + mapSetTitle, + mapSetAddress, + mapSetDescription, + mapSetOwner, + mapSetPublic, +} from './actions'; -const setMap = ( - state: IMapReducer, - { map }: ReturnType -): IMapReducer => ({ +const setMap = (state: IMapReducer, { map }: ReturnType): IMapReducer => ({ ...state, - ...map + ...map, }); const setProvider = ( @@ -15,15 +23,12 @@ const setProvider = ( { provider }: ReturnType ): IMapReducer => ({ ...state, - provider + provider, }); -const setRoute = ( - state: IMapReducer, - { route }: ReturnType -): IMapReducer => ({ +const setRoute = (state: IMapReducer, { route }: ReturnType): IMapReducer => ({ ...state, - route + route, }); const setSticker = ( @@ -31,7 +36,7 @@ const setSticker = ( { sticker, index }: ReturnType ): IMapReducer => ({ ...state, - stickers: state.stickers.map((item, i) => (i === index ? sticker : item)) + stickers: state.stickers.map((item, i) => (i === index ? sticker : item)), }); const dropSticker = ( @@ -39,7 +44,40 @@ const dropSticker = ( { index }: ReturnType ): IMapReducer => ({ ...state, - stickers: state.stickers.filter((_, i) => i !== index) + stickers: state.stickers.filter((_, i) => i !== index), +}); + +const addSticker = ( + state: IMapReducer, + { sticker }: ReturnType +): IMapReducer => ({ + ...state, + stickers: [...state.stickers, sticker], +}); + +const setTitle = (state: IMapReducer, { title }: ReturnType): IMapReducer => ({ + ...state, + title, +}); + +const setAddress = (state: IMapReducer, { address }: ReturnType): IMapReducer => ({ + ...state, + address, +}); + +const setDescription = (state: IMapReducer, { description }: ReturnType): IMapReducer => ({ + ...state, + description, +}); + +const setOwner = (state: IMapReducer, { owner }: ReturnType): IMapReducer => ({ + ...state, + owner, +}); + +const setPublic = (state: IMapReducer, { is_public }: ReturnType): IMapReducer => ({ + ...state, + is_public, }); export const MAP_HANDLERS = { @@ -48,4 +86,10 @@ export const MAP_HANDLERS = { [MAP_ACTIONS.SET_ROUTE]: setRoute, [MAP_ACTIONS.SET_STICKER]: setSticker, [MAP_ACTIONS.DROP_STICKER]: dropSticker, + [MAP_ACTIONS.ADD_STICKER]: addSticker, + [MAP_ACTIONS.SET_TITLE]: setTitle, + [MAP_ACTIONS.SET_ADDRESS]: setAddress, + [MAP_ACTIONS.SET_DESCRIPTION]: setDescription, + [MAP_ACTIONS.SET_OWNER]: setOwner, + [MAP_ACTIONS.SET_PUBLIC]: setPublic, }; diff --git a/src/redux/map/index.ts b/src/redux/map/index.ts index 35d8920..41ecff2 100644 --- a/src/redux/map/index.ts +++ b/src/redux/map/index.ts @@ -7,13 +7,23 @@ import { IStickerDump } from '$modules/Sticker'; export interface IMapReducer { provider: string; route: IMapRoute; - stickers: IStickerDump[] + stickers: IStickerDump[]; + title: string; + address: string; + description: string; + owner: { id: string }; + is_public: boolean; } -export const MAP_INITIAL_STATE = { +export const MAP_INITIAL_STATE: IMapReducer = { provider: DEFAULT_PROVIDER, route: [], stickers: [], + title: '', + address: '', + description: '', + owner: { id: null }, + is_public: false, } export const map = createReducer(MAP_INITIAL_STATE, MAP_HANDLERS) \ No newline at end of file diff --git a/src/redux/map/sagas.ts b/src/redux/map/sagas.ts index abfbdc1..6acec43 100644 --- a/src/redux/map/sagas.ts +++ b/src/redux/map/sagas.ts @@ -1,40 +1,323 @@ -import { takeEvery, select, put } from "redux-saga/effects"; -import { MAP_ACTIONS } from "./constants"; -import { mapClicked, mapSet } from "./actions"; -import { selectUserMode, selectUserActiveSticker } from "$redux/user/selectors"; -import { IRootReducer } from "$redux/user"; -import { MODES } from "$constants/modes"; -import { selectMapStickers } from "./selectors"; -import { setActiveSticker, setMode } from "$redux/user/actions"; +import { takeEvery, select, put, call, TakeEffect, race, take, takeLatest } from 'redux-saga/effects'; +import { MAP_ACTIONS } from './constants'; +import { mapClicked, mapAddSticker, mapSetProvider, mapSet, mapSetTitle, mapSetAddress, mapSetDescription, mapSetOwner, mapSetPublic } from './actions'; +import { selectUserMode, selectUserActiveSticker, selectUser, selectUserUser } from '$redux/user/selectors'; +import { MODES } from '$constants/modes'; +import { + setMode, + setChanged, + setAddressOrigin, + setEditing, + setReady, + setActiveSticker, + setSaveError, + setSaveLoading, + sendSaveRequest, + setSaveSuccess, + setSaveOverwrite, +} from '$redux/user/actions'; +import { pushLoaderState, getUrlData, pushPath, replacePath } from '$utils/history'; +import { setReadySaga, searchSetSagaWorker } from '$redux/user/sagas'; +import { getStoredMap, postMap } from '$utils/api'; +import { Unwrap } from '$utils/middleware'; +import { DEFAULT_PROVIDER } from '$constants/providers'; +import { USER_ACTIONS } from '$redux/user/constants'; +import { selectMap } from './selectors'; +import { TIPS } from '$constants/tips'; +import { delay } from 'redux-saga'; function* onMapClick({ latlng }: ReturnType) { const mode = yield select(selectUserMode); const { set, sticker } = yield select(selectUserActiveSticker); - const stickers = yield select(selectMapStickers); switch (mode) { case MODES.STICKERS: - yield put( - mapSet({ - stickers: [ - ...stickers, - { - latlng, - set, - sticker, - text: "", - angle: 0, - } - ] - }) - ); - yield put(setMode(MODES.NONE)) + yield put(mapAddSticker({ latlng, set, sticker, text: '', angle: 0 })); + yield put(setMode(MODES.NONE)); break; default: } } -export function* mapSaga() { - yield takeEvery(MAP_ACTIONS.MAP_CLICKED, onMapClick); +// function* changeProviderSaga({ provider }: ReturnType) { +// const { provider: current_provider } = yield select(selectUser); + +// yield put(mapSetProvider(provider)); + +// if (current_provider === provider) return; + +// yield put(setChanged(true)); + +// return put(setMode(MODES.NONE)); +// } + +function* setLogoSaga({ logo }: { type: string; logo: string }) { + const { mode } = yield select(selectUser); + + yield put(setChanged(true)); + + if (mode === MODES.LOGO) { + yield put(setMode(MODES.NONE)); + } +} + +export function* replaceAddressIfItsBusy(destination, original) { + if (original) { + yield put(setAddressOrigin(original)); + } + + pushPath(`/${destination}/edit`); +} + +export function* loadMapSaga(path) { + const { + data: { route, error, random_url }, + }: Unwrap = yield call(getStoredMap, { name: path }); + + if (route && !error) { + // TODO: set initial data + // TODO: fit bounds + + yield put( + mapSet({ + provider: route.provider, + route: route.route, + stickers: route.stickers, + title: route.title, + }) + ); + + return { route, random_url }; + } + + return null; +} + +function* startEmptyEditorSaga() { + const { + user: { id, random_url }, + provider = DEFAULT_PROVIDER, + } = yield select(selectUser); + + // TODO: set owner { id } + pushPath(`/${random_url}/edit`); + + yield put(setChanged(false)); + yield put(setEditing(true)); + + return yield call(setReadySaga); +} + +export function* mapInitSaga() { + pushLoaderState(90); + + const { path, mode, hash } = getUrlData(); + const { + provider, + user: { id }, + } = yield select(selectUser); + + yield put(mapSetProvider(provider)); + + if (hash && /^#map/.test(hash)) { + const [, newUrl] = hash.match(/^#map[:/?!](.*)$/); + + if (newUrl) { + yield pushPath(`/${newUrl}`); + yield call(setReadySaga); + return; + } + } + + if (path) { + const map = yield call(loadMapSaga, path); + + 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(setEditing(true)); + // TODO: start editing + } else { + yield put(setEditing(false)); + // TODO: stop editing + } + + yield call(setReadySaga); + return true; + } + } + + yield call(startEmptyEditorSaga); + yield put(setReady(true)); + + pushLoaderState(100); + + return true; +} + +function* setActiveStickerSaga() { + yield put(setMode(MODES.STICKERS)); +} + +function* setTitleSaga({ title }: ReturnType) { + if (title) { + document.title = `${title} | Редактор маршрутов`; + } +} + +function* clearSaga({ type }) { + switch (type) { + case USER_ACTIONS.CLEAR_POLY: + // TODO: clear router waypoints + yield put( + mapSet({ + route: [], + }) + ); + break; + + case USER_ACTIONS.CLEAR_STICKERS: + yield put( + mapSet({ + stickers: [], + }) + ); + break; + + case USER_ACTIONS.CLEAR_ALL: + yield put(setChanged(false)); + yield put( + mapSet({ + route: [], + stickers: [], + }) + ); + break; + + default: + break; + } + + yield put(setActiveSticker(null)); // TODO: move to maps + yield put(setMode(MODES.NONE)); +} + +function* sendSaveRequestSaga({ + title, + address, + force, + is_public, + description, +}: ReturnType) { + const { route, stickers, provider } = yield select(selectMap); + + if (!route.length && !stickers.length) { + return yield put(setSaveError(TIPS.SAVE_EMPTY)); // TODO: move setSaveError to editor + } + + const { logo, distance } = yield select(selectUser); + const { token } = yield select(selectUserUser); + + yield put(setSaveLoading(true)); // TODO: move setSaveLoading to maps + + const { + result, + timeout, + cancel, + }: { + result: Unwrap; + timeout: boolean; + cancel: TakeEffect; + } = yield race({ + result: postMap({ + token, + route, + stickers, + title, + force, + address, + logo, + distance, + provider, + is_public, + description, + }), + timeout: delay(10000), + cancel: take(USER_ACTIONS.RESET_SAVE_DIALOG), + }); + + yield put(setSaveLoading(false)); + + if (cancel) return yield put(setMode(MODES.NONE)); + + if (result && result.data.code === 'already_exist') return yield put(setSaveOverwrite()); // TODO: move setSaveOverwrite to editor + if (result && result.data.code === 'conflict') return yield put(setSaveError(TIPS.SAVE_EXISTS)); + if (timeout || !result || !result.data.route || !result.data.route.address) + return yield put(setSaveError(TIPS.SAVE_TIMED_OUT)); + + return yield put( // TODO: move setSaveSuccess to editor + setSaveSuccess({ + address: result.data.route.address, + title: result.data.route.title, + is_public: result.data.route.is_public, + description: result.data.route.description, + + save_error: TIPS.SAVE_SUCCESS, + }) + ); +} + +function* setSaveSuccessSaga({ + address, + title, + is_public, + description, +}: ReturnType) { + const { id } = yield select(selectUser); + const { dialog_active } = yield select(selectUser); + + replacePath(`/${address}/edit`); + + yield put(mapSetTitle(title)); + yield put(mapSetAddress(address)); + yield put(mapSetPublic(is_public)); + yield put(mapSetDescription(description)); + yield put(setChanged(false)); + yield put(mapSetOwner({ id })); + + if (dialog_active) { + yield call(searchSetSagaWorker); + } + + // yield editor.setInitialData(); + // TODO: set initial data here + + return +} + +export function* mapSaga() { + yield takeEvery(USER_ACTIONS.SET_ACTIVE_STICKER, setActiveStickerSaga); // TODO: move active sticker to maps + yield takeEvery(MAP_ACTIONS.MAP_CLICKED, onMapClick); + yield takeEvery(MAP_ACTIONS.SET_TITLE, setTitleSaga); + yield takeEvery(USER_ACTIONS.SET_LOGO, setLogoSaga); + yield takeLatest(USER_ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga); + yield takeLatest(USER_ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga); + + yield takeEvery( + // TODO: move all actions to MAP + [ + USER_ACTIONS.CLEAR_POLY, + USER_ACTIONS.CLEAR_STICKERS, + USER_ACTIONS.CLEAR_ALL, + USER_ACTIONS.CLEAR_CANCEL, + ], + clearSaga + ); } diff --git a/src/redux/map/selectors.ts b/src/redux/map/selectors.ts index 8a1675b..6bceeda 100644 --- a/src/redux/map/selectors.ts +++ b/src/redux/map/selectors.ts @@ -1,5 +1,6 @@ import { IState } from "$redux/store"; +export const selectMap = (state: IState) => state.map; export const selectMapProvider = (state: IState) => state.map.provider; export const selectMapRoute= (state: IState) => state.map.route; export const selectMapStickers = (state: IState) => state.map.stickers; \ No newline at end of file diff --git a/src/redux/user/sagas.ts b/src/redux/user/sagas.ts index f5e2683..ce04081 100644 --- a/src/redux/user/sagas.ts +++ b/src/redux/user/sagas.ts @@ -1,5 +1,5 @@ -import { REHYDRATE, RehydrateAction } from "redux-persist"; -import { delay, SagaIterator } from "redux-saga"; +import { REHYDRATE, RehydrateAction } from 'redux-persist'; +import { delay, SagaIterator } from 'redux-saga'; import { takeLatest, select, @@ -7,9 +7,7 @@ import { put, takeEvery, race, - take, - TakeEffect -} from "redux-saga/effects"; +} from 'redux-saga/effects'; import { checkIframeToken, checkOSRMService, @@ -17,56 +15,41 @@ import { dropRoute, getGuestToken, getRouteList, - getStoredMap, modifyRoute, - postMap, - sendRouteStarred -} from "$utils/api"; + sendRouteStarred, +} from '$utils/api'; import { hideRenderer, searchPutRoutes, searchSetLoading, - setActiveSticker, - setAddress, setChanged, setDialogActive, setEditing, setMode, setReady, setRenderer, - setSaveError, - setSaveOverwrite, - setSaveSuccess, - setTitle, searchSetTab, setUser, setDialog, - setPublic, setAddressOrigin, - setProvider, - changeProvider, - setSaveLoading, mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, setRouteStarred, - setDescription -} from "$redux/user/actions"; +} from '$redux/user/actions'; + import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, - replacePath -} from "$utils/history"; -import { editor } from "$modules/Editor"; -import { USER_ACTIONS } from "$redux/user/constants"; -import { MODES } from "$constants/modes"; -import { DEFAULT_USER, IUser } from "$constants/auth"; -import { TIPS } from "$constants/tips"; +} from '$utils/history'; +import { USER_ACTIONS } from '$redux/user/constants'; +import { MODES } from '$constants/modes'; +import { DEFAULT_USER } from '$constants/auth'; import { composeArrows, composeDistMark, @@ -78,32 +61,33 @@ import { getPolyPlacement, getStickersPlacement, getTilePlacement, - imageFetcher -} from "$utils/renderer"; -import { LOGOS } from "$constants/logos"; -import { DEFAULT_PROVIDER } from "$constants/providers"; -import { DIALOGS, TABS } from "$constants/dialogs"; + imageFetcher, +} from '$utils/renderer'; +import { LOGOS } from '$constants/logos'; +import { DIALOGS, TABS } from '$constants/dialogs'; -import * as ActionCreators from "$redux/user/actions"; -import { IRootState } from "$redux/user"; -import { downloadGPXTrack, getGPXString } from "$utils/gpx"; -import { Unwrap } from "$utils/middleware"; -import { IState } from "$redux/store"; -import { mapSetProvider, mapSet } from "$redux/map/actions"; +import * as ActionCreators from '$redux/user/actions'; +import { downloadGPXTrack, getGPXString } from '$utils/gpx'; +import { Unwrap } from '$utils/middleware'; +import { IState } from '$redux/store'; +import { selectUser, selectUserUser } from './selectors'; +import { mapInitSaga, loadMapSaga, replaceAddressIfItsBusy } from '$redux/map/sagas'; +import { LatLng } from 'leaflet'; +import { selectMap } from '$redux/map/selectors'; -const getUser = (state: IState) => state.user.user; -const getState = (state: IState) => state.user; +// const getUser = (state: IState) => state.user.user; +// const selectUser = (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 { - data: { user, random_url } + data: { user, random_url }, }: Unwrap = yield call(getGuestToken); yield put(setUser({ ...user, random_url })); @@ -111,31 +95,13 @@ function* generateGuestSaga() { return { ...user, random_url }; } -function* startEmptyEditorSaga() { - const { - user: { id, random_url }, - provider = DEFAULT_PROVIDER - } = yield select(getState); - - pushPath(`/${random_url}/edit`); - - editor.owner = id; - editor.setProvider(provider); - editor.startEditing(); - - yield put(setChanged(false)); - yield put(setEditing(true)); - - return yield call(setReadySaga); -} - function* startEditingSaga() { const { path } = getUrlData(); yield pushPath(`/${path}/edit`); } function* stopEditingSaga() { - const { changed, editing, mode, address_origin } = yield select(getState); + const { changed, editing, mode, address_origin } = yield select(selectUser); const { path } = getUrlData(); if (!editing) return; @@ -144,60 +110,23 @@ function* stopEditingSaga() { return; } - yield editor.cancelEditing(); + // TODO: cancel editing? + // yield editor.cancelEditing(); yield put(setMode(MODES.NONE)); yield put(setChanged(false)); - yield put(setEditing(editor.hasEmptyHistory)); // don't close editor if no previous map + // TODO: dont close editor if theres no initial data + // yield put(setEditing(editor.hasEmptyHistory)); // don't close editor if no previous map yield pushPath(`/${address_origin || path}/`); } -function* loadMapSaga(path) { - const { - data: { route, error, random_url } - }: Unwrap = yield call(getStoredMap, { name: path }); - - if (route && !error) { - yield editor.clearAll(); - yield editor.setData(route); - yield editor.fitDrawing(); - yield editor.setInitialData(); - - yield put(setChanged(false)); - - // TODO: REACTIVE BRANCH: - // yield put(mapSetProvider(route.provider)); - yield put( - mapSet({ - provider: route.provider, - route: route.route, - stickers: route.stickers - }) - ); - - return { route, random_url }; - } - - return null; -} - -function* replaceAddressIfItsBusy(destination, original) { - if (original) { - yield put(setAddressOrigin(original)); - } - - pushPath(`/${destination}/edit`); -} - function* checkOSRMServiceSaga() { - const north_east = editor.map.map.getBounds().getNorthEast(); - const south_west = editor.map.map.getBounds().getSouthWest(); - const routing = yield call(checkOSRMService, [north_east, south_west]); + const routing = yield call(checkOSRMService, [new LatLng(1,1), new LatLng(2,2)]); yield put(setFeature({ routing })); } -function* setReadySaga() { +export function* setReadySaga() { yield put(setReady(true)); hideLoader(); @@ -205,72 +134,13 @@ function* setReadySaga() { yield put(searchSetTab(TABS.MY)); } -function* mapInitSaga() { - pushLoaderState(90); - - const { path, mode, hash } = getUrlData(); - const { - provider, - user: { id } - } = yield select(getState); - - editor.map.setProvider(provider); - yield put(changeProvider(provider)); - - if (hash && /^#map/.test(hash)) { - const [, newUrl] = hash.match(/^#map[:/?!](.*)$/); - - if (newUrl) { - yield pushPath(`/${newUrl}`); - return yield call(setReadySaga); - } - } - - if (path) { - const map = yield call(loadMapSaga, path); - - 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(setEditing(true)); - editor.startEditing(); - } else { - yield put(setEditing(false)); - editor.stopEditing(); - } - - yield call(setReadySaga); - return true; - } - } - - yield call(startEmptyEditorSaga); - yield put(setReady(true)); - - pushLoaderState(100); - - return true; -} - function* authCheckSaga({ key }: RehydrateAction) { - if (key !== "user") return; + if (key !== 'user') return; pushLoaderState(70); - const { id, token } = yield select(getUser); - const { ready } = yield select(getState); + const { id, token } = yield select(selectUserUser); + const { ready } = yield select(selectUser); if (window.location.search || true) { const { viewer_id, auth_key } = yield parseQuery(window.location.search); @@ -290,10 +160,10 @@ function* authCheckSaga({ key }: RehydrateAction) { if (id && token) { const { - data: { user, random_url } + data: { user, random_url }, }: Unwrap = yield call(checkUserToken, { id, - token + token, }); if (user) { @@ -315,180 +185,69 @@ function* authCheckSaga({ key }: RehydrateAction) { return yield call(mapInitSaga); } -function* setModeSaga({ mode }: ReturnType) { - return yield editor.changeMode(mode); +// function* setModeSaga({ mode }: ReturnType) { + // return yield editor.changeMode(mode); // console.log('change', mode); -} +// } -function* setActiveStickerSaga({ - activeSticker -}: { - type: string; - activeSticker: IRootState["activeSticker"]; -}) { - yield (editor.activeSticker = activeSticker); - yield put(setMode(MODES.STICKERS)); +// function* setLogoSaga({ logo }: { type: string; logo: string }) { +// const { mode } = yield select(selectUser); +// editor.logo = logo; - return true; -} +// yield put(setChanged(true)); -function* setLogoSaga({ logo }: { type: string; logo: string }) { - const { mode } = yield select(getState); - editor.logo = logo; +// if (mode === MODES.LOGO) { +// yield put(setMode(MODES.NONE)); +// } +// } - yield put(setChanged(true)); +// function* routerCancelSaga() { +// yield call(editor.router.cancelDrawing); +// yield put(setMode(MODES.NONE)); - if (mode === MODES.LOGO) { - yield put(setMode(MODES.NONE)); - } -} +// return true; +// } -function* routerCancelSaga() { - yield call(editor.router.cancelDrawing); - yield put(setMode(MODES.NONE)); +// function* routerSubmitSaga() { +// yield call(editor.router.submitDrawing); +// yield put(setMode(MODES.NONE)); - return true; -} +// return true; +// } -function* routerSubmitSaga() { - yield call(editor.router.submitDrawing); - yield put(setMode(MODES.NONE)); - - return true; -} - -function* clearSaga({ type }) { - switch (type) { - case USER_ACTIONS.CLEAR_POLY: - yield editor.poly.clearAll(); - yield editor.router.clearAll(); - yield put( - mapSet({ - route: [] - }) - ); - break; - - case USER_ACTIONS.CLEAR_STICKERS: - yield editor.stickers.clearAll(); - yield put( - mapSet({ - stickers: [] - }) - ); - break; - - case USER_ACTIONS.CLEAR_ALL: - yield editor.clearAll(); - yield put(setChanged(false)); - yield put( - mapSet({ - route: [], - stickers: [] - }) - ); - break; - - default: - break; - } - - yield put(setActiveSticker(null)); - yield put(setMode(MODES.NONE)); -} - -function* sendSaveRequestSaga({ - 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 { token } = yield select(getUser); - - yield put(setSaveLoading(true)); - - const { - result, - timeout, - cancel - }: { - result: Unwrap; - timeout: boolean; - cancel: TakeEffect; - } = yield race({ - result: postMap({ - token, - route, - stickers, - title, - force, - address, - logo, - distance, - provider, - is_public, - description - }), - timeout: delay(10000), - cancel: take(USER_ACTIONS.RESET_SAVE_DIALOG) - }); - - yield put(setSaveLoading(false)); - - if (cancel) return yield put(setMode(MODES.NONE)); - - if (result && result.data.code === "already_exist") - return yield put(setSaveOverwrite()); - if (result && result.data.code === "conflict") - return yield put(setSaveError(TIPS.SAVE_EXISTS)); - if (timeout || !result || !result.data.route || !result.data.route.address) - return yield put(setSaveError(TIPS.SAVE_TIMED_OUT)); - - return yield put( - setSaveSuccess({ - address: result.data.route.address, - title: result.data.route.title, - is_public: result.data.route.is_public, - description: result.data.route.description, - - 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 { route, stickers, provider }: ReturnType = yield select(selectMap); + + 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(); - const stickers = getStickersPlacement(); - const distance = editor.poly.poly.distance; + const points = getPolyPlacement(route); + const sticker_points = getStickersPlacement(stickers); + // TODO: get distance: + const distance = 0; + // const distance = editor.poly.poly.distance; ctx.clearRect(0, 0, canvas.width, canvas.height); - const images = yield fetchImages(ctx, geometry); + const images = yield fetchImages(ctx, geometry, provider); - yield put(setRenderer({ info: "Отрисовка", progress: 0.5 })); + yield put(setRenderer({ info: 'Отрисовка', progress: 0.5 })); yield composeImages({ geometry, images, ctx }); yield composePoly({ points, ctx }); yield composeArrows({ points, ctx }); yield composeDistMark({ ctx, points, distance }); - yield composeStickers({ stickers, ctx }); + yield composeStickers({ stickers: sticker_points, 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() { @@ -496,7 +255,7 @@ function* takeAShotSaga() { const { result, timeout } = yield race({ result: worker, - timeout: delay(500) + timeout: delay(500), }); if (timeout) yield put(setMode(MODES.SHOT_PREFETCH)); @@ -509,7 +268,7 @@ function* takeAShotSaga() { data, renderer_active: true, width: window.innerWidth, - height: window.innerHeight + height: window.innerHeight, }) ); } @@ -517,66 +276,42 @@ function* takeAShotSaga() { function* getCropData({ x, y, width, height }) { const { logo, - renderer: { data } - } = yield select(getState); - const canvas = document.getElementById("renderer"); + renderer: { data }, + } = yield select(selectUser); + 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); + const { title, address } = yield select(selectUser); yield call(getCropData, params); - const canvas = document.getElementById("renderer") as HTMLCanvasElement; + const canvas = document.getElementById('renderer') as HTMLCanvasElement; - downloadCanvas(canvas, (title || address).replace(/\./gi, " ")); + downloadCanvas(canvas, (title || address).replace(/\./gi, ' ')); return yield put(hideRenderer()); } -function* changeProviderSaga({ - provider -}: ReturnType) { - const { provider: current_provider } = yield select(getState); - - yield put(setProvider(provider)); - - // TODO: REACTIVE BRANCH - yield put(mapSetProvider(provider)); - - if (current_provider === provider) return; - - yield put(setChanged(true)); - - editor.provider = provider; - editor.map.setProvider(provider); - - return put(setMode(MODES.NONE)); -} - -function* locationChangeSaga({ - location -}: ReturnType) { +function* locationChangeSaga({ location }: ReturnType) { const { address, ready, - user: { id, random_url } - } = yield select(getState); + user: { id, random_url }, + } = yield select(selectUser); + + const { owner }: ReturnType = yield select(selectMap) if (!ready) return; @@ -585,67 +320,55 @@ function* locationChangeSaga({ if (address !== path) { const map = yield call(loadMapSaga, path); - if ( - map && - map.route && - map.route.owner && - mode === "edit" && - map.route.owner !== id - ) { - console.log("replace address if its busy"); + if (map && map.route && map.route.owner && mode === 'edit' && map.route.owner !== id) { return yield call(replaceAddressIfItsBusy, map.random_url, map.address); } - } else if (mode === "edit" && editor.owner !== id) { + } else if (mode === 'edit' && owner.id !== id) { return yield call(replaceAddressIfItsBusy, random_url, address); } else { - yield put(setAddressOrigin("")); + yield put(setAddressOrigin('')); } - if (mode !== "edit") { + if (mode !== 'edit') { yield put(setEditing(false)); - editor.stopEditing(); + // editor.stopEditing(); } else { yield put(setEditing(true)); - editor.startEditing(); + // editor.startEditing(); } } -function* gotVkUserSaga({ - user: u -}: ReturnType) { +function* gotVkUserSaga({ user: u }: ReturnType) { const { - data: { user, random_url } + 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") { + if (key === 'Escape') { const { dialog_active, mode, - renderer: { renderer_active } - } = yield select(getState); + renderer: { renderer_active }, + } = yield select(selectUser); 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") { + } else if (key === 'Delete') { const { - user: { editing } + user: { editing }, } = yield select(); if (!editing) return; - const { mode } = yield select(getState); + const { mode } = yield select(selectUser); if (mode === MODES.TRASH) { yield put(clearAll()); @@ -656,15 +379,15 @@ function* keyPressedSaga({ } function* searchGetRoutes() { - const { token } = yield select(getUser); + const { token } = yield select(selectUserUser); const { routes: { step, shift, - filter: { title, distance, tab } - } - } = yield select(getState); + filter: { title, distance, tab }, + }, + } = yield select(selectUser); const result: Unwrap = yield getRouteList({ token, @@ -673,23 +396,23 @@ function* searchGetRoutes() { max: distance[1], step, shift, - tab + tab, }); return result; } -function* searchSetSagaWorker() { +export function* searchSetSagaWorker() { const { - routes: { filter } - }: ReturnType = yield select(getState); + routes: { filter }, + }: ReturnType = yield select(selectUser); const { data: { routes, limits: { min, max, count: limit }, - filter: { shift, step } - } + filter: { shift, step }, + }, }: Unwrap = yield call(searchGetRoutes); yield put(searchPutRoutes({ list: routes, min, max, limit, shift, step })); @@ -701,12 +424,8 @@ function* searchSetSagaWorker() { ) { 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] + filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0], + filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1], ]) ); } @@ -721,15 +440,13 @@ function* searchSetSaga() { yield call(searchSetSagaWorker); } -function* openMapDialogSaga({ - tab -}: ReturnType) { +function* openMapDialogSaga({ tab }: ReturnType) { const { dialog_active, routes: { - filter: { tab: current } - } - } = yield select(getState); + filter: { tab: current }, + }, + } = yield select(selectUser); if (dialog_active && tab === current) { return yield put(setDialogActive(false)); @@ -748,37 +465,9 @@ function* openMapDialogSaga({ function* searchSetTabSaga() { 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("")); -} - -function* setSaveSuccessSaga({ - address, - title, - is_public, - description -}: ReturnType) { - const { id } = yield select(getUser); - const { dialog_active } = yield select(getState); - - replacePath(`/${address}/edit`); - - yield put(setTitle(title)); - yield put(setAddress(address)); - yield put(setPublic(is_public)); - yield put(setDescription(description)); - yield put(setChanged(false)); - - editor.owner = id; - - if (dialog_active) { - yield call(searchSetSagaWorker); - } - - return yield editor.setInitialData(); + yield put(searchSetTitle('')); } function* userLogoutSaga(): SagaIterator { @@ -787,24 +476,16 @@ function* userLogoutSaga(): SagaIterator { } function* setUserSaga() { - const { dialog_active } = yield select(getState); + const { dialog_active } = yield select(selectUser); if (dialog_active) yield call(searchSetSagaWorker); return true; } -function* setTitleSaga({ - title -}: ReturnType): SagaIterator { - if (title) { - document.title = `${title} | Редактор маршрутов`; - } -} - function* getGPXTrackSaga(): SagaIterator { - const { route, stickers } = editor.dumpData(); - const { title, address } = yield select(getState); + const { route, stickers }: ReturnType = yield select(selectMap); + const { title, address } = yield select(selectUser); if (!route || route.length <= 0) return; @@ -815,8 +496,8 @@ function* getGPXTrackSaga(): SagaIterator { function* mapsLoadMoreSaga() { const { - routes: { limit, list, shift, step, loading, filter } - } = yield select(getState); + routes: { limit, list, shift, step, loading, filter }, + } = yield select(selectUser); if (loading || list.length >= limit || limit === 0) return; @@ -829,8 +510,8 @@ function* mapsLoadMoreSaga() { data: { limits: { min, max, count }, filter: { shift: resp_shift, step: resp_step }, - routes - } + routes, + }, }: Unwrap = yield call(searchGetRoutes); if ( @@ -839,12 +520,8 @@ function* mapsLoadMoreSaga() { ) { 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] + filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0], + filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1], ]) ); } @@ -856,25 +533,23 @@ function* mapsLoadMoreSaga() { limit: count, shift: resp_shift, step: resp_step, - list: [...list, ...routes] + list: [...list, ...routes], }) ); yield put(searchSetLoading(false)); } -function* dropRouteSaga({ - address -}: ReturnType): SagaIterator { - const { token } = yield select(getUser); +function* dropRouteSaga({ address }: ReturnType): SagaIterator { + const { token } = yield select(selectUserUser); const { routes: { list, step, shift, limit, - filter: { min, max } - } - } = yield select(getState); + filter: { min, max }, + }, + } = yield select(selectUser); const index = list.findIndex(el => el.address === address); @@ -886,7 +561,7 @@ function* dropRouteSaga({ max, step, shift: shift > 0 ? shift - 1 : 0, - limit: limit > 0 ? limit - 1 : limit + limit: limit > 0 ? limit - 1 : limit, }) ); } @@ -897,32 +572,30 @@ function* dropRouteSaga({ function* modifyRouteSaga({ address, title, - is_public + is_public, }: ReturnType): SagaIterator { - const { token } = yield select(getUser); + const { token } = yield select(selectUserUser); const { routes: { list, step, shift, limit, - filter: { min, max } - } - }: ReturnType = yield select(getState); + filter: { min, max }, + }, + }: ReturnType = yield select(selectUser); const index = list.findIndex(el => el.address === address); if (index >= 0) { yield put( searchPutRoutes({ - list: list.map(el => - el.address !== address ? el : { ...el, title, is_public } - ), + 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 + limit: limit > 0 ? limit - 1 : limit, }) ); } @@ -931,20 +604,20 @@ function* modifyRouteSaga({ } function* toggleRouteStarredSaga({ - address + address, }: ReturnType) { const { - routes: { list } - }: IState["user"] = yield select(getState); + routes: { list }, + }: IState['user'] = yield select(selectUser); const route = list.find(el => el.address === address); - const { token } = yield select(getUser); + const { token } = yield select(selectUserUser); yield put(setRouteStarred(address, !route.is_published)); const result = yield sendRouteStarred({ token, address, - is_published: !route.is_published + is_published: !route.is_published, }); if (!result) return yield put(setRouteStarred(address, route.is_published)); @@ -952,39 +625,25 @@ function* toggleRouteStarredSaga({ export function* userSaga() { yield takeLatest(REHYDRATE, authCheckSaga); - yield takeEvery(USER_ACTIONS.SET_MODE, setModeSaga); yield takeEvery(USER_ACTIONS.START_EDITING, startEditingSaga); yield takeEvery(USER_ACTIONS.STOP_EDITING, stopEditingSaga); yield takeEvery(USER_ACTIONS.USER_LOGOUT, userLogoutSaga); - yield takeEvery(USER_ACTIONS.SET_ACTIVE_STICKER, setActiveStickerSaga); - yield takeEvery(USER_ACTIONS.SET_LOGO, setLogoSaga); - yield takeEvery(USER_ACTIONS.ROUTER_CANCEL, routerCancelSaga); - yield takeEvery(USER_ACTIONS.ROUTER_SUBMIT, routerSubmitSaga); - yield takeEvery( - [ - USER_ACTIONS.CLEAR_POLY, - USER_ACTIONS.CLEAR_STICKERS, - USER_ACTIONS.CLEAR_ALL, - USER_ACTIONS.CLEAR_CANCEL - ], - clearSaga - ); + // yield takeEvery(USER_ACTIONS.ROUTER_CANCEL, routerCancelSaga); + // yield takeEvery(USER_ACTIONS.ROUTER_SUBMIT, routerSubmitSaga); - yield takeLatest(USER_ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga); - yield takeLatest(USER_ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga); yield takeLatest(USER_ACTIONS.TAKE_A_SHOT, takeAShotSaga); yield takeLatest(USER_ACTIONS.CROP_A_SHOT, cropAShotSaga); - yield takeEvery(USER_ACTIONS.CHANGE_PROVIDER, changeProviderSaga); + // yield takeEvery(USER_ACTIONS.CHANGE_PROVIDER, changeProviderSaga); yield takeLatest(USER_ACTIONS.LOCATION_CHANGED, locationChangeSaga); yield takeLatest(USER_ACTIONS.GOT_VK_USER, gotVkUserSaga); yield takeLatest(USER_ACTIONS.KEY_PRESSED, keyPressedSaga); - yield takeLatest(USER_ACTIONS.SET_TITLE, setTitleSaga); + // yield takeLatest(USER_ACTIONS.SET_TITLE, setTitleSaga); yield takeLatest( [USER_ACTIONS.SEARCH_SET_TITLE, USER_ACTIONS.SEARCH_SET_DISTANCE], diff --git a/src/redux/user/selectors.ts b/src/redux/user/selectors.ts index 5fec11b..b656f50 100644 --- a/src/redux/user/selectors.ts +++ b/src/redux/user/selectors.ts @@ -1,5 +1,7 @@ import { IState } from '$redux/store' +export const selectUser = (state: IState) => state.user; +export const selectUserUser = (state: IState) => state.user.user; export const selectUserEditing = (state: IState) => state.user.editing; export const selectUserMode = (state: IState) => state.user.mode; export const selectUserActiveSticker = (state: IState) => state.user.activeSticker; \ No newline at end of file diff --git a/src/styles/map.less b/src/styles/map.less index dc7e580..17fffb2 100644 --- a/src/styles/map.less +++ b/src/styles/map.less @@ -285,7 +285,7 @@ } #canvas { - background: #ff3344; + background: #eeeeee; z-index: 0; > div { diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 58f7158..aa3ad45 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -1,53 +1,72 @@ -import { editor } from '$modules/Editor'; +// import { editor } from '$modules/Editor'; import { COLORS, CLIENT } from '$config/frontend'; import * as saveAs from 'file-saver'; import { replaceProviderUrl } from '$constants/providers'; import { STICKERS } from '$constants/stickers'; -import { ILatLng } from "$modules/Stickers"; -import { IStickerDump } from "$modules/Sticker"; -import { IRootState } from "$redux/user"; -import { angleBetweenPoints, angleBetweenPointsRad, findDistancePx, middleCoordPx } from "$utils/geom"; -import { Point } from "leaflet"; +import { ILatLng } from '$modules/Stickers'; +import { IStickerDump } from '$modules/Sticker'; +import { IRootState } from '$redux/user'; +import { + angleBetweenPoints, + angleBetweenPointsRad, + findDistancePx, + middleCoordPx, +} from '$utils/geom'; +import { Point } from 'leaflet'; +import { MainMap } from '$containers/map/Map'; export interface ITilePlacement { - minX: number, - maxX: number, - minY: number, - maxY: number, - shiftX: number, - shiftY: number, - size: number, - width: number, - height: number, - zoom: number, + minX: number; + maxX: number; + minY: number; + maxY: number; + shiftX: number; + shiftY: number; + size: number; + width: number; + height: number; + zoom: number; } export interface IStickerPlacement extends IStickerDump { - x: number, - y: number, + x: number; + y: number; } -const latLngToTile = (latlng: { lat: number, lng: number }): { x: number, y: number, z: number } => { - const { map } = editor.map; +const latLngToTile = (latlng: { + lat: number; + lng: number; +}): { x: number; y: number; z: number } => { + const map = MainMap; const zoom = map.getZoom(); - const xtile = Number(Math.floor((latlng.lng + 180) / 360 * (1 << zoom))); - const ytile = Number(Math.floor((1 - Math.log(Math.tan(latlng.lat * Math.PI / 180) + 1 / Math.cos(latlng.lat * Math.PI / 180)) / Math.PI) / 2 * (1 << zoom))); + const xtile = Number(Math.floor(((latlng.lng + 180) / 360) * (1 << zoom))); + const ytile = Number( + Math.floor( + ((1 - + Math.log( + Math.tan((latlng.lat * Math.PI) / 180) + 1 / Math.cos((latlng.lat * Math.PI) / 180) + ) / + Math.PI) / + 2) * + (1 << zoom) + ) + ); return { x: xtile, y: ytile, z: zoom }; }; -const tileToLatLng = (point: { x: number, y: number }): ILatLng => { - const { map } = editor.map; +const tileToLatLng = (point: { x: number; y: number }): ILatLng => { + const map = MainMap; const z = map.getZoom(); - const lng = (point.x / Math.pow(2, z) * 360 - 180); - const n = Math.PI - 2 * Math.PI * point.y / Math.pow(2, z); - const lat = (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))); + const lng = (point.x / Math.pow(2, z)) * 360 - 180; + const n = Math.PI - (2 * Math.PI * point.y) / Math.pow(2, z); + const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); return { lat, lng }; }; export const getTilePlacement = (): ITilePlacement => { - const { map } = editor.map; + const map = MainMap; const width = window.innerWidth; const height = window.innerHeight; @@ -75,7 +94,7 @@ export const getTilePlacement = (): ITilePlacement => { minY, maxY, shiftX: tileTransformTranslate.x - msw2.x, - shiftY: ((maxY - minY) * 256) - (window.innerHeight + (tileTransformTranslate.y - msw2.y)), + shiftY: (maxY - minY) * 256 - (window.innerHeight + (tileTransformTranslate.y - msw2.y)), size: 256, width, height, @@ -83,70 +102,79 @@ export const getTilePlacement = (): ITilePlacement => { }; }; -export const getPolyPlacement = (): Point[] => ( - (!editor.poly.poly || !editor.poly.poly.getLatLngs() || editor.poly.poly.getLatLngs().length <= 0) +export const getPolyPlacement = (latlngs: ILatLng[]): Point[] => + latlngs.length === 0 ? [] - : editor.poly.poly.getLatLngs().map((latlng) => ({ ...editor.map.map.latLngToContainerPoint(latlng) })) -); + : latlngs.map(latlng => (MainMap.latLngToContainerPoint(latlng))); -export const getStickersPlacement = (): IStickerPlacement[] => ( - (!editor.stickers || editor.stickers.dumpData().length <= 0) +export const getStickersPlacement = (stickers: IStickerDump[]): IStickerPlacement[] => + stickers.length === 0 ? [] - : editor.stickers.dumpData().map(sticker => ({ - ...sticker, - ...editor.map.map.latLngToContainerPoint(sticker.latlng), - })) -); + : stickers.map(sticker => ({ + ...sticker, + ...MainMap.latLngToContainerPoint(sticker.latlng), + })); -const getImageSource = (coords: { x: number, y: number, zoom: number }): string => ( - replaceProviderUrl(editor.getProvider(), coords) -); +const getImageSource = (coords: { x: number; y: number; zoom: number }, provider: string): string => + replaceProviderUrl(provider, coords); -export const imageFetcher = (source: string): Promise => new Promise((resolve, reject) => { - const img = new Image(); +export const imageFetcher = (source: string): Promise => + new Promise((resolve, reject) => { + const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => resolve(img); - img.onerror = () => reject(img); + img.crossOrigin = 'anonymous'; + img.onload = () => resolve(img); + img.onerror = () => reject(img); - img.src = source; -}); + img.src = source; + }); -export const fetchImages = (ctx: CanvasRenderingContext2D, geometry: ITilePlacement ): Promise<{ x: number, y: number, image: HTMLImageElement }[]> => { - const { - minX, maxX, minY, maxY, zoom - } = geometry; +export const fetchImages = ( + ctx: CanvasRenderingContext2D, + geometry: ITilePlacement, + provider, +): Promise<{ x: number; y: number; image: HTMLImageElement }[]> => { + const { minX, maxX, minY, maxY, zoom } = geometry; const images = []; for (let x = minX; x <= maxX; x += 1) { for (let y = minY; y <= maxY; y += 1) { - images.push({ x, y, source: getImageSource({ x, y, zoom }) }); + images.push({ x, y, source: getImageSource({ x, y, zoom }, provider) }); } } - return Promise.all(images.map(({ x, y, source }) => ( - imageFetcher(source).then(image => ({ x, y, image })) - ))); + return Promise.all( + images.map(({ x, y, source }) => imageFetcher(source).then(image => ({ x, y, image }))) + ); }; -export const composeImages = ( - { images, geometry, ctx }: - { images: [], geometry: ITilePlacement, ctx: CanvasRenderingContext2D } -): void => { - const { - minX, minY, shiftX, shiftY, size - } = geometry; +export const composeImages = ({ + images, + geometry, + ctx, +}: { + images: []; + geometry: ITilePlacement; + ctx: CanvasRenderingContext2D; +}): void => { + const { minX, minY, shiftX, shiftY, size } = geometry; images.map(({ x, y, image }) => { - const posX = ((x - minX) * size) + shiftX; - const posY = ((y - minY) * size) - shiftY; + const posX = (x - minX) * size + shiftX; + const posY = (y - minY) * size - shiftY; return ctx.drawImage(image, posX, posY, 256, 256); }); }; -export const composePoly = ({ points, ctx }: { points: Point[], ctx: CanvasRenderingContext2D }): void => { - if (editor.poly.isEmpty) return; +export const composePoly = ({ + points, + ctx, +}: { + points: Point[]; + ctx: CanvasRenderingContext2D; +}): void => { + if (points.length === 0) return; let minX = points[0].x; let maxX = points[0].x; @@ -180,16 +208,22 @@ export const composePoly = ({ points, ctx }: { points: Point[], ctx: CanvasRende ctx.closePath(); }; -export const composeArrows = async ({ points, ctx }: { points: Point[], ctx: CanvasRenderingContext2D }): Promise => { +export const composeArrows = async ({ + points, + ctx, +}: { + points: Point[]; + ctx: CanvasRenderingContext2D; +}): Promise => { const image = await imageFetcher(require('$sprites/arrow.svg')); - const distances = points.map((point, i) => ( - (points[i + 1] && findDistancePx(points[i], points[i + 1])) || 0 - )); + const distances = points.map( + (point, i) => (points[i + 1] && findDistancePx(points[i], points[i + 1])) || 0 + ); // we want to annotate at least 5 arrows - const min_arrows = (distances.length >= 5 ? 5 : distances.length - 1); - const min_distance = distances.sort((a, b) => (b - a))[min_arrows]; + const min_arrows = distances.length >= 5 ? 5 : distances.length - 1; + const min_distance = distances.sort((a, b) => b - a)[min_arrows]; return points.map((point, i) => { if (!points[i + 1]) return false; @@ -203,7 +237,7 @@ export const composeArrows = async ({ points, ctx }: { points: Point[], ctx: Can ctx.save(); ctx.translate(middle.x, middle.y); - ctx.rotate((Math.PI * 0.5) - angle); + ctx.rotate(Math.PI * 0.5 - angle); ctx.translate(-middle.x, -middle.y); ctx.moveTo(middle.x, middle.y); @@ -220,35 +254,33 @@ const measureText = ( ctx: CanvasRenderingContext2D, text: string, lineHeight: number -): { width: number, height: number } => ( - text.split('\n').reduce((obj, line) => ( - { +): { width: number; height: number } => + text.split('\n').reduce( + (obj, line) => ({ width: Math.max(ctx.measureText(line).width, obj.width), height: obj.height + lineHeight, - } - ), { width: 0, height: 0 }) -); + }), + { width: 0, height: 0 } + ); const measureDistText = ( ctx: CanvasRenderingContext2D, text: string, height: number -): { width: number, height: number } => ( - { - width: ctx.measureText(text).width, - height, - } -); +): { width: number; height: number } => ({ + width: ctx.measureText(text).width, + height, +}); const composeStickerArrow = ( ctx: CanvasRenderingContext2D, x: number, y: number, - angle: number, + angle: number ) => { ctx.save(); ctx.translate(x, y); - ctx.rotate(angle + (Math.PI * 0.75)); + ctx.rotate(angle + Math.PI * 0.75); ctx.translate(-x, -y); ctx.fillStyle = '#ff3344'; ctx.beginPath(); @@ -261,18 +293,12 @@ const composeStickerArrow = ( ctx.restore(); }; -const measureRect = ( - x: number, - y: number, - width: number, - height: number, - reversed: boolean, -) => ({ - rectX: reversed ? (x - width - 36 - 10) : x, - rectY: (y - 7 - (height / 2)), +const measureRect = (x: number, y: number, width: number, height: number, reversed: boolean) => ({ + rectX: reversed ? x - width - 36 - 10 : x, + rectY: y - 7 - height / 2, rectW: width + 36 + 10, rectH: height + 20, - textX: reversed ? (x - width - 36) : x + 36 + textX: reversed ? x - width - 36 : x + 36, }); const measureDistRect = ( @@ -280,16 +306,24 @@ const measureDistRect = ( y: number, width: number, height: number, - reversed: boolean, + reversed: boolean ) => ({ - rectX: reversed ? (x - width - 12) : x - 8, - rectY: (y - 2 - (height / 2)), - rectW: reversed ? (width + 22) : (width + 20), + rectX: reversed ? x - width - 12 : x - 8, + rectY: y - 2 - height / 2, + rectW: reversed ? width + 22 : width + 20, rectH: height + 4, - textX: reversed ? (x - width - 8) : x + 8 + textX: reversed ? x - width - 8 : x + 8, }); -export const roundedRect = (ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, fill: string, rad: number = 5): void => { +export const roundedRect = ( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + fill: string, + rad: number = 5 +): void => { ctx.fillStyle = fill; ctx.beginPath(); ctx.moveTo(x, y + rad); @@ -317,7 +351,6 @@ export const roundedRect = (ctx: CanvasRenderingContext2D, x: number, y: number, ctx.closePath(); ctx.fill(); - }; const composeStickerText = ( @@ -325,19 +358,23 @@ const composeStickerText = ( x: number, y: number, angle: number, - text: string, + text: string ): void => { const rad = 56; - const tX = ((Math.cos(angle + Math.PI) * rad) - 30) + x + 28; - const tY = ((Math.sin(angle + Math.PI) * rad) - 30) + y + 29; + const tX = Math.cos(angle + Math.PI) * rad - 30 + x + 28; + const tY = Math.sin(angle + Math.PI) * rad - 30 + y + 29; ctx.font = '12px "Helvetica Neue", Arial'; const lines = text.split('\n'); const { width, height } = measureText(ctx, text, 16); - const { - rectX, rectY, rectW, rectH, textX - } = measureRect(tX, tY, width, height, (angle > -(Math.PI / 2) && angle < (Math.PI / 2))); + const { rectX, rectY, rectW, rectH, textX } = measureRect( + tX, + tY, + width, + height, + angle > -(Math.PI / 2) && angle < Math.PI / 2 + ); // rectangle // ctx.fillStyle = '#222222'; // ctx.beginPath(); @@ -353,23 +390,18 @@ const composeStickerText = ( // text ctx.fillStyle = 'white'; - lines.map((line, i) => ( - ctx.fillText( - line, - textX, - rectY + 6 + (16 * (i + 1)), - ) - )); + lines.map((line, i) => ctx.fillText(line, textX, rectY + 6 + 16 * (i + 1))); }; -export const composeDistMark = ( - { ctx, points, distance }: - { - ctx: CanvasRenderingContext2D, - points: Point[], - distance: number, - } -): void => { +export const composeDistMark = ({ + ctx, + points, + distance, +}: { + ctx: CanvasRenderingContext2D; + points: Point[]; + distance: number; +}): void => { if (points.length <= 1) return; ctx.font = 'bold 12px "Helvetica Neue", Arial'; @@ -378,9 +410,13 @@ export const composeDistMark = ( const dist = parseFloat(distance.toFixed(1)); const { x, y } = points[points.length - 1]; const { width, height } = measureDistText(ctx, String(dist), 16); - const { - rectX, rectY, rectW, rectH, textX - } = measureDistRect(x, y, width, height, !(angle > -90 && angle < 90)); + const { rectX, rectY, rectW, rectH, textX } = measureDistRect( + x, + y, + width, + height, + !(angle > -90 && angle < 90) + ); roundedRect(ctx, rectX, rectY, rectW, rectH, '#ff3344', 2); @@ -390,11 +426,7 @@ export const composeDistMark = ( ctx.closePath(); ctx.fill(); - ctx.fillText( - String(dist), - textX, - rectY + 14, - ); + ctx.fillText(String(dist), textX, rectY + 14); }; const composeStickerImage = async ( @@ -403,22 +435,25 @@ const composeStickerImage = async ( y: number, angle: number, set: IRootState['activeSticker']['set'], - sticker: IRootState['activeSticker']['sticker'], + sticker: IRootState['activeSticker']['sticker'] ): Promise => { const rad = 56; - const tX = ((Math.cos(angle + Math.PI) * rad) - 30) + (x - 8); - const tY = ((Math.sin(angle + Math.PI) * rad) - 30) + (y - 4); + const tX = Math.cos(angle + Math.PI) * rad - 30 + (x - 8); + const tY = Math.sin(angle + Math.PI) * rad - 30 + (y - 4); const offsetX = STICKERS[set].layers[sticker].off * 72; - return imageFetcher(STICKERS[set].url).then(image => ( + return imageFetcher(STICKERS[set].url).then(image => ctx.drawImage(image, offsetX, 0, 72, 72, tX, tY, 72, 72) - )); + ); }; -export const composeStickers = async ( - { stickers, ctx }: - { stickers: IStickerPlacement[], ctx: CanvasRenderingContext2D } -): Promise => { +export const composeStickers = async ({ + stickers, + ctx, +}: { + stickers: IStickerPlacement[]; + ctx: CanvasRenderingContext2D; +}): Promise => { if (!stickers || stickers.length < 0) return; stickers.map(({ x, y, angle, text }) => { @@ -427,12 +462,12 @@ export const composeStickers = async ( if (text) composeStickerText(ctx, x, y, angle, text); }); - await Promise.all(stickers.map(({ - x, y, angle, set, sticker - }) => ( - composeStickerImage(ctx, x, y, angle, set, sticker) - ))); + await Promise.all( + stickers.map(({ x, y, angle, set, sticker }) => + composeStickerImage(ctx, x, y, angle, set, sticker) + ) + ); }; -export const downloadCanvas = (canvas: HTMLCanvasElement, title: IRootState['title']): void => canvas.toBlob(blob => saveAs(blob, title)); - +export const downloadCanvas = (canvas: HTMLCanvasElement, title: IRootState['title']): void => + canvas.toBlob(blob => saveAs(blob, title));