starred content

This commit is contained in:
muerwre 2019-03-21 17:58:38 +07:00
parent a920217959
commit baa41f707d
19 changed files with 159 additions and 22 deletions

View file

@ -13,6 +13,7 @@ const RouteSchema = new Schema(
owner: { type: Schema.Types.ObjectId, ref: 'User' }, owner: { type: Schema.Types.ObjectId, ref: 'User' },
distance: { type: Number, default: 0 }, distance: { type: Number, default: 0 },
is_public: { type: Boolean, default: false }, is_public: { type: Boolean, default: false },
is_starred: { type: Boolean, default: false },
is_deleted: { type: Boolean, default: false }, is_deleted: { type: Boolean, default: false },
created_at: { type: Date, default: Date.now() }, created_at: { type: Date, default: Date.now() },
updated_at: { type: Date, default: Date.now() }, updated_at: { type: Date, default: Date.now() },

View file

@ -4,9 +4,11 @@ const get = require('./route/get');
const list = require('./route/list'); const list = require('./route/list');
const drop = require('./route/drop'); const drop = require('./route/drop');
const patch = require('./route/patch'); const patch = require('./route/patch');
const star = require('./route/star');
const router = express.Router(); const router = express.Router();
router.post('/star', star);
router.post('/', post); router.post('/', post);
router.get('/', get); router.get('/', get);
router.patch('/', patch); router.patch('/', patch);

View file

@ -3,10 +3,12 @@ const { Route, User } = require('../../models');
module.exports = async (req, res) => { module.exports = async (req, res) => {
const { const {
query: { query: {
id, token, title, distance, author, step = 20, shift = 0, id, token, title, distance, author, step = 20, shift = 0, starred,
} }
} = req; } = req;
const is_starred = parseInt(starred, 10) === 1;
const user = await User.findOne({ _id: id, token }); const user = await User.findOne({ _id: id, token });
let criteria = { is_deleted: false }; let criteria = { is_deleted: false };
@ -14,13 +16,21 @@ module.exports = async (req, res) => {
if (title) { if (title) {
criteria = { criteria = {
...criteria, ...criteria,
$or: [ $and: [
{ title: new RegExp(title.trim(), 'ig') }, { title: new RegExp(title.trim(), 'ig') },
{ _id: new RegExp(title.trim(), 'ig') }, { _id: new RegExp(title.trim(), 'ig') },
], ],
}; };
} }
console.log('is starred?', starred);
if (is_starred) {
criteria = {
...criteria,
is_starred: true,
};
}
if (!author || !user || (user._id !== author)) { if (!author || !user || (user._id !== author)) {
criteria = { criteria = {
...criteria, ...criteria,
@ -32,7 +42,7 @@ module.exports = async (req, res) => {
{ {
...criteria, ...criteria,
}, },
'_id title distance owner updated_at is_public is_deleted', '_id title distance owner updated_at is_public is_deleted is_starred',
{ {
limit: 9000, limit: 9000,
sort: { updated_at: -1 }, sort: { updated_at: -1 },

View file

@ -17,7 +17,7 @@ module.exports = async (req, res) => {
if (!exists) return res.send({ success: false, mode: 'not_exists' }); if (!exists) return res.send({ success: false, mode: 'not_exists' });
if (exists && exists.owner._id !== id) return res.send({ success: false, mode: 'not_yours' }); if (exists && exists.owner._id !== id) return res.send({ success: false, mode: 'not_yours' });
exists.set({ title, is_public }).save(); await exists.set({ title, is_public }).save();
return res.send({ success: true, ...exists }); return res.send({ success: true, ...exists });
}; };

View file

@ -27,7 +27,7 @@ module.exports = async (req, res) => {
if (exists && !force) return res.send({ success: false, mode: 'overwriting' }); if (exists && !force) return res.send({ success: false, mode: 'overwriting' });
if (exists) { if (exists) {
exists.set({ await exists.set({
title, route, stickers, logo, distance, updated_at: Date.now(), provider, is_public, title, route, stickers, logo, distance, updated_at: Date.now(), provider, is_public,
}).save(); }).save();

View file

@ -0,0 +1,19 @@
const { User, Route } = require('../../models');
module.exports = async (req, res) => {
const { body, body: { id, token, address } } = req;
const owner = await User.findOne({ _id: id, token }).populate('routes');
if (!owner || owner.role !== 'admin') return res.send({ success: false, reason: 'unauthorized' });
const is_starred = !!body.is_starred;
const exists = await Route.findOne({ _id: address }).populate('owner', '_id');
if (!exists) return res.send({ success: false, mode: 'not_exists' });
await exists.set({ is_starred }).save();
return res.send({ success: true, ...exists });
};

View file

@ -10,7 +10,7 @@ import {
setDialogActive, setDialogActive,
mapsLoadMore, mapsLoadMore,
dropRoute, dropRoute,
modifyRoute, modifyRoute, toggleRouteStarred,
} from '$redux/user/actions'; } from '$redux/user/actions';
import { isMobile } from '$utils/window'; import { isMobile } from '$utils/window';
import classnames from 'classnames'; import classnames from 'classnames';
@ -21,12 +21,14 @@ import { TABS } from '$constants/dialogs';
import { Icon } from '$components/panels/Icon'; import { Icon } from '$components/panels/Icon';
import { pushPath } from '$utils/history'; import { pushPath } from '$utils/history';
import { IRootState, IRouteListItem } from '$redux/user/reducer'; import { IRootState, IRouteListItem } from '$redux/user/reducer';
import { ROLES } from "$constants/auth";
export interface IMapListDialogProps extends IRootState { export interface IMapListDialogProps extends IRootState {
marks: { [x: number]: string }, marks: { [x: number]: string },
routes_sorted: Array<IRouteListItem>, routes_sorted: Array<IRouteListItem>,
routes: IRootState['routes'], routes: IRootState['routes'],
ready: IRootState['ready'], ready: IRootState['ready'],
role: IRootState['user']['role'],
mapsLoadMore: typeof mapsLoadMore, mapsLoadMore: typeof mapsLoadMore,
searchSetDistance: typeof searchSetDistance, searchSetDistance: typeof searchSetDistance,
@ -35,6 +37,7 @@ export interface IMapListDialogProps extends IRootState {
setDialogActive: typeof setDialogActive, setDialogActive: typeof setDialogActive,
dropRoute: typeof dropRoute, dropRoute: typeof dropRoute,
modifyRoute: typeof modifyRoute, modifyRoute: typeof modifyRoute,
toggleRouteStarred: typeof toggleRouteStarred,
} }
export interface IMapListDialogState { export interface IMapListDialogState {
@ -117,9 +120,12 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
this.stopEditing(); this.stopEditing();
}; };
toggleStarred = (id: string) => this.props.toggleRouteStarred(id);
render() { render() {
const { const {
ready, ready,
role,
routes: { routes: {
list, list,
loading, loading,
@ -136,6 +142,8 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
const { editor_target, menu_target, is_editing, is_dropping } = this.state; const { editor_target, menu_target, is_editing, is_dropping } = this.state;
console.log('role', this.props.role);
return ( return (
<div className="dialog-content"> <div className="dialog-content">
{ list.length === 0 && loading && { list.length === 0 && loading &&
@ -156,7 +164,7 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
} }
<div className="dialog-tabs"> <div className="dialog-tabs">
{ {
Object.keys(TABS).map(item => ( Object.keys(TABS).map(item => (role === ROLES.admin || item !== 'all') && (
<div <div
className={classnames('dialog-tab', { active: tab === item })} className={classnames('dialog-tab', { active: tab === item })}
onClick={() => this.props.searchSetTab(item)} onClick={() => this.props.searchSetTab(item)}
@ -208,6 +216,7 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
distance={route.distance} distance={route.distance}
_id={route._id} _id={route._id}
is_public={route.is_public} is_public={route.is_public}
is_starred={route.is_starred}
tab={tab} tab={tab}
is_editing_mode={is_dropping ? 'drop' : 'edit'} is_editing_mode={is_dropping ? 'drop' : 'edit'}
is_editing_target={editor_target === route._id} is_editing_target={editor_target === route._id}
@ -220,7 +229,9 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
showDropCard={this.showDropCard} showDropCard={this.showDropCard}
dropRoute={this.dropRoute} dropRoute={this.dropRoute}
modifyRoute={this.modifyRoute} modifyRoute={this.modifyRoute}
toggleStarred={this.toggleStarred}
key={route._id} key={route._id}
is_admin={role === ROLES.admin}
/> />
)) ))
} }
@ -233,13 +244,15 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
} }
} }
const mapStateToProps = ({ user: { editing, routes } }) => { const mapStateToProps = ({ user: { editing, routes, user: { role } } }: { user: IRootState }) => {
if (routes.filter.max >= 9999) { if (routes.filter.max >= 9999) {
return { return {
routes, editing, marks: {}, ready: false, routes, editing, marks: {}, ready: false, role,
}; };
} }
return ({ return ({
role,
routes, routes,
editing, editing,
ready: true, ready: true,
@ -260,6 +273,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({
mapsLoadMore, mapsLoadMore,
dropRoute, dropRoute,
modifyRoute, modifyRoute,
toggleRouteStarred,
}, dispatch); }, dispatch);
export const MapListDialog = connect(mapStateToProps, mapDispatchToProps)(Component); export const MapListDialog = connect(mapStateToProps, mapDispatchToProps)(Component);

View file

@ -5,15 +5,20 @@ import { MapListDialog } from "$components/dialogs/MapListDialog";
import { Tooltip } from "$components/panels/Tooltip"; import { Tooltip } from "$components/panels/Tooltip";
import { ReactElement } from "react"; import { ReactElement } from "react";
import classnames from 'classnames'; import classnames from 'classnames';
import { toggleRouteStarred } from "$redux/user/actions";
interface Props { interface Props {
_id: string,
tab: string, tab: string,
_id: string,
title: string, title: string,
distance: number, distance: number,
is_public: boolean, is_public: boolean,
is_admin: boolean,
is_starred: boolean,
openRoute: typeof MapListDialog.openRoute, openRoute: typeof MapListDialog.openRoute,
toggleStarred: typeof MapListDialog.toggleStarred,
startEditing: typeof MapListDialog.startEditing, startEditing: typeof MapListDialog.startEditing,
stopEditing: typeof MapListDialog.stopEditing, stopEditing: typeof MapListDialog.stopEditing,
showMenu: typeof MapListDialog.showMenu, showMenu: typeof MapListDialog.showMenu,
@ -22,11 +27,21 @@ interface Props {
} }
export const RouteRowView = ({ export const RouteRowView = ({
title, distance, _id, openRoute, tab, startEditing, showMenu, showDropCard, hideMenu, title, distance, _id, openRoute, tab, startEditing, showMenu, showDropCard, hideMenu, is_admin, is_starred, toggleStarred,
}: Props): ReactElement<Props, null> => ( }: Props): ReactElement<Props, null> => (
<div <div
className={classnames('route-row-view', { has_menu: (tab === 'mine') })} className={classnames('route-row-view', { has_menu: (tab === 'mine') })}
> >
{
(tab === 'all' || tab === 'starred') && is_admin &&
<div className="route-row-fav" onClick={toggleStarred.bind(null, _id)}>
{
is_starred
? <Icon icon="icon-star-fill" size={24}/>
: <Icon icon="icon-star-blank" size={24}/>
}
</div>
}
<div <div
className="route-row" className="route-row"
onClick={() => openRoute(_id)} onClick={() => openRoute(_id)}

View file

@ -12,7 +12,9 @@ interface Props {
title: string, title: string,
distance: number, distance: number,
is_public: boolean, is_public: boolean,
is_starred: boolean,
is_admin: boolean,
is_editing_target: boolean, is_editing_target: boolean,
is_menu_target: boolean, is_menu_target: boolean,
@ -24,6 +26,7 @@ interface Props {
showDropCard: typeof MapListDialog.showDropCard, showDropCard: typeof MapListDialog.showDropCard,
dropRoute: typeof MapListDialog.dropRoute, dropRoute: typeof MapListDialog.dropRoute,
modifyRoute: typeof MapListDialog.modifyRoute, modifyRoute: typeof MapListDialog.modifyRoute,
toggleStarred: typeof MapListDialog.toggleStarred,
is_editing_mode: 'edit' | 'drop', is_editing_mode: 'edit' | 'drop',
} }
@ -31,7 +34,7 @@ interface Props {
export const RouteRowWrapper = ({ export const RouteRowWrapper = ({
title, distance, _id, openRoute, tab, startEditing, showMenu, title, distance, _id, openRoute, tab, startEditing, showMenu,
showDropCard, is_public, is_editing_target, is_menu_target, is_editing_mode, showDropCard, is_public, is_editing_target, is_menu_target, is_editing_mode,
dropRoute, stopEditing, modifyRoute, hideMenu, dropRoute, stopEditing, modifyRoute, hideMenu, is_admin, is_starred, toggleStarred,
}: Props): ReactElement<Props, null> => ( }: Props): ReactElement<Props, null> => (
<div <div
className={classnames('route-row-wrapper', { className={classnames('route-row-wrapper', {
@ -64,12 +67,15 @@ export const RouteRowWrapper = ({
title={title} title={title}
distance={distance} distance={distance}
is_public={is_public} is_public={is_public}
is_starred={is_starred}
openRoute={openRoute} openRoute={openRoute}
startEditing={startEditing} startEditing={startEditing}
stopEditing={stopEditing} stopEditing={stopEditing}
showMenu={showMenu} showMenu={showMenu}
hideMenu={hideMenu} hideMenu={hideMenu}
showDropCard={showDropCard} showDropCard={showDropCard}
is_admin={is_admin}
toggleStarred={toggleStarred}
/> />
} }
</div> </div>

View file

@ -10,4 +10,5 @@ export const API: { [x: string]: string } = {
DROP_ROUTE: `${CLIENT.API_ADDR}/route`, DROP_ROUTE: `${CLIENT.API_ADDR}/route`,
MODIFY_ROUTE: `${CLIENT.API_ADDR}/route/modify`, MODIFY_ROUTE: `${CLIENT.API_ADDR}/route/modify`,
SET_STARRED: `${CLIENT.API_ADDR}/route/star`,
}; };

View file

@ -1,6 +1,7 @@
export interface IRoles { export interface IRoles {
guest: string, guest: string,
vk: string, vk: string,
admin: string,
} }
export interface IUser { export interface IUser {
@ -24,6 +25,7 @@ export interface IUser {
export const ROLES: IRoles = { export const ROLES: IRoles = {
guest: 'guest', guest: 'guest',
vk: 'vk', vk: 'vk',
admin: 'admin',
}; };
export const DEFAULT_USER: IUser = { export const DEFAULT_USER: IUser = {

View file

@ -7,6 +7,7 @@ export interface IDialogs {
export interface IMapTabs { export interface IMapTabs {
mine: string, mine: string,
all: string, all: string,
starred: string,
} }
export const DIALOGS: IDialogs = ({ export const DIALOGS: IDialogs = ({
@ -17,5 +18,6 @@ export const DIALOGS: IDialogs = ({
export const TABS: IMapTabs = ({ export const TABS: IMapTabs = ({
mine: 'Мои', mine: 'Мои',
all: 'Общие', starred: 'Общие',
all: 'ВСЕ',
}); });

View file

@ -76,3 +76,5 @@ export const dropRoute = (_id: string) => ({ type: ACTIONS.DROP_ROUTE, _id });
export const modifyRoute = (_id: string, { title, is_public }: { title: string, is_public: boolean }) => ({ export const modifyRoute = (_id: string, { title, is_public }: { title: string, is_public: boolean }) => ({
type: ACTIONS.MODIFY_ROUTE, _id, title, is_public type: ACTIONS.MODIFY_ROUTE, _id, 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 });

View file

@ -81,4 +81,6 @@ export const ACTIONS: IActions = {
DROP_ROUTE: 'DROP_ROUTE', DROP_ROUTE: 'DROP_ROUTE',
MODIFY_ROUTE: 'MODIFY_ROUTE', MODIFY_ROUTE: 'MODIFY_ROUTE',
SET_ROUTE_STARRED: 'SET_ROUTE_STARRED',
TOGGLE_ROUTE_STARRED: 'TOGGLE_ROUTE_STARRED',
}; };

View file

@ -14,6 +14,7 @@ export interface IRouteListItem {
title: string, title: string,
distance: number, distance: number,
is_public: boolean, is_public: boolean,
is_starred: boolean,
updated_at: string, updated_at: string,
} }
@ -304,6 +305,18 @@ const setIsRouting: ActionHandler<typeof ActionCreators.setIsRouting> = (state,
is_routing, is_routing,
}); });
const setRouteStarred: ActionHandler<typeof ActionCreators.setRouteStarred> = (state, { _id, is_starred }) => ({
...state,
routes: {
...state.routes,
list: (
state.routes.list
.map(el => el._id === _id ? { ...el, is_starred } : el)
.filter(el => state.routes.filter.tab !== 'starred' || el.is_starred)
)
}
});
const HANDLERS = ({ const HANDLERS = ({
[ACTIONS.SET_USER]: setUser, [ACTIONS.SET_USER]: setUser,
[ACTIONS.SET_EDITING]: setEditing, [ACTIONS.SET_EDITING]: setEditing,
@ -348,6 +361,8 @@ const HANDLERS = ({
[ACTIONS.SET_FEATURE]: setFeature, [ACTIONS.SET_FEATURE]: setFeature,
[ACTIONS.SET_IS_ROUTING]: setIsRouting, [ACTIONS.SET_IS_ROUTING]: setIsRouting,
[ACTIONS.SET_ROUTE_STARRED]: setRouteStarred,
}); });
export const INITIAL_STATE: IRootReducer = { export const INITIAL_STATE: IRootReducer = {

View file

@ -6,7 +6,7 @@ import {
checkUserToken, dropRoute, checkUserToken, dropRoute,
getGuestToken, getRouteList, getGuestToken, getRouteList,
getStoredMap, modifyRoute, getStoredMap, modifyRoute,
postMap postMap, sendRouteStarred
} from '$utils/api'; } from '$utils/api';
import { import {
hideRenderer, hideRenderer,
@ -32,7 +32,7 @@ import {
setProvider, setProvider,
changeProvider, changeProvider,
setSaveLoading, setSaveLoading,
mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, setRouteStarred,
} from '$redux/user/actions'; } from '$redux/user/actions';
import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history'; import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history';
import { editor } from '$modules/Editor'; import { editor } from '$modules/Editor';
@ -51,7 +51,7 @@ import {
} from '$utils/renderer'; } from '$utils/renderer';
import { ILogos, LOGOS } from '$constants/logos'; import { ILogos, LOGOS } from '$constants/logos';
import { DEFAULT_PROVIDER } from '$constants/providers'; import { DEFAULT_PROVIDER } from '$constants/providers';
import { DIALOGS } from '$constants/dialogs'; import { DIALOGS, TABS } from '$constants/dialogs';
import * as ActionCreators from '$redux/user/actions'; import * as ActionCreators from '$redux/user/actions';
import { IRootState } from "$redux/user/reducer"; import { IRootState } from "$redux/user/reducer";
@ -501,7 +501,7 @@ function* searchGetRoutes() {
const { routes: { step, shift, filter: { title, distance, tab } } } = yield select(getState); const { routes: { step, shift, filter: { title, distance, tab } } } = yield select(getState);
const result = yield call(getRouteList, { return yield call(getRouteList, {
id, id,
token, token,
title, title,
@ -509,10 +509,8 @@ function* searchGetRoutes() {
step, step,
shift, shift,
author: tab === 'mine' ? id : '', author: tab === 'mine' ? id : '',
starred: tab === 'starred', starred: tab === 'starred' ? 1 : 0,
}); });
return result;
} }
function* searchSetSagaWorker() { function* searchSetSagaWorker() {
@ -697,6 +695,17 @@ function* modifyRouteSaga({ _id, title, is_public }: ReturnType<typeof ActionCre
return yield call(modifyRoute, { address: _id, id, token, title, is_public }); return yield call(modifyRoute, { address: _id, id, token, title, is_public });
} }
function* toggleRouteStarredSaga({ _id }: ReturnType<typeof ActionCreators.toggleRouteStarred>) {
const { routes: { list } } = yield select(getState);
const route = list.find(el => el._id === _id);
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 });
if (!result) return yield put(setRouteStarred(_id, route.is_starred));
}
export function* userSaga() { export function* userSaga() {
yield takeLatest(REHYDRATE, authCheckSaga); yield takeLatest(REHYDRATE, authCheckSaga);
yield takeEvery(ACTIONS.SET_MODE, setModeSaga); yield takeEvery(ACTIONS.SET_MODE, setModeSaga);
@ -744,4 +753,5 @@ export function* userSaga() {
yield takeLatest(ACTIONS.DROP_ROUTE, dropRouteSaga); yield takeLatest(ACTIONS.DROP_ROUTE, dropRouteSaga);
yield takeLatest(ACTIONS.MODIFY_ROUTE, modifyRouteSaga); yield takeLatest(ACTIONS.MODIFY_ROUTE, modifyRouteSaga);
yield takeLatest(ACTIONS.TOGGLE_ROUTE_STARRED, toggleRouteStarredSaga);
} }

View file

@ -384,6 +384,16 @@
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)"/> <path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)"/>
</g> </g>
<g id="icon-star-blank" stroke="none">
<path stroke="none" fill="black"/>
<path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)"/>
</g>
<g id="icon-star-fill" stroke="none">
<path stroke="none" fill="black"/>
<path d="m12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)"/>
</g>
<g id="icon-cluster-1" stroke="none"> <g id="icon-cluster-1" stroke="none">
<rect x="0" y="0" width="32" height="32" fill="black" stroke="none" /> <rect x="0" y="0" width="32" height="32" fill="black" stroke="none" />
<circle cx="10" cy="21" fill="white" r="4" /> <circle cx="10" cy="21" fill="white" r="4" />

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

View file

@ -196,7 +196,7 @@
} }
&.is_menu_target { &.is_menu_target {
.route-row { .route-row, .route-row-fav {
transform: translateX(-120px); transform: translateX(-120px);
} }
@ -222,6 +222,7 @@
overflow: hidden; overflow: hidden;
transition: height 500ms; transition: height 500ms;
position: relative; position: relative;
display: flex;
&.has_menu { &.has_menu {
padding-right: 32px; padding-right: 32px;
@ -285,6 +286,21 @@
} }
} }
.route-row-fav {
width: 32px;
display: flex;
align-items: center;
justify-content: center;
fill: fade(white, 30%);
background: fade(white, 5%);
cursor: pointer;
transition: background 250ms, transform 500ms;
&:hover {
background: fade(white, 10%);
}
}
.route-row-edit-menu { .route-row-edit-menu {
width: 0; width: 0;
height: 100%; height: 100%;

View file

@ -29,7 +29,7 @@ interface IGetRouteList {
author: IRootState['routes']['filter']['author'], author: IRootState['routes']['filter']['author'],
step: IRootState['routes']['step'], step: IRootState['routes']['step'],
shift: IRootState['routes']['step'], shift: IRootState['routes']['step'],
starred: IRootState['routes']['filter']['starred'], starred: number,
id: IRootState['user']['id'], id: IRootState['user']['id'],
token: IRootState['user']['token'], token: IRootState['user']['token'],
} }
@ -112,3 +112,13 @@ export const modifyRoute = (
): AxiosPromise<any> => ( ): AxiosPromise<any> => (
axios.patch(API.DROP_ROUTE, { address, id, token, title, is_public }) axios.patch(API.DROP_ROUTE, { address, id, token, title, is_public })
); );
export const sendRouteStarred = (
{ id, token, _id, is_starred }:
{ id: string, token: string, _id: string, is_starred: boolean }
): Promise<boolean> => (
axios.post(API.SET_STARRED, { id, token, address: _id, is_starred })
.then(() => true)
.catch(() => true)
);