diff --git a/backend/routes/route/list.js b/backend/routes/route/list.js index 1323136..bbc7ed3 100644 --- a/backend/routes/route/list.js +++ b/backend/routes/route/list.js @@ -28,21 +28,28 @@ module.exports = async (req, res) => { }; } - let list = await Route.find({ - ...criteria, - }, '_id title distance owner updated_at', { limit: 500 }).populate('owner'); + let list = await Route.find( + { + ...criteria, + }, + '_id title distance owner updated_at', + { + limit: 500, + sort: { updated_at: -1 }, + } + ).populate('owner'); list = list.filter(item => ( !author || item.owner._id === author )); let limits = list.reduce(({ min, max }, { distance: dist }) => ({ - min: Math.ceil(Math.min(dist, min) / 20) * 20, - max: Math.ceil(Math.max(dist, max) / 20) * 20, + min: Math.ceil(Math.min(dist, min) / 25) * 25, + max: Math.ceil(Math.max(dist, max) / 25) * 25, }), { min: 999999, max: 0 }); const minDist = parseInt(distance[0], 10); - const maxDist = parseInt(distance[1], 10); + const maxDist = parseInt(distance[1], 10) === 200 ? 99999 : parseInt(distance[1], 10); // const maxDist = parseInt(distance[1], 10) > parseInt(distance[0], 10) // ? parseInt(distance[1], 10) // : 10000; @@ -60,6 +67,8 @@ module.exports = async (req, res) => { limits = { min: 0, max: 0 }; } else if (limits.min === limits.max) { limits = { min: limits.max - 20, max: limits.max }; + } else if (limits.max > 200) { + limits = { min: limits.min, max: 200 }; } res.send({ diff --git a/src/components/dialogs/MapListMoreDialog.jsx b/src/components/dialogs/MapListMoreDialog.jsx index 097347c..30880e9 100644 --- a/src/components/dialogs/MapListMoreDialog.jsx +++ b/src/components/dialogs/MapListMoreDialog.jsx @@ -7,12 +7,16 @@ import { Scroll } from '$components/Scroll'; import { searchSetDistance, searchSetTitle, + searchSetTab, } from '$redux/user/actions'; - +import classnames from 'classnames'; import { Range } from 'rc-slider'; +import { TABS } from '$constants/dialogs'; +import { Icon } from '$components/panels/Icon'; type Props = { + ready: Boolean, routes: { limit: Number, loading: Boolean, // <-- maybe delete this @@ -30,6 +34,7 @@ type Props = { searchSetAuthor: Function, searchSetDistance: Function, searchSetTitle: Function, + searchSetTab: Function, }; class Component extends React.Component { @@ -37,26 +42,12 @@ class Component extends React.Component { this.props.searchSetTitle(value); }; - setDistanceMin = ({ target: { value } }) => { - console.log('A'); - const parsed = parseInt(value > 0 ? value : 0, 10); - const { distance } = this.props.routes.filter; - - this.props.searchSetDistance([parsed, distance[1] > parsed ? distance[1] : parsed]); - }; - - setDistanceMax = ({ target: { value } }) => { - console.log('B'); - const parsed = parseInt(value > 0 ? value : 0, 10); - const { distance } = this.props.routes.filter; - - this.props.searchSetDistance([distance[0], parsed > distance[0] ? parsed : distance[0]]); - }; - render() { const { + ready, routes: { list, + loading, filter: { min, max, @@ -71,10 +62,18 @@ class Component extends React.Component { return (
-
-
Мои
-
Общие
-
Выбранные
+
+ { + Object.keys(TABS).map(item => ( +
this.props.searchSetTab(item)} + key={item} + > + {TABS[item]} +
+ )) + }
@@ -87,17 +86,22 @@ class Component extends React.Component {
- = max} - /> + { + ready + ? + = max} + /> + :
+ }
@@ -112,6 +116,22 @@ class Component extends React.Component { /> )) } + { list.length === 0 && loading && +
+
+ +
+ Загрузка +
+ } + { ready && !loading && list.length === 0 && +
+
+ +
+ НИЧЕГО НЕ НАЙДЕНО +
+ }
@@ -119,18 +139,29 @@ class Component extends React.Component { } } -const mapStateToProps = ({ user: { editing, routes } }) => ({ - routes, - editing, - marks: [...new Array((routes.filter.max - routes.filter.min) / 20 + 1)].reduce((obj, el, i) => ({ - ...obj, - [routes.filter.min + (i * 20)]: String(routes.filter.min + (i * 20)), - }), {}), -}); +const mapStateToProps = ({ user: { editing, routes } }) => { + if (routes.filter.max >= 9999){ + return { + routes, editing, marks: {}, ready: false, + }; + } + return ({ + routes, + editing, + ready: true, + marks: [...new Array(Math.floor((routes.filter.max - routes.filter.min) / 25) + 1)].reduce((obj, el, i) => ({ + ...obj, + [routes.filter.min + (i * 25)]: + ` ${routes.filter.min + (i * 25)}${(routes.filter.min + (i * 25) >= 200) ? '+' : ''} + `, + }), {}), + }); +}; const mapDispatchToProps = dispatch => bindActionCreators({ searchSetDistance, searchSetTitle, + searchSetTab, }, dispatch); export const MapListMoreDialog = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/src/components/panels/UserPanel.jsx b/src/components/panels/UserPanel.jsx index a8d66c9..06e6e1c 100644 --- a/src/components/panels/UserPanel.jsx +++ b/src/components/panels/UserPanel.jsx @@ -4,7 +4,7 @@ import { GuestButton } from '$components/user/GuestButton'; import { DEFAULT_USER, ROLES } from '$constants/auth'; import { UserButton } from '$components/user/UserButton'; import { UserMenu } from '$components/user/UserMenu'; -import { setUser, userLogout, takeAShot, setDialog, gotVkUser, setDialogActive } from '$redux/user/actions'; +import { setUser, userLogout, takeAShot, setDialog, gotVkUser, setDialogActive, openMapDialog } from '$redux/user/actions'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import type { UserType } from '$constants/types'; @@ -24,6 +24,7 @@ type Props = { setDialogActive: Function, gotVkUser: Function, takeAShot: Function, + openMapDialog: Function, }; export class Component extends React.PureComponent { @@ -61,8 +62,9 @@ export class Component extends React.PureComponent { setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened }); openMapsDialog = () => { - this.props.setDialog(DIALOGS.MAP_LIST); - this.props.setDialogActive(this.props.dialog !== DIALOGS.MAP_LIST); + // this.props.setDialog(DIALOGS.MAP_LIST); + // this.props.setDialogActive(this.props.dialog !== DIALOGS.MAP_LIST); + this.props.openMapDialog('mine'); }; openAppInfoDialog = () => { @@ -146,6 +148,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({ setDialog, gotVkUser, setDialogActive, + openMapDialog, }, dispatch); export const UserPanel = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/src/constants/dialogs.js b/src/constants/dialogs.js index 65ff3ee..cd12143 100644 --- a/src/constants/dialogs.js +++ b/src/constants/dialogs.js @@ -4,3 +4,9 @@ export const DIALOGS = ({ MAP_LIST: 'MAP_LIST', APP_INFO: 'APP_INFO', }: { [key: String]: String }); + +export const TABS = ({ + mine: 'Мои', + all: 'Общие', + // starred: 'Выбранные', +}: { [key: String]: String }); diff --git a/src/redux/user/actions.js b/src/redux/user/actions.js index 55a1f64..333bd6d 100644 --- a/src/redux/user/actions.js +++ b/src/redux/user/actions.js @@ -41,6 +41,7 @@ export const setProvider = provider => ({ type: ACTIONS.SET_PROVIDER, provider } export const setDialog = dialog => ({ type: ACTIONS.SET_DIALOG, dialog }); export const setDialogActive = dialog_active => ({ type: ACTIONS.SET_DIALOG_ACTIVE, dialog_active }); +export const openMapDialog = tab => ({ type: ACTIONS.OPEN_MAP_DIALOG, tab }); export const locationChanged = location => ({ type: ACTIONS.LOCATION_CHANGED, location }); export const setReady = ready => ({ type: ACTIONS.SET_READY, ready }); diff --git a/src/redux/user/constants.js b/src/redux/user/constants.js index 1b5556f..31bfbe2 100644 --- a/src/redux/user/constants.js +++ b/src/redux/user/constants.js @@ -57,4 +57,6 @@ export const ACTIONS = ({ SEARCH_SET_TAB: 'SEARCH_SET_TAB', SEARCH_PUT_ROUTES: 'SEARCH_PUT_ROUTES', SEARCH_SET_LOADING: 'SEARCH_SET_LOADING', + + OPEN_MAP_DIALOG: 'OPEN_MAP_DIALOG', }: { [key: String]: String }); diff --git a/src/redux/user/reducer.js b/src/redux/user/reducer.js index 0035950..285ac4d 100644 --- a/src/redux/user/reducer.js +++ b/src/redux/user/reducer.js @@ -6,7 +6,7 @@ import { MODES } from '$constants/modes'; import { DEFAULT_LOGO } from '$constants/logos'; import { TIPS } from '$constants/tips'; import { DEFAULT_PROVIDER } from '$constants/providers'; -import { DIALOGS } from '$constants/dialogs'; +import { DIALOGS, TABS } from '$constants/dialogs'; const getEstimated = distance => { const time = (distance && (distance / 15)) || 0; @@ -117,28 +117,17 @@ const searchSetDistance = (state, { distance = [0, 9999] }) => ({ } }); -const searchSetTab = (state, { tab = 'mine' }) => ({ +const searchSetTab = (state, { tab = TABS[Object.keys(TABS)[0]] }) => ({ ...state, routes: { ...state.routes, filter: { ...state.routes.filter, - tab: ['mine', 'all', 'star'].indexOf(tab) >= 0 ? tab : 'mine', + tab: Object.keys(TABS).indexOf(tab) >= 0 ? tab : TABS[Object.keys(TABS)[0]], } } }); -const newDistCalc = ({ - distance, min, max, filter -}) => { - if (filter.min === filter.max) { - // slider was disabled - return [min, max]; - } - - // state.routes.filter.distance -}; - const searchPutRoutes = (state, { list = [], min, max }) => ({ ...state, routes: { @@ -218,8 +207,8 @@ export const INITIAL_STATE = { save_overwriting: false, save_processing: false, - dialog: DIALOGS.MAP_LIST, - dialog_active: true, + dialog: DIALOGS.NONE, + dialog_active: false, renderer: { data: '', @@ -237,11 +226,11 @@ export const INITIAL_STATE = { filter: { title: '', starred: false, - distance: [0, 99999], + distance: [0, 10000], author: '', tab: 'mine', min: 0, - max: 0, + max: 10000, } }, }; diff --git a/src/redux/user/sagas.js b/src/redux/user/sagas.js index d1517ac..4fdfadc 100644 --- a/src/redux/user/sagas.js +++ b/src/redux/user/sagas.js @@ -16,7 +16,8 @@ import { setMode, setReady, setRenderer, setSaveError, setSaveOverwrite, setSaveSuccess, setTitle, - setUser + searchSetTab, + setUser, setDialog, } from '$redux/user/actions'; import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history'; import { editor } from '$modules/Editor'; @@ -34,6 +35,7 @@ import { } from '$utils/renderer'; import { LOGOS } from '$constants/logos'; import { DEFAULT_PROVIDER } from '$constants/providers'; +import { DIALOGS } from '$constants/dialogs'; const getUser = state => (state.user.user); const getState = state => (state.user); @@ -433,10 +435,9 @@ function* keyPressedSaga({ key }): void { } } -function* searchSetSaga() { +function* searchSetSagaWorker() { const { id, token } = yield select(getUser); - yield delay(1000); - yield put(searchSetLoading(true)); + const { routes: { filter, filter: { title, distance, tab } } } = yield select(getState); const { list, min, max } = yield call(getRouteList, { @@ -468,6 +469,33 @@ function* searchSetSaga() { return yield put(searchSetLoading(false)); } +function* searchSetSaga() { + yield put(searchSetLoading(true)); + yield delay(500); + yield call(searchSetSagaWorker); +} + +function* openMapDialogSaga({ tab }) { + const { dialog_active, routes: { filter: { tab: current } } } = yield select(getState); + + if (dialog_active && tab === current) { + return yield put(setDialogActive(false)); + } + + yield put(searchSetTab(tab)); + yield put(setDialog(DIALOGS.MAP_LIST)); + yield put(setDialogActive(true)); + + return tab; +} + +function* searchSetTabSaga() { + yield put(searchSetDistance([0, 10000])); + yield put(searchPutRoutes({ list: [], min: 0, max: 10000 })); + + yield call(searchSetSaga); +} + export function* userSaga() { yield takeLatest(REHYDRATE, authCheckSaga); yield takeEvery(ACTIONS.SET_MODE, setModeSaga); @@ -505,4 +533,7 @@ export function* userSaga() { 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/sprites/icon.svg b/src/sprites/icon.svg index 1987e6d..9e90b19 100644 --- a/src/sprites/icon.svg +++ b/src/sprites/icon.svg @@ -348,8 +348,18 @@ + + + + + + + + + + - + diff --git a/src/styles/dialogs.less b/src/styles/dialogs.less index 38f45b2..042f31a 100644 --- a/src/styles/dialogs.less +++ b/src/styles/dialogs.less @@ -114,6 +114,44 @@ box-sizing: border-box; } +@keyframes pulse { + 0% { opacity: 0.5; } + 100% { opacity: 1; } +} + +@keyframes spin { + 0% { transform: rotate(0); } + 100% { transform: rotate(360deg); } +} + +.dialog-maplist-loader { + height: 60px; + display: flex; + margin-bottom: 10px; + text-transform: uppercase; + color: white; + align-items: center; + justify-content: center; + user-select: none; + position: relative; + opacity: 0.5; + padding-left: 60px; + + .spin { + animation: spin infinite reverse 2s linear; + } + .dialog-maplist-icon { + position: absolute; + left: 0; + + svg { + width: 60px; + height: 60px; + fill: white; + } + } +} + .route-row { margin-bottom: 10px; background: rgba(255, 255, 255, 0.05); @@ -168,20 +206,23 @@ color: white; } -.dialog-head-tabs { +.dialog-tabs { background: rgba(255, 255, 255, 0); border-radius: @panel_radius @panel_radius 0 0; height: 32px; + user-select: none; + flex-direction: row; - .dialog-head-tab { - height: 32px; + .dialog-tab { display: inline-flex; align-items: center; justify-content: center; color: white; - padding: 0 10px; + padding: 0 20px; cursor: pointer; border-radius: @panel_radius @panel_radius 0 0; + flex: 1; + height: 32px; &.active { background: rgba(255, 255, 255, 0.1); diff --git a/src/styles/slider.less b/src/styles/slider.less index 8503a7e..84ab8ef 100644 --- a/src/styles/slider.less +++ b/src/styles/slider.less @@ -319,3 +319,20 @@ border-top-color: @tooltip-arrow-color; } } + +.range-placeholder { + height: 40px; + padding: 15px 0; + box-sizing: border-box; + margin: 8px; + + &::after { + content: ' '; + display: block; + width: 100%; + background-color: rgba(0, 0, 0, 0.3); + height: 4px; + border-radius: 6px; + pointer-events: none; + } +}