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

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

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@ export interface IDialogs {
export interface IMapTabs {
mine: string,
all: string,
starred: string,
}
export const DIALOGS: IDialogs = ({
@ -17,5 +18,6 @@ export const DIALOGS: IDialogs = ({
export const TABS: IMapTabs = ({
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 }) => ({
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',
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,
distance: number,
is_public: boolean,
is_starred: boolean,
updated_at: string,
}
@ -304,6 +305,18 @@ const setIsRouting: ActionHandler<typeof ActionCreators.setIsRouting> = (state,
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 = ({
[ACTIONS.SET_USER]: setUser,
[ACTIONS.SET_EDITING]: setEditing,
@ -348,6 +361,8 @@ const HANDLERS = ({
[ACTIONS.SET_FEATURE]: setFeature,
[ACTIONS.SET_IS_ROUTING]: setIsRouting,
[ACTIONS.SET_ROUTE_STARRED]: setRouteStarred,
});
export const INITIAL_STATE: IRootReducer = {

View file

@ -6,7 +6,7 @@ import {
checkUserToken, dropRoute,
getGuestToken, getRouteList,
getStoredMap, modifyRoute,
postMap
postMap, sendRouteStarred
} from '$utils/api';
import {
hideRenderer,
@ -32,7 +32,7 @@ import {
setProvider,
changeProvider,
setSaveLoading,
mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle,
mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, setRouteStarred,
} from '$redux/user/actions';
import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history';
import { editor } from '$modules/Editor';
@ -51,7 +51,7 @@ import {
} from '$utils/renderer';
import { ILogos, LOGOS } from '$constants/logos';
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 { IRootState } from "$redux/user/reducer";
@ -501,7 +501,7 @@ function* searchGetRoutes() {
const { routes: { step, shift, filter: { title, distance, tab } } } = yield select(getState);
const result = yield call(getRouteList, {
return yield call(getRouteList, {
id,
token,
title,
@ -509,10 +509,8 @@ function* searchGetRoutes() {
step,
shift,
author: tab === 'mine' ? id : '',
starred: tab === 'starred',
starred: tab === 'starred' ? 1 : 0,
});
return result;
}
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 });
}
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() {
yield takeLatest(REHYDRATE, authCheckSaga);
yield takeEvery(ACTIONS.SET_MODE, setModeSaga);
@ -744,4 +753,5 @@ export function* userSaga() {
yield takeLatest(ACTIONS.DROP_ROUTE, dropRouteSaga);
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)"/>
</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">
<rect x="0" y="0" width="32" height="32" fill="black" stroke="none" />
<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 {
.route-row {
.route-row, .route-row-fav {
transform: translateX(-120px);
}
@ -222,6 +222,7 @@
overflow: hidden;
transition: height 500ms;
position: relative;
display: flex;
&.has_menu {
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 {
width: 0;
height: 100%;

View file

@ -29,7 +29,7 @@ interface IGetRouteList {
author: IRootState['routes']['filter']['author'],
step: IRootState['routes']['step'],
shift: IRootState['routes']['step'],
starred: IRootState['routes']['filter']['starred'],
starred: number,
id: IRootState['user']['id'],
token: IRootState['user']['token'],
}
@ -112,3 +112,13 @@ export const modifyRoute = (
): AxiosPromise<any> => (
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)
);