moved editor to separate reducer

This commit is contained in:
Fedor Katurov 2020-01-09 10:59:26 +07:00
parent e950d98b73
commit 87670770b0
38 changed files with 1425 additions and 1069 deletions

View file

@ -1,17 +1,18 @@
import React from 'react';
import { Icon } from '~/components/panels/Icon';
import { MODES } from '~/constants/modes';
import { IStickerPack, STICKERS } from '~/constants/stickers';
import { STICKERS } from '~/constants/stickers';
import { StickerIcon } from '~/components/StickerIcon';
import { IRootReducer } from '~/redux/user';
import { connect } from 'react-redux';
import { selectEditor } from '~/redux/editor/selectors'
interface Props {
mode: IRootReducer['mode'],
sticker: string,
set: keyof IStickerPack,
}
const mapStateToProps = state => ({
editor: selectEditor
});
export class Cursor extends React.PureComponent<Props, {}> {
const mapDispatchToProps = {};
class CursorUnconnected extends React.PureComponent<Props, {}> {
componentDidMount() {
window.addEventListener('mousemove', this.moveCursor);
}
@ -27,15 +28,27 @@ export class Cursor extends React.PureComponent<Props, {}> {
cursor: HTMLElement = null;
render() {
const { mode, set, sticker } = this.props;
const activeSticker = (sticker && set && STICKERS[set] && STICKERS[set].layers[sticker]);
const {
editor: { mode, set, sticker },
} = this.props;
const activeSticker = sticker && set && STICKERS[set] && STICKERS[set].layers[sticker];
return (
<div className="cursor-tooltip desktop-only" ref={el => { this.cursor = el; }}>
{ mode === MODES.ROUTER && <Icon icon="icon-router" />}
{ mode === MODES.POLY && <Icon icon="icon-poly" />}
{ mode === MODES.STICKERS && activeSticker && <StickerIcon sticker={sticker} set={set} /> }
<div
className="cursor-tooltip desktop-only"
ref={el => {
this.cursor = el;
}}
>
{mode === MODES.ROUTER && <Icon icon="icon-router" />}
{mode === MODES.POLY && <Icon icon="icon-poly" />}
{mode === MODES.STICKERS && activeSticker && <StickerIcon sticker={sticker} set={set} />}
</div>
);
}
};
}
const Cursor = connect()(CursorUnconnected);
export { Cursor }

View file

@ -2,21 +2,23 @@ import React from 'react';
import { MODES } from '~/constants/modes';
import { Icon } from '~/components/panels/Icon';
import { setMode, stopEditing } from "~/redux/user/actions";
import { editorSetMode, editorStopEditing } from '~/redux/editor/actions';
type Props = {
stopEditing: typeof stopEditing,
setMode: typeof setMode,
width: number,
const mapStateToProps = () => ({});
const mapDispatchToProps = {
editorSetMode,
editorStopEditing,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & { width?: number };
export class CancelDialog extends React.Component<Props, void> {
cancel = () => {
this.props.stopEditing();
this.props.editorStopEditing();
};
proceed = () => {
this.props.setMode(MODES.NONE);
this.props.editorSetMode(MODES.NONE);
};
render() {

View file

@ -6,12 +6,14 @@ import {
searchSetDistance,
searchSetTitle,
searchSetTab,
setDialogActive,
mapsLoadMore,
dropRoute,
modifyRoute,
toggleRouteStarred,
} from '~/redux/user/actions';
import { editorSetDialogActive } from '~/redux/editor/actions';
import { isMobile } from '~/utils/window';
import classnames from 'classnames';
@ -19,28 +21,60 @@ import Range from 'rc-slider/lib/Range';
import { TABS, TABS_TITLES } from '~/constants/dialogs';
import { Icon } from '~/components/panels/Icon';
import { pushPath } from '~/utils/history';
import { IRootState, IRouteListItem } from '~/redux/user';
import { IRouteListItem } from '~/redux/user';
import { ROLES } from '~/constants/auth';
import { IState } from '~/redux/store';
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;
searchSetTitle: typeof searchSetTitle;
searchSetTab: typeof searchSetTab;
setDialogActive: typeof setDialogActive;
dropRoute: typeof dropRoute;
modifyRoute: typeof modifyRoute;
toggleRouteStarred: typeof toggleRouteStarred;
}
const mapStateToProps = ({
editor: { editing },
user: {
routes,
user: { role },
},
}: IState) => {
if (routes.filter.max >= 9999) {
return {
routes,
editing,
marks: {},
ready: false,
role,
};
}
export interface IMapListDialogState {
return {
role,
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 = {
searchSetDistance,
searchSetTitle,
searchSetTab,
editorSetDialogActive,
mapsLoadMore,
dropRoute,
modifyRoute,
toggleRouteStarred,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
export interface State {
menu_target: IRouteListItem['address'];
editor_target: IRouteListItem['address'];
@ -48,7 +82,7 @@ export interface IMapListDialogState {
is_dropping: boolean;
}
class MapListDialogUnconnected extends React.Component<IMapListDialogProps, IMapListDialogState> {
class MapListDialogUnconnected extends React.Component<Props, State> {
state = {
menu_target: null,
editor_target: null,
@ -92,14 +126,11 @@ class MapListDialogUnconnected extends React.Component<IMapListDialogProps, IMap
};
openRoute = (_id: string): void => {
if (isMobile()) this.props.setDialogActive(false);
if (isMobile()) this.props.editorSetDialogActive(false);
// pushPath(`/${_id}/${this.props.editing ? 'edit' : ''}`);
this.stopEditing();
pushPath(`/${_id}`);
// pushPath(`/${_id}/${this.props.editing ? 'edit' : ''}`);
};
onScroll = (e: {
@ -148,7 +179,7 @@ class MapListDialogUnconnected extends React.Component<IMapListDialogProps, IMap
filter: { min, max, title, distance, tab },
},
marks,
}: IMapListDialogProps = this.props;
}: Props = this.props;
const { editor_target, menu_target, is_editing, is_dropping } = this.state;
@ -245,51 +276,6 @@ class MapListDialogUnconnected extends React.Component<IMapListDialogProps, IMap
}
}
const mapStateToProps = ({
user: {
editing,
routes,
user: { role },
},
}: IState) => {
if (routes.filter.max >= 9999) {
return {
routes,
editing,
marks: {},
ready: false,
role,
};
}
return {
role,
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 = {
searchSetDistance,
searchSetTitle,
searchSetTab,
setDialogActive,
mapsLoadMore,
dropRoute,
modifyRoute,
toggleRouteStarred,
};
const MapListDialog = connect(mapStateToProps, mapDispatchToProps)(MapListDialogUnconnected);

View file

@ -1,9 +1,6 @@
import React from 'react';
import { Icon } from '~/components/panels/Icon';
import {
routerCancel as routerCancelAction,
routerSubmit as routerSubmitAction,
} from "~/redux/user/actions";
import * as EDITOR_ACTIONS from '~/redux/editor/actions'
import classnames from "classnames";
type Props = {
@ -11,11 +8,11 @@ type Props = {
width: number,
is_routing: boolean,
routerCancel: typeof routerCancelAction,
routerSubmit: typeof routerSubmitAction,
editorRouterCancel: typeof EDITOR_ACTIONS.editorRouterCancel,
editorRouterSubmit: typeof EDITOR_ACTIONS.editorRouterSubmit,
}
const noPoints = ({ routerCancel }: { routerCancel: typeof routerCancelAction }) => (
const noPoints = ({ editorRouterCancel }: { editorRouterCancel: typeof EDITOR_ACTIONS.editorRouterCancel }) => (
<React.Fragment>
<div className="helper router-helper">
<div className="helper__text">
@ -28,7 +25,7 @@ const noPoints = ({ routerCancel }: { routerCancel: typeof routerCancelAction })
<div className="helper router-helper">
<div className="helper__buttons flex_1">
<div className="flex_1" />
<div className="button router-helper__button" onClick={routerCancel}>
<div className="button router-helper__button" onClick={editorRouterCancel}>
Отмена
</div>
</div>
@ -36,7 +33,7 @@ const noPoints = ({ routerCancel }: { routerCancel: typeof routerCancelAction })
</React.Fragment>
);
const firstPoint = ({ routerCancel }: { routerCancel: typeof routerCancelAction }) => (
const firstPoint = ({ editorRouterCancel }: { editorRouterCancel: typeof EDITOR_ACTIONS.editorRouterCancel }) => (
<React.Fragment>
<div className="helper router-helper">
<div className="helper__text">
@ -47,7 +44,7 @@ const firstPoint = ({ routerCancel }: { routerCancel: typeof routerCancelAction
<div className="helper router-helper">
<div className="helper__buttons flex_1">
<div className="flex_1" />
<div className="button router-helper__button" onClick={routerCancel}>
<div className="button router-helper__button" onClick={editorRouterCancel}>
Отмена
</div>
</div>
@ -56,10 +53,10 @@ const firstPoint = ({ routerCancel }: { routerCancel: typeof routerCancelAction
);
const draggablePoints = ({
routerCancel, routerSubmit
editorRouterCancel, editorRouterSubmit
}: {
routerCancel: typeof routerCancelAction,
routerSubmit: typeof routerSubmitAction,
editorRouterCancel: typeof EDITOR_ACTIONS.editorRouterCancel,
editorRouterSubmit: typeof EDITOR_ACTIONS.editorRouterSubmit,
}) => (
<React.Fragment>
<div className="helper">
@ -71,10 +68,10 @@ const draggablePoints = ({
<div className="helper router-helper">
<div className="helper__buttons button-group flex_1">
<div className="flex_1" />
<div className="button button_red router-helper__button" onClick={routerCancel}>
<div className="button button_red router-helper__button" onClick={editorRouterCancel}>
Отмена
</div>
<div className="button primary router-helper__button" onClick={routerSubmit}>
<div className="button primary router-helper__button" onClick={editorRouterSubmit}>
Применить
</div>
</div>
@ -83,13 +80,13 @@ const draggablePoints = ({
);
export const RouterDialog = ({
routerPoints, routerCancel, routerSubmit, width, is_routing,
routerPoints, editorRouterCancel, editorRouterSubmit, width, is_routing,
}: Props) => (
<div className="control-dialog" style={{ width }}>
<div className={classnames('save-loader', { active: is_routing })} />
{!routerPoints && noPoints({ routerCancel })}
{routerPoints === 1 && firstPoint({ routerCancel })}
{routerPoints >= 2 && draggablePoints({ routerCancel, routerSubmit })}
{!routerPoints && noPoints({ editorRouterCancel })}
{routerPoints === 1 && firstPoint({ editorRouterCancel })}
{routerPoints >= 2 && draggablePoints({ editorRouterCancel, editorRouterSubmit })}
</div>
);

View file

@ -10,17 +10,17 @@ import classnames from 'classnames';
import ExpandableTextarea from 'react-expandable-textarea';
import { connect } from 'react-redux';
import { selectMap } from '~/redux/map/selectors';
import { selectUser } from '~/redux/user/selectors';
import * as USER_ACTIONS from '~/redux/user/actions';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import { selectEditor } from '~/redux/editor/selectors';
const mapStateToProps = state => ({
map: selectMap(state),
user: selectUser(state),
editor: selectEditor(state),
});
const mapDispatchToProps = {
setMode: USER_ACTIONS.setMode,
sendSaveRequest: USER_ACTIONS.sendSaveRequest,
editorSetMode: EDITOR_ACTIONS.editorSetMode,
editorSendSaveRequest: EDITOR_ACTIONS.editorSendSaveRequest,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & { width: number };
@ -33,14 +33,14 @@ interface State {
}
class SaveDialogUnconnected extends React.Component<Props, State> {
constructor(props) {
constructor(props: Props) {
super(props);
this.state = {
address: props.address || '',
title: props.title || '',
is_public: props.is_public || false,
description: props.description || '',
address: props.map.address || '',
title: props.map.title || '',
is_public: props.map.is_public || false,
description: props.map.description || '',
};
}
@ -57,16 +57,18 @@ class SaveDialogUnconnected extends React.Component<Props, State> {
setTitle = ({ target: { value } }) =>
this.setState({ title: (value && value.substr(0, 64)) || '' });
setAddress = ({ target: { value } }) =>
this.setState({ address: (value && value.substr(0, 32)) || '' });
setDescription = ({ target: { value } }) =>
this.setState({ description: (value && value.substr(0, 256)) || '' });
sendSaveRequest = (e, force = false) => {
editorSendSaveRequest = (e, force = false) => {
const { title, is_public, description } = this.state;
const address = this.getAddress();
this.props.sendSaveRequest({
this.props.editorSendSaveRequest({
title,
address,
force,
@ -74,9 +76,10 @@ class SaveDialogUnconnected extends React.Component<Props, State> {
description,
});
};
forceSaveRequest = e => this.sendSaveRequest(e, true);
cancelSaving = () => this.props.setMode(MODES.NONE);
forceSaveRequest = e => this.editorSendSaveRequest(e, true);
cancelSaving = () => this.props.editorSetMode(MODES.NONE);
onCopy = e => {
e.preventDefault();
@ -91,7 +94,7 @@ class SaveDialogUnconnected extends React.Component<Props, State> {
render() {
const { title, is_public, description } = this.state;
const {
user: { save_error, save_finished, save_overwriting, save_loading },
editor: { save_error, save_finished, save_overwriting, save_loading },
width,
} = this.props;
const { host, protocol } = getUrlData();
@ -157,7 +160,7 @@ class SaveDialogUnconnected extends React.Component<Props, State> {
</div>
)}
{!save_finished && !save_overwriting && (
<div className="button primary" onClick={this.sendSaveRequest}>
<div className="button primary" onClick={this.editorSendSaveRequest}>
Сохранить
</div>
)}

View file

@ -1,9 +1,9 @@
import React from 'react';
import { connect } from 'react-redux';
import { selectUserRenderer } from '~/redux/user/selectors';
import { selectEditorRenderer } from '~/redux/editor/selectors';
const mapStateToProps = state => ({
renderer: selectUserRenderer(state),
renderer: selectEditorRenderer(state),
});
type Props = ReturnType<typeof mapStateToProps> & {};

View file

@ -1,39 +1,44 @@
// @flow
import React from 'react';
import { STICKERS } from '~/constants/stickers';
import { setActiveSticker as setActiveStickerAction } from "~/redux/user/actions";
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import { connect } from 'react-redux';
interface Props {
setActiveSticker: typeof setActiveStickerAction,
width: number,
}
const mapStateToProps = () => ({});
const mapDispatchToProps = {
editorSetActiveSticker: EDITOR_ACTIONS.editorSetActiveSticker,
};
export const StickersDialog = ({ setActiveSticker, width }: Props) => (
type Props = ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps & {
width: number;
};
const StickersDialogUnconnected = ({ editorSetActiveSticker, width }: Props) => (
<div className="control-dialog control-dialog-big" style={{ width }}>
<div className="helper stickers-helper">
{
Object.keys(STICKERS).map(set => (
<div key={set}>
<div className="stickers-set-title">{STICKERS[set].title || null}</div>
<div className="stickers-grid">
{
Object.keys(STICKERS[set].layers).map(sticker => (
<div
style={{
backgroundImage: `url(${STICKERS[set].url})`,
backgroundPosition: `${-STICKERS[set].layers[sticker].off * 48}px 50%`,
}}
className="sticker-preview"
key={`${set}-${sticker}`}
onClick={() => setActiveSticker({ set, sticker })}
/>
))
}
</div>
{Object.keys(STICKERS).map(set => (
<div key={set}>
<div className="stickers-set-title">{STICKERS[set].title || null}</div>
<div className="stickers-grid">
{Object.keys(STICKERS[set].layers).map(sticker => (
<div
style={{
backgroundImage: `url(${STICKERS[set].url})`,
backgroundPosition: `${-STICKERS[set].layers[sticker].off * 48}px 50%`,
}}
className="sticker-preview"
key={`${set}-${sticker}`}
onClick={() => editorSetActiveSticker({ set, sticker })}
/>
))}
</div>
))
}
</div>
))}
</div>
</div>
);
const StickersDialog = connect(mapStateToProps, mapDispatchToProps)(StickersDialogUnconnected);
export { StickersDialog };

View file

@ -5,11 +5,11 @@ import classnames from 'classnames';
import { getStyle } from '~/utils/dom';
import { nearestInt } from '~/utils/geom';
import { parseDesc } from '~/utils/format';
import { selectUser } from '~/redux/user/selectors';
import { selectMap } from '~/redux/map/selectors';
import { selectEditor } from '~/redux/editor/selectors';
const mapStateToProps = state => ({
user: selectUser(state),
editor: selectEditor(state),
map: selectMap(state),
});
@ -80,7 +80,7 @@ export class TitleDialogUnconnected extends React.PureComponent<Props, State> {
render() {
const {
user: { editing },
editor: { editing },
map: { title, description },
} = this.props;
const { raised, height, height_raised } = this.state;

View file

@ -1,20 +1,25 @@
// flow
import React from 'react';
import { toHours } from '~/utils/format';
import { Icon } from '~/components/panels/Icon';
import { connect } from 'react-redux';
// import Slider from 'rc-slider';
import Slider from 'rc-slider/lib/Slider';
import { bindActionCreators } from 'redux';
import { setSpeed } from '~/redux/user/actions';
import { IRootState } from "~/redux/user";
import { editorSetSpeed } from '~/redux/editor/actions';
import { Tooltip } from "~/components/panels/Tooltip";
import { isMobile } from "~/utils/window";
import { IState } from '~/redux/store';
interface Props extends IRootState {
setSpeed: typeof setSpeed,
function mapStateToProps(state) {
const {
editor: { distance, estimated, speed },
}: IState = state;
return { distance, estimated, speed };
}
const mapDispatchToProps = { editorSetSpeed };
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
interface State {
dialogOpened: boolean,
}
@ -68,7 +73,7 @@ class Component extends React.PureComponent<Props, State> {
min={min}
max={max}
step={step}
onChange={this.props.setSpeed}
onChange={this.props.editorSetSpeed}
defaultValue={15}
value={speed}
marks={marks}
@ -81,18 +86,6 @@ class Component extends React.PureComponent<Props, State> {
}
}
function mapStateToProps(state) {
const {
user: { distance, estimated, speed },
} = state;
return { distance, estimated, speed };
}
const mapDispatchToProps = dispatch => bindActionCreators({
setSpeed,
}, dispatch);
export const DistanceBar = connect(
mapStateToProps,
mapDispatchToProps

View file

@ -12,29 +12,13 @@ import { connect } from 'react-redux';
import { ProviderDialog } from '~/components/dialogs/ProviderDialog';
import { ShotPrefetchDialog } from '~/components/dialogs/ShotPrefetchDialog';
import { selectUserMode } from '~/redux/user/selectors';
import { selectEditorMode } from '~/redux/editor/selectors';
const mapStateToProps = state => ({ mode: selectUserMode(state) });
// const mapDispatchToProps = dispatch => bindActionCreators({
// routerCancel: USER_ACTIONS.routerCancel,
// routerSubmit: USER_ACTIONS.routerSubmit,
// setActiveSticker: USER_ACTIONS.setActiveSticker,
// clearStickers: USER_ACTIONS.clearStickers,
// clearPoly: USER_ACTIONS.clearPoly,
// clearAll: USER_ACTIONS.clearAll,
// clearCancel: USER_ACTIONS.clearCancel,
// stopEditing: USER_ACTIONS.stopEditing,
// setEditing: USER_ACTIONS.setEditing,
// setMode: USER_ACTIONS.setMode,
// sendSaveRequest: USER_ACTIONS.sendSaveRequest,
// changeProvider: USER_ACTIONS.changeProvider,
// mapSetLogo: MAP_ACTIONS.mapSetLogo,
// }, dispatch);
const mapStateToProps = state => ({ mode: selectEditorMode(state) });
type Props = ReturnType<typeof mapStateToProps> & {
width: number;
};
width: number;
};
const DIALOG_CONTENTS: { [x: string]: any } = {
[MODES.ROUTER]: RouterDialog,
@ -47,12 +31,9 @@ const DIALOG_CONTENTS: { [x: string]: any } = {
[MODES.SHOT_PREFETCH]: ShotPrefetchDialog,
};
export const Component = (props: Props) =>
props.mode && DIALOG_CONTENTS[props.mode]
? createElement(DIALOG_CONTENTS[props.mode])
: null;
const EditorDialogUnconnected = (props: Props) =>
props.mode && DIALOG_CONTENTS[props.mode] ? createElement(DIALOG_CONTENTS[props.mode]) : null;
export const EditorDialog = connect(
mapStateToProps
// mapDispatchToProps
)(Component);
const EditorDialog = connect(mapStateToProps)(EditorDialogUnconnected);
export { EditorDialog };

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PureComponent } from 'react';
import { MODES } from '~/constants/modes';
import classnames from 'classnames';
@ -6,21 +6,34 @@ import { Icon } from '~/components/panels/Icon';
import { EditorDialog } from '~/components/panels/EditorDialog';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { setMode, startEditing, stopEditing, takeAShot, keyPressed } from '~/redux/user/actions';
import { IRootState } from "~/redux/user";
import { Tooltip } from "~/components/panels/Tooltip";
import {
editorSetMode,
editorStartEditing,
editorStopEditing,
editorTakeAShot,
editorKeyPressed,
} from '~/redux/editor/actions';
import { Tooltip } from '~/components/panels/Tooltip';
import { IState } from '~/redux/store';
import { selectEditor } from '~/redux/editor/selectors';
interface Props extends IRootState {
routing: IRootState['features']['routing'],
setMode: typeof setMode,
startEditing: typeof startEditing,
stopEditing: typeof stopEditing,
keyPressed: EventListenerOrEventListenerObject,
}
const mapStateToProps = (state: IState) => ({
editor: selectEditor(state),
});
class Component extends React.PureComponent<Props, void> {
const mapDispatchToProps = {
editorSetMode,
editorStartEditing,
editorStopEditing,
editorTakeAShot,
editorKeyPressed,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
class EditorPanelUnconnected extends PureComponent<Props, void> {
componentDidMount() {
window.addEventListener('keydown', this.props.keyPressed);
window.addEventListener('keydown', this.props.editorKeyPressed as any);
const obj = document.getElementById('control-dialog');
const { width } = this.panel.getBoundingClientRect();
@ -33,29 +46,38 @@ class Component extends React.PureComponent<Props, void> {
panel: HTMLElement = null;
componentWillUnmount() {
window.removeEventListener('keydown', this.props.keyPressed);
window.removeEventListener('keydown', this.props.editorKeyPressed as any);
}
startPolyMode = () => this.props.setMode(MODES.POLY);
startStickerMode = () => this.props.setMode(MODES.STICKERS_SELECT);
startRouterMode = () => this.props.setMode(MODES.ROUTER);
startTrashMode = () => this.props.setMode(MODES.TRASH);
startPolyMode = () => this.props.editorSetMode(MODES.POLY);
startStickerMode = () => this.props.editorSetMode(MODES.STICKERS_SELECT);
startRouterMode = () => this.props.editorSetMode(MODES.ROUTER);
startTrashMode = () => this.props.editorSetMode(MODES.TRASH);
startSaveMode = () => {
// if (!this.props.changed) return;
this.props.setMode(MODES.SAVE);
this.props.editorSetMode(MODES.SAVE);
};
render() {
const {
mode, changed, editing, routing,
editor: {
mode,
changed,
editing,
features: { routing },
},
} = this.props;
return (
<div>
<div className={classnames('panel right', { active: editing })} ref={el => { this.panel = el; }}>
<div
className={classnames('panel right', { active: editing })}
ref={el => {
this.panel = el;
}}
>
<div className="control-bar control-bar-padded">
{
routing &&
{routing && (
<button
className={classnames({ active: mode === MODES.ROUTER })}
onClick={this.startRouterMode}
@ -63,8 +85,7 @@ class Component extends React.PureComponent<Props, void> {
<Tooltip>Автоматический маршрут</Tooltip>
<Icon icon="icon-route-2" />
</button>
}
)}
<button
className={classnames({ active: mode === MODES.POLY })}
@ -75,13 +96,14 @@ class Component extends React.PureComponent<Props, void> {
</button>
<button
className={classnames({ active: (mode === MODES.STICKERS || mode === MODES.STICKERS_SELECT) })}
className={classnames({
active: mode === MODES.STICKERS || mode === MODES.STICKERS_SELECT,
})}
onClick={this.startStickerMode}
>
<Tooltip>Точки маршрута</Tooltip>
<Icon icon="icon-sticker-3" />
</button>
</div>
<div className="control-sep" />
@ -99,10 +121,7 @@ class Component extends React.PureComponent<Props, void> {
<div className="control-sep" />
<div className="control-bar">
<button
className="highlighted cancel"
onClick={this.props.stopEditing}
>
<button className="highlighted cancel" onClick={this.props.editorStopEditing}>
<Icon icon="icon-cancel-1" />
</button>
@ -114,59 +133,21 @@ class Component extends React.PureComponent<Props, void> {
<Icon icon="icon-check-1" />
</button>
</div>
</div>
<div className={classnames('panel right', { active: !editing })}>
<div className="control-bar">
<button className="primary single" onClick={this.props.startEditing}>
<button className="primary single" onClick={this.props.editorStartEditing}>
<Icon icon="icon-route-2" />
<span>
РЕДАКТИРОВАТЬ
</span>
<span>РЕДАКТИРОВАТЬ</span>
</button>
</div>
</div>
<EditorDialog
width={((this.panel && this.panel.getBoundingClientRect().width) || 0)}
/>
<EditorDialog width={(this.panel && this.panel.getBoundingClientRect().width) || 0} />
</div>
);
}
}
function mapStateToProps(state) {
const {
user: {
editing,
mode,
changed,
features: {
routing,
}
},
} = state;
return {
editing,
mode,
changed,
routing,
};
}
const mapDispatchToProps = dispatch => bindActionCreators({
setMode,
// setLogo,
startEditing,
stopEditing,
takeAShot,
keyPressed,
}, dispatch);
export const EditorPanel = connect(
mapStateToProps,
mapDispatchToProps
)(Component);
export const EditorPanel = connect(mapStateToProps, mapDispatchToProps)(EditorPanelUnconnected);

View file

@ -1,36 +1,34 @@
// flow
import React, { useCallback } from 'react';
import { Icon } from '~/components/panels/Icon';
import { PROVIDERS } from '~/constants/providers';
import { LOGOS } from '~/constants/logos';
import * as USER_ACTIONS from '~/redux/user/actions';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import { connect } from 'react-redux';
import { MODES } from '~/constants/modes';
import { IRootState } from '~/redux/user';
import { Tooltip } from '~/components/panels/Tooltip';
import { selectMap } from '~/redux/map/selectors';
import { selectUser } from '~/redux/user/selectors';
import { selectEditor } from '~/redux/editor/selectors';
const mapStateToProps = state => ({
map: selectMap(state),
user: selectUser(state),
editor: selectEditor(state),
});
const mapDispatchToProps = {
setMode: USER_ACTIONS.setMode,
editorSetMode: EDITOR_ACTIONS.editorSetMode,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const TopRightPanelUnconnected = ({
map: { provider, logo },
user: { markers_shown, editing },
setMode,
editor: { markers_shown, editing },
editorSetMode,
}: Props) => {
const startProviderMode = useCallback(() => setMode(MODES.PROVIDER), [setMode]);
const startLogoMode = useCallback(() => setMode(MODES.LOGO), [setMode]);
const clearMode = useCallback(() => setMode(MODES.NONE), [setMode]);
const startProviderMode = useCallback(() => editorSetMode(MODES.PROVIDER), [editorSetMode]);
const startLogoMode = useCallback(() => editorSetMode(MODES.LOGO), [editorSetMode]);
const clearMode = useCallback(() => editorSetMode(MODES.NONE), [editorSetMode]);
return (
<div className="status-panel top right">

View file

@ -4,35 +4,41 @@ 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, gotVkUser, openMapDialog } from '~/redux/user/actions';
import {
setUser,
userLogout,
takeAShot,
setDialog,
gotVkUser,
setDialogActive,
openMapDialog,
getGPXTrack,
} from '~/redux/user/actions';
editorTakeAShot,
editorSetDialog,
editorSetDialogActive,
editorGetGPXTrack,
} from '~/redux/editor/actions';
import { connect } from 'react-redux';
import { Icon } from '~/components/panels/Icon';
import classnames from 'classnames';
import { CLIENT } from '~/config/frontend';
import { DIALOGS, TABS } from '~/constants/dialogs';
import { IRootState } from '~/redux/user';
import { Tooltip } from '~/components/panels/Tooltip';
import { TitleDialog } from '~/components/dialogs/TitleDialog';
interface Props extends IRootState {
userLogout: typeof userLogout;
setDialog: typeof setDialog;
setDialogActive: typeof setDialogActive;
gotVkUser: typeof gotVkUser;
takeAShot: typeof takeAShot;
openMapDialog: typeof openMapDialog;
getGPXTrack: typeof getGPXTrack;
}
const mapStateToProps = ({ user: { user }, editor: { dialog, dialog_active, is_empty } }) => ({
dialog,
dialog_active,
user,
is_empty,
});
const mapDispatchToProps = {
setUser,
userLogout,
editorTakeAShot,
editorSetDialog,
gotVkUser,
editorSetDialogActive,
openMapDialog,
editorGetGPXTrack,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
interface State {
menuOpened: boolean;
@ -84,8 +90,8 @@ export class UserPanelUnconnected extends PureComponent<Props, State> {
openAppInfoDialog = () => {
this.setMenuOpened();
this.props.setDialog(DIALOGS.APP_INFO);
this.props.setDialogActive(this.props.dialog !== DIALOGS.APP_INFO);
this.props.editorSetDialog(DIALOGS.APP_INFO);
this.props.editorSetDialogActive(this.props.dialog !== DIALOGS.APP_INFO);
};
openOauthFrame = () => {
@ -143,7 +149,7 @@ export class UserPanelUnconnected extends PureComponent<Props, State> {
<div className="control-sep" />
<div className="control-bar">
<button className={classnames({ active: false })} onClick={this.props.takeAShot}>
<button className={classnames({ active: false })} onClick={this.props.editorTakeAShot}>
<Tooltip>Снимок карты</Tooltip>
<Icon icon="icon-shot-4" />
</button>
@ -153,7 +159,10 @@ export class UserPanelUnconnected extends PureComponent<Props, State> {
<div className="control-sep" />
<div className="control-bar">
<button className={classnames({ active: false })} onClick={this.props.getGPXTrack}>
<button
className={classnames({ active: false })}
onClick={this.props.editorGetGPXTrack}
>
<Tooltip>Экспорт GPX</Tooltip>
<Icon icon="icon-gpx-1" />
</button>
@ -166,24 +175,6 @@ export class UserPanelUnconnected extends PureComponent<Props, State> {
}
}
const mapStateToProps = ({ user: { dialog, dialog_active, user, is_empty } }) => ({
dialog,
dialog_active,
user,
is_empty,
});
const mapDispatchToProps = {
setUser,
userLogout,
takeAShot,
setDialog,
gotVkUser,
setDialogActive,
openMapDialog,
getGPXTrack,
};
const UserPanel = connect(mapStateToProps, mapDispatchToProps)(UserPanelUnconnected);
export { UserPanel };

View file

@ -1,22 +1,26 @@
import React from 'react';
import { hideRenderer, cropAShot } from '~/redux/user/actions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Croppr from 'croppr';
import 'croppr/dist/croppr.css';
import { LOGOS } from '~/constants/logos';
import { RendererPanel } from '~/components/panels/RendererPanel';
import { IRootState } from "~/redux/user";
import { IRoute } from '~/redux/map/types';
import { selectEditor } from '~/redux/editor/selectors';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import { selectMap } from '~/redux/map/selectors';
type Props = {
data: IRootState['renderer']['data'],
logo: IRoute['logo'],
hideRenderer: typeof hideRenderer,
cropAShot: typeof cropAShot,
const mapStateToProps = state => ({
editor: selectEditor(state),
map: selectMap(state),
});
const mapDispatchToProps = {
editorHideRenderer: EDITOR_ACTIONS.editorHideRenderer,
editorCropAShot: EDITOR_ACTIONS.editorCropAShot,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
type State = {
opacity: number,
};
@ -47,7 +51,7 @@ class Component extends React.Component<Props, State> {
this.logo.style.transform = `scale(${scale})`;
this.logoImg = document.createElement('img');
if (this.props.logo && LOGOS[this.props.logo][1]) this.logoImg.src = LOGOS[this.props.logo][1];
if (this.props.map.logo && LOGOS[this.props.map.logo][1]) this.logoImg.src = LOGOS[this.props.map.logo][1];
this.logo.append(this.logoImg);
regionEl.append(this.logo);
@ -58,10 +62,10 @@ class Component extends React.Component<Props, State> {
image: HTMLImageElement;
logoImg: HTMLImageElement;
getImage = () => this.props.cropAShot(this.croppr.getValue());
getImage = () => this.props.editorCropAShot(this.croppr.getValue());
render() {
const { data } = this.props;
const { data } = this.props.editor.renderer;
const { opacity } = this.state;
const { innerWidth, innerHeight } = window;
const padding = 30;
@ -95,7 +99,7 @@ class Component extends React.Component<Props, State> {
</div>
<RendererPanel
onCancel={this.props.hideRenderer}
onCancel={this.props.editorHideRenderer}
onSubmit={this.getImage}
/>
</div>
@ -103,12 +107,4 @@ class Component extends React.Component<Props, State> {
}
}
const mapStateToProps = state => ({ ...state.user.renderer, logo: state.user.logo });
const mapDispatchToProps = dispatch => bindActionCreators({
hideRenderer,
cropAShot,
}, dispatch);
export const Renderer = connect(mapStateToProps, mapDispatchToProps)(Component);

View file

@ -11,7 +11,7 @@ export interface IUser {
role: IRoles[keyof IRoles];
routes: {};
success: boolean;
id?: number;
id?: string;
uid: string;
token?: string;
photo: string;

View file

@ -1,4 +1,3 @@
// @flow
import React from 'react';
import { EditorPanel } from '~/components/panels/EditorPanel';
@ -9,8 +8,7 @@ import { bindActionCreators } from 'redux';
import { hot } from 'react-hot-loader';
import { Renderer } from '~/components/renderer/Renderer';
import { hideRenderer, setDialogActive } from '~/redux/user/actions';
import { Cursor } from '~/components/Cursor';
import { editorHideRenderer, editorSetDialogActive } from '~/redux/editor/actions';
import { LeftDialog } from '~/containers/LeftDialog';
import { TopLeftPanel } from '~/components/panels/TopLeftPanel';
import { TopRightPanel } from '~/components/panels/TopRightPanel';
@ -19,18 +17,19 @@ import { IStickerPack } from '~/constants/stickers';
import { IDialogs } from '~/constants/dialogs';
import { Map } from '~/containers/map/Map';
import { IRootReducer } from '~/redux/user';
import { IEditorState } from '~/redux/editor';
import { IState } from '~/redux/store';
type Props = {
sticker: string;
renderer_active: boolean;
mode: IRootReducer['mode'];
mode: IEditorState['mode'];
dialog: keyof IDialogs;
dialog_active: boolean;
set: keyof IStickerPack;
hideRenderer: typeof hideRenderer;
setDialogActive: typeof setDialogActive;
editorHideRenderer: typeof editorHideRenderer;
editorSetDialogActive: typeof editorSetDialogActive;
};
const Component = (props: Props) => (
@ -45,26 +44,26 @@ const Component = (props: Props) => (
<LeftDialog
dialog={props.dialog}
dialog_active={props.dialog_active}
setDialogActive={props.setDialogActive}
editorSetDialogActive={props.editorSetDialogActive}
/>
<LogoPreview />
<Map />
{props.renderer_active && <Renderer onClick={props.hideRenderer} />}
{props.renderer_active && <Renderer onClick={props.editorHideRenderer} />}
</div>
);
const mapStateToProps = ({
user: {
editor: {
mode,
dialog,
dialog_active,
renderer,
activeSticker: { sticker = null, set = null },
},
}) => ({
}: IState) => ({
renderer_active: renderer.renderer_active,
mode,
dialog,
@ -74,5 +73,5 @@ const mapStateToProps = ({
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ hideRenderer, setDialogActive }, dispatch);
bindActionCreators({ editorHideRenderer, editorSetDialogActive }, dispatch);
export const App = connect(mapStateToProps, mapDispatchToProps)(hot(module)(Component));

View file

@ -4,12 +4,12 @@ import classnames from 'classnames';
import { AppInfoDialog } from '~/components/dialogs/AppInfoDialog';
import { Icon } from '~/components/panels/Icon';
import { MapListDialog } from '~/components/dialogs/MapListDialog';
import * as USER_ACTIONS from '~/redux/user/actions';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
interface Props {
dialog: keyof IDialogs;
dialog_active: Boolean;
setDialogActive: typeof USER_ACTIONS.setDialogActive;
editorSetDialogActive: typeof EDITOR_ACTIONS.editorSetDialogActive;
}
const LEFT_DIALOGS = {
@ -17,7 +17,7 @@ const LEFT_DIALOGS = {
[DIALOGS.APP_INFO]: AppInfoDialog,
};
const LeftDialog = ({ dialog, dialog_active, setDialogActive }: Props) => (
const LeftDialog = ({ dialog, dialog_active, editorSetDialogActive }: Props) => (
<React.Fragment>
{Object.keys(LEFT_DIALOGS).map(item => (
<div
@ -26,11 +26,11 @@ const LeftDialog = ({ dialog, dialog_active, setDialogActive }: Props) => (
>
{dialog && LEFT_DIALOGS[item] && createElement(LEFT_DIALOGS[item], {})}
<div className="dialog-close-button desktop-only" onClick={() => setDialogActive(false)}>
<div className="dialog-close-button desktop-only" onClick={() => editorSetDialogActive(false)}>
<Icon icon="icon-cancel-1" />
</div>
<div className="dialog-close-button mobile-only" onClick={() => setDialogActive(false)}>
<div className="dialog-close-button mobile-only" onClick={() => editorSetDialogActive(false)}>
<Icon icon="icon-chevron-down" />
</div>
</div>

View file

@ -12,15 +12,15 @@ import * as MAP_ACTIONS from "~/redux/map/actions";
import { Route } from "~/containers/map/Route";
import { TileLayer } from "~/containers/map/TileLayer";
import { Stickers } from "~/containers/map/Stickers";
import { selectUserEditing } from '~/redux/user/selectors'
import 'leaflet/dist/leaflet.css';
import { selectEditorEditing } from "~/redux/editor/selectors";
const mapStateToProps = state => ({
provider: selectMapProvider(state),
route: selectMapRoute(state),
stickers: selectMapStickers(state),
editing: selectUserEditing(state),
editing: selectEditorEditing(state),
});
const mapDispatchToProps = {

120
src/redux/editor/actions.ts Normal file
View file

@ -0,0 +1,120 @@
import { EDITOR_ACTIONS } from './constants';
import { IEditorState } from '.';
import { IRoute } from '../map/types';
import { KeyboardEvent } from 'react';
export const editorSetEditing = (editing: IEditorState['editing']) => ({
type: EDITOR_ACTIONS.SET_EDITING,
editing,
});
export const editorSetMode = (mode: IEditorState['mode']) => ({
type: EDITOR_ACTIONS.SET_MODE,
mode,
});
export const editorSetDistance = (distance: IEditorState['distance']) => ({
type: EDITOR_ACTIONS.SET_DISTANCE,
distance,
});
export const editorSetChanged = (changed: IEditorState['changed']) => ({
type: EDITOR_ACTIONS.SET_CHANGED,
changed,
});
export const editorSetSpeed = speed => ({ type: EDITOR_ACTIONS.SET_SPEED, speed });
export const editorStartEditing = () => ({ type: EDITOR_ACTIONS.START_EDITING });
export const editorStopEditing = () => ({ type: EDITOR_ACTIONS.STOP_EDITING });
export const editorRouterCancel = () => ({ type: EDITOR_ACTIONS.ROUTER_CANCEL });
export const editorRouterSubmit = () => ({ type: EDITOR_ACTIONS.ROUTER_SUBMIT });
export const editorClearPoly = () => ({ type: EDITOR_ACTIONS.CLEAR_POLY });
export const editorClearStickers = () => ({ type: EDITOR_ACTIONS.CLEAR_STICKERS });
export const editorClearAll = () => ({ type: EDITOR_ACTIONS.CLEAR_ALL });
export const editorClearCancel = () => ({ type: EDITOR_ACTIONS.CLEAR_CANCEL });
export const editorSendSaveRequest = (payload: {
title: IRoute['title'];
address: IRoute['address'];
is_public: IRoute['is_public'];
description: IRoute['description'];
force: boolean;
}) => ({
type: EDITOR_ACTIONS.SEND_SAVE_REQUEST,
...payload,
});
export const editorResetSaveDialog = () => ({ type: EDITOR_ACTIONS.RESET_SAVE_DIALOG });
export const editorSetSaveLoading = (save_loading: IEditorState['save_loading']) => ({
type: EDITOR_ACTIONS.SET_SAVE_LOADING,
save_loading,
});
export const editorSetSaveSuccess = (payload: {
address: IRoute['address'];
title: IRoute['address'];
is_public: IRoute['is_public'];
description: IRoute['description'];
save_error: string;
}) => ({ type: EDITOR_ACTIONS.SET_SAVE_SUCCESS, ...payload });
export const editorSetSaveError = (save_error: IEditorState['save_error']) => ({
type: EDITOR_ACTIONS.SET_SAVE_ERROR,
save_error,
});
export const editorSetSaveOverwrite = () => ({ type: EDITOR_ACTIONS.SET_SAVE_OVERWRITE });
export const editorHideRenderer = () => ({ type: EDITOR_ACTIONS.HIDE_RENDERER });
export const editorSetRenderer = payload => ({ type: EDITOR_ACTIONS.SET_RENDERER, payload });
export const editorTakeAShot = () => ({ type: EDITOR_ACTIONS.TAKE_A_SHOT });
export const editorCropAShot = payload => ({ type: EDITOR_ACTIONS.CROP_A_SHOT, ...payload });
export const editorSetDialog = dialog => ({ type: EDITOR_ACTIONS.SET_DIALOG, dialog });
export const editorSetDialogActive = dialog_active => ({
type: EDITOR_ACTIONS.SET_DIALOG_ACTIVE,
dialog_active,
});
export const editorSetReady = ready => ({ type: EDITOR_ACTIONS.SET_READY, ready });
export const editorGetGPXTrack = () => ({ type: EDITOR_ACTIONS.GET_GPX_TRACK });
export const editorSetMarkersShown = markers_shown => ({
type: EDITOR_ACTIONS.SET_MARKERS_SHOWN,
markers_shown,
});
export const editorSetIsEmpty = is_empty => ({ type: EDITOR_ACTIONS.SET_IS_EMPTY, is_empty });
export const editorSetFeature = (features: { [x: string]: boolean }) => ({
type: EDITOR_ACTIONS.SET_FEATURE,
features,
});
export const editorSetIsRouting = (is_routing: boolean) => ({
type: EDITOR_ACTIONS.SET_IS_ROUTING,
is_routing,
});
export const editorSetRouterPoints = (routerPoints: IEditorState['routerPoints']) => ({
type: EDITOR_ACTIONS.SET_ROUTER_POINTS,
routerPoints,
});
export const editorSetActiveSticker = (activeSticker: IEditorState['activeSticker']) => ({
type: EDITOR_ACTIONS.SET_ACTIVE_STICKER,
activeSticker,
});
export const editorLocationChanged = location => ({
type: EDITOR_ACTIONS.LOCATION_CHANGED,
location,
});
export const editorKeyPressed = ({
key,
target: { tagName },
}: {
key: string;
target: { tagName: string };
}) => ({
type: EDITOR_ACTIONS.KEY_PRESSED,
key,
target: tagName,
});

View file

@ -0,0 +1,47 @@
const P = 'EDITOR';
export const EDITOR_ACTIONS = {
SET_EDITING: `${P}-SET_EDITING`,
SET_MODE: `${P}-SET_MODE`,
SET_DISTANCE: `${P}-SET_DISTANCE`,
SET_CHANGED: `${P}-SET_CHANGED`,
SET_SPEED: `${P}-SET_SPEED`,
SET_ROUTER_POINTS: `${P}-SET_ROUTER_POINTS`,
SET_ACTIVE_STICKER: `${P}-SET_ACTIVE_STICKER`,
START_EDITING: `${P}-START_EDITING`,
STOP_EDITING: `${P}-STOP_EDITING`,
ROUTER_CANCEL: `${P}-ROUTER_CANCEL`,
ROUTER_SUBMIT: `${P}-ROUTER_SUBMIT`,
CLEAR_POLY: `${P}-CLEAR_POLY`,
CLEAR_STICKERS: `${P}-CLEAR_STICKERS`,
CLEAR_ALL: `${P}-CLEAR_ALL`,
CLEAR_CANCEL: `${P}-CLEAR_CANCEL`,
SEND_SAVE_REQUEST: `${P}-SEND_SAVE_REQUEST`,
SET_SAVE_LOADING: `${P}-SET_SAVE_LOADING`,
CANCEL_SAVE_REQUEST: `${P}-CANCEL_SAVE_REQUEST`,
RESET_SAVE_DIALOG: `${P}-RESET_SAVE_DIALOG`,
SET_SAVE_SUCCESS: `${P}-SET_SAVE_SUCCESS`,
SET_SAVE_ERROR: `${P}-SET_SAVE_ERROR`,
SET_SAVE_OVERWRITE: `${P}-SET_SAVE_OVERWRITE`,
SHOW_RENDERER: `${P}-SHOW_RENDERER`,
HIDE_RENDERER: `${P}-HIDE_RENDERER`,
SET_RENDERER: `${P}-SET_RENDERER`,
TAKE_A_SHOT: `${P}-TAKE_A_SHOT`,
CROP_A_SHOT: `${P}-CROP_A_SHOT`,
SET_DIALOG: `${P}-SET_DIALOG`,
SET_DIALOG_ACTIVE: `${P}-SET_DIALOG_ACTIVE`,
LOCATION_CHANGED: `${P}-LOCATION_CHANGED`,
SET_READY: `${P}-SET_READY`,
SET_MARKERS_SHOWN: `${P}-SET_MARKERS_SHOWN`,
GET_GPX_TRACK: `${P}-GET_GPX_TRACK`,
SET_IS_EMPTY: `${P}-SET_IS_EMPTY`,
SET_FEATURE: `${P}-SET_FEATURE`,
SET_IS_ROUTING: `${P}-SET_IS_ROUTING`,
KEY_PRESSED: `${P}-KEY_PRESSED`,
};

View file

@ -0,0 +1,200 @@
import { getEstimated } from '~/utils/format';
import * as ACTIONS from '~/redux/editor/actions';
import { EDITOR_ACTIONS } from '~/redux/editor/constants';
import { IEditorState } from '~/redux/editor';
import { TIPS } from '~/constants/tips';
const setEditing = (
state,
{ editing }: ReturnType<typeof ACTIONS.editorSetEditing>
): IEditorState => ({
...state,
editing,
});
const setChanged = (
state,
{ changed }: ReturnType<typeof ACTIONS.editorSetChanged>
): IEditorState => ({
...state,
changed,
});
const setMode = (state, { mode }: ReturnType<typeof ACTIONS.editorSetMode>): IEditorState => ({
...state,
mode,
});
const setDistance = (
state,
{ distance }: ReturnType<typeof ACTIONS.editorSetDistance>
): IEditorState => ({
...state,
distance,
estimated: getEstimated(distance, state.speed),
});
const setRouterPoints = (
state,
{ routerPoints }: ReturnType<typeof ACTIONS.editorSetRouterPoints>
): IEditorState => ({
...state,
routerPoints,
});
const setActiveSticker = (
state,
{ activeSticker }: ReturnType<typeof ACTIONS.editorSetActiveSticker>
): IEditorState => ({
...state,
activeSticker: activeSticker || { set: null, sticker: null },
});
const hideRenderer = (state): IEditorState => ({
...state,
renderer: { ...state.renderer, renderer_active: false },
});
const setRenderer = (
state,
{ payload }: ReturnType<typeof ACTIONS.editorSetRenderer>
): IEditorState => ({
...state,
renderer: { ...state.renderer, ...payload },
});
const sendSaveRequest = (state): IEditorState => ({
...state,
save_processing: true,
});
const setSaveError = (
state,
{ save_error }: ReturnType<typeof ACTIONS.editorSetSaveError>
): IEditorState => ({
...state,
save_error,
save_finished: false,
save_processing: false,
});
const setSaveLoading = (
state,
{ save_loading }: ReturnType<typeof ACTIONS.editorSetSaveLoading>
): IEditorState => ({
...state,
save_loading,
});
const setSaveOverwrite = (state): IEditorState => ({
...state,
save_overwriting: true,
save_finished: false,
save_processing: false,
save_error: TIPS.SAVE_OVERWRITE,
});
const setSaveSuccess = (
state,
{ save_error }: ReturnType<typeof ACTIONS.editorSetSaveSuccess>
): IEditorState => ({
...state,
save_overwriting: false,
save_finished: true,
save_processing: false,
save_error,
});
const resetSaveDialog = (state): IEditorState => ({
...state,
save_overwriting: false,
save_finished: false,
save_processing: false,
save_error: '',
});
const setDialog = (state, { dialog }: ReturnType<typeof ACTIONS.editorSetDialog>): IEditorState => ({
...state,
dialog,
});
const setDialogActive = (
state,
{ dialog_active }: ReturnType<typeof ACTIONS.editorSetDialogActive>
): IEditorState => ({
...state,
dialog_active: dialog_active || !state.dialog_active,
});
const setReady = (state, { ready = true }: ReturnType<typeof ACTIONS.editorSetReady>): IEditorState => ({
...state,
ready,
});
const setSpeed = (
state,
{ speed = 15 }: ReturnType<typeof ACTIONS.editorSetSpeed>
): IEditorState => ({
...state,
speed,
estimated: getEstimated(state.distance, speed),
});
const setMarkersShown = (
state,
{ markers_shown = true }: ReturnType<typeof ACTIONS.editorSetMarkersShown>
): IEditorState => ({ ...state, markers_shown });
const setIsEmpty = (
state,
{ is_empty = true }: ReturnType<typeof ACTIONS.editorSetIsEmpty>
): IEditorState => ({ ...state, is_empty });
const setFeature = (
state,
{ features }: ReturnType<typeof ACTIONS.editorSetFeature>
): IEditorState => ({
...state,
features: {
...state.features,
...features,
},
});
const setIsRouting = (
state,
{ is_routing }: ReturnType<typeof ACTIONS.editorSetIsRouting>
): IEditorState => ({
...state,
is_routing,
});
export const EDITOR_HANDLERS = {
[EDITOR_ACTIONS.SET_EDITING]: setEditing,
[EDITOR_ACTIONS.SET_CHANGED]: setChanged,
[EDITOR_ACTIONS.SET_MODE]: setMode,
[EDITOR_ACTIONS.SET_DISTANCE]: setDistance,
[EDITOR_ACTIONS.SET_ROUTER_POINTS]: setRouterPoints,
[EDITOR_ACTIONS.SET_ACTIVE_STICKER]: setActiveSticker,
[EDITOR_ACTIONS.SET_SAVE_ERROR]: setSaveError,
[EDITOR_ACTIONS.SET_SAVE_LOADING]: setSaveLoading,
[EDITOR_ACTIONS.SET_SAVE_OVERWRITE]: setSaveOverwrite,
[EDITOR_ACTIONS.SET_SAVE_SUCCESS]: setSaveSuccess,
[EDITOR_ACTIONS.SEND_SAVE_REQUEST]: sendSaveRequest,
[EDITOR_ACTIONS.RESET_SAVE_DIALOG]: resetSaveDialog,
[EDITOR_ACTIONS.HIDE_RENDERER]: hideRenderer,
[EDITOR_ACTIONS.SET_RENDERER]: setRenderer,
[EDITOR_ACTIONS.SET_DIALOG]: setDialog,
[EDITOR_ACTIONS.SET_DIALOG_ACTIVE]: setDialogActive,
[EDITOR_ACTIONS.SET_READY]: setReady,
[EDITOR_ACTIONS.SET_SPEED]: setSpeed,
[EDITOR_ACTIONS.SET_MARKERS_SHOWN]: setMarkersShown,
[EDITOR_ACTIONS.SET_IS_EMPTY]: setIsEmpty,
[EDITOR_ACTIONS.SET_FEATURE]: setFeature,
[EDITOR_ACTIONS.SET_IS_ROUTING]: setIsRouting,
};

85
src/redux/editor/index.ts Normal file
View file

@ -0,0 +1,85 @@
import { IDialogs } from '~/constants/dialogs';
import { MODES } from '~/constants/modes';
import { createReducer } from 'reduxsauce';
import { EDITOR_HANDLERS } from './handlers';
export interface IEditorState {
changed: boolean,
editing: boolean,
ready: boolean,
markers_shown: boolean;
mode: typeof MODES[keyof typeof MODES],
dialog: IDialogs[keyof IDialogs],
dialog_active: boolean,
routerPoints: number,
distance: number,
estimated: number,
speed: number,
activeSticker: { set?: string, sticker?: string },
is_empty: boolean,
is_published: boolean,
is_routing: boolean,
save_error: string,
save_finished: boolean,
save_overwriting: boolean,
save_processing: boolean,
save_loading: boolean,
features: {
routing: boolean,
},
renderer: {
data: string,
width: number,
height: number
renderer_active: boolean,
info: string,
progress: number,
},
}
const EDITOR_INITIAL_STATE = {
changed: false,
editing: false,
ready: false,
markers_shown: false,
mode: MODES.NONE,
dialog: null,
dialog_active: false,
routerPoints: 0,
distance: 0,
estimated: 0,
speed: 15,
activeSticker: { set: null, sticker: null },
is_published: false,
is_empty: true,
is_routing: false,
save_error: '',
save_finished: false,
save_overwriting: false,
save_processing: false,
save_loading: false,
features: {
routing: false,
},
renderer: {
data: '',
width: 0,
height: 0,
renderer_active: false,
info: '',
progress: 0,
},
}
export const editor = createReducer(EDITOR_INITIAL_STATE, EDITOR_HANDLERS);

256
src/redux/editor/sagas.ts Normal file
View file

@ -0,0 +1,256 @@
import { call, put, takeEvery, takeLatest, select, race } from 'redux-saga/effects';
import { delay, SagaIterator } from 'redux-saga';
import { selectEditor } from '~/redux/editor/selectors';
import {
editorHideRenderer,
editorSetChanged,
editorSetEditing,
editorSetMode,
editorSetReady,
editorSetRenderer,
editorSetDialog,
editorSetDialogActive,
editorClearAll,
editorSetFeature,
editorLocationChanged,
editorKeyPressed,
} from '~/redux/editor/actions';
import { getUrlData, pushPath } from '~/utils/history';
import { MODES } from '~/constants/modes';
import { checkOSRMService } from '~/utils/api';
import { LatLng } from 'leaflet';
import { searchSetTab } from '../user/actions';
import { TABS } from '~/constants/dialogs';
import { EDITOR_ACTIONS } from './constants';
import { getGPXString, downloadGPXTrack } from '~/utils/gpx';
import {
getTilePlacement,
getPolyPlacement,
getStickersPlacement,
fetchImages,
composeArrows,
composeDistMark,
composeImages,
composePoly,
composeStickers,
imageFetcher,
downloadCanvas,
} from '~/utils/renderer';
import { selectMap } from '../map/selectors';
import { selectUser } from '../user/selectors';
import { LOGOS } from '~/constants/logos';
import { loadMapSaga, replaceAddressIfItsBusy } from '../map/sagas';
import { mapSetAddressOrigin } from '../map/actions';
const hideLoader = () => {
document.getElementById('loader').style.opacity = String(0);
document.getElementById('loader').style.pointerEvents = 'none';
return true;
};
function* stopEditingSaga() {
const { changed, editing, mode }: ReturnType<typeof selectEditor> = yield select(selectEditor);
const { address_origin }: ReturnType<typeof selectMap> = yield select(selectMap);
const { path } = getUrlData();
if (!editing) return;
if (changed && mode !== MODES.CONFIRM_CANCEL) {
yield put(editorSetMode(MODES.CONFIRM_CANCEL));
return;
}
yield put(editorSetMode(MODES.NONE));
yield put(editorSetChanged(false));
yield pushPath(`/${address_origin || path}/`);
}
function* checkOSRMServiceSaga() {
const routing = yield call(checkOSRMService, [new LatLng(1, 1), new LatLng(2, 2)]);
yield put(editorSetFeature({ routing }));
}
export function* setReadySaga() {
yield put(editorSetReady(true));
hideLoader();
yield call(checkOSRMServiceSaga);
yield put(searchSetTab(TABS.MY));
}
function* getRenderData() {
yield put(editorSetRenderer({ info: 'Загрузка тайлов', progress: 0.1 }));
const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
const canvas = <HTMLCanvasElement>document.getElementById('renderer');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
const geometry = getTilePlacement();
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, provider);
yield put(editorSetRenderer({ 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: sticker_points, ctx });
yield put(editorSetRenderer({ info: 'Готово', progress: 1 }));
return yield canvas.toDataURL('image/jpeg');
}
function* takeAShotSaga() {
const worker = call(getRenderData);
const { result, timeout } = yield race({
result: worker,
timeout: delay(500),
});
if (timeout) yield put(editorSetMode(MODES.SHOT_PREFETCH));
const data = yield result || worker;
yield put(editorSetMode(MODES.NONE));
yield put(
editorSetRenderer({
data,
renderer_active: true,
width: window.innerWidth,
height: window.innerHeight,
})
);
}
function* getCropData({ x, y, width, height }) {
const { logo }: ReturnType<typeof selectMap> = yield select(selectMap);
const {
renderer: { data },
}: ReturnType<typeof selectEditor> = yield select(selectEditor);
const canvas = <HTMLCanvasElement>document.getElementById('renderer');
canvas.width = width;
canvas.height = height;
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);
}
return yield canvas.toDataURL('image/jpeg');
}
function* cropAShotSaga(params) {
const { title, address }: ReturnType<typeof selectMap> = yield select(selectMap);
yield call(getCropData, params);
const canvas = document.getElementById('renderer') as HTMLCanvasElement;
downloadCanvas(canvas, (title || address).replace(/\./gi, ' '));
return yield put(editorHideRenderer());
}
function* locationChangeSaga({ location }: ReturnType<typeof editorLocationChanged>) {
const {
user: { id, random_url },
}: ReturnType<typeof selectUser> = yield select(selectUser);
const { ready }: ReturnType<typeof selectEditor> = yield select(selectEditor);
const { owner, address }: ReturnType<typeof selectMap> = yield select(selectMap);
if (!ready) return;
const { path, mode } = getUrlData(location);
if (address !== path) {
const map = yield call(loadMapSaga, path);
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' && owner.id !== id) {
return yield call(replaceAddressIfItsBusy, random_url, address);
} else {
yield put(mapSetAddressOrigin(''));
}
if (mode !== 'edit') {
yield put(editorSetEditing(false));
// editor.stopEditing();
} else {
yield put(editorSetEditing(true));
// editor.startEditing();
}
}
function* keyPressedSaga({ key, target }: ReturnType<typeof editorKeyPressed>): any {
if (target === 'INPUT' || target === 'TEXTAREA') {
return;
}
if (key === 'Escape') {
const {
dialog_active,
mode,
renderer: { renderer_active },
}: ReturnType<typeof selectEditor> = yield select(selectEditor);
if (renderer_active) return yield put(editorHideRenderer());
if (dialog_active) return yield put(editorSetDialogActive(false));
if (mode !== MODES.NONE) return yield put(editorSetMode(MODES.NONE));
} else if (key === 'Delete') {
const { editing } = yield select(selectEditor);
if (!editing) return;
const { mode } = yield select(selectUser);
if (mode === MODES.TRASH) {
yield put(editorClearAll());
} else {
yield put(editorSetMode(MODES.TRASH));
}
}
}
function* getGPXTrackSaga(): SagaIterator {
const { route, stickers, title, address }: ReturnType<typeof selectMap> = yield select(selectMap);
// const { title, address }: = yield select(selectUser);
if (!route || route.length <= 0) return;
const track = getGPXString({ route, stickers, title: title || address });
return downloadGPXTrack({ track, title });
}
export function* editorSaga() {
yield takeEvery(EDITOR_ACTIONS.STOP_EDITING, stopEditingSaga);
yield takeLatest(EDITOR_ACTIONS.TAKE_A_SHOT, takeAShotSaga);
yield takeLatest(EDITOR_ACTIONS.CROP_A_SHOT, cropAShotSaga);
yield takeLatest(EDITOR_ACTIONS.LOCATION_CHANGED, locationChangeSaga);
yield takeLatest(EDITOR_ACTIONS.KEY_PRESSED, keyPressedSaga);
yield takeLatest(EDITOR_ACTIONS.GET_GPX_TRACK, getGPXTrackSaga);
}

View file

@ -0,0 +1,7 @@
import { IState } from "../store";
export const selectEditor = (state: IState) => state.editor;
export const selectEditorEditing = (state: IState) => state.editor.editing;
export const selectEditorMode = (state: IState) => state.editor.mode;
export const selectEditorActiveSticker = (state: IState) => state.editor.activeSticker;
export const selectEditorRenderer = (state: IState) => state.editor.renderer;

View file

@ -68,3 +68,8 @@ export const mapSetLogo = (logo: IMapReducer['logo']) => ({
type: MAP_ACTIONS.SET_LOGO,
logo,
});
export const mapSetAddressOrigin = (address_origin: IMapReducer['address_origin']) => ({
type: MAP_ACTIONS.SET_ADDRESS_ORIGIN,
address_origin,
});

View file

@ -7,6 +7,7 @@ export const MAP_ACTIONS = {
SET_TITLE: `${P}-SET_TILE`,
SET_DESCRIPTION: `${P}-SETDESCRIPTION`,
SET_ADDRESS: `${P}-SET_ADDRESS`,
SET_ADDRESS_ORIGIN: `${P}-SET_ADDRESS_ORIGIN`,
SET_OWNER: `${P}-SET_OWNER`,
SET_PUBLIC: `${P}-SET_PUBLIC`,
SET_LOGO: `${P}-SET_LOGO`,

View file

@ -12,6 +12,7 @@ import {
mapSetOwner,
mapSetPublic,
mapSetLogo,
mapSetAddressOrigin,
} from './actions';
const setMap = (state: IMapReducer, { map }: ReturnType<typeof mapSet>): IMapReducer => ({
@ -86,6 +87,11 @@ const setLogo = (state: IMapReducer, { logo }: ReturnType<typeof mapSetLogo>): I
logo,
});
const setAddressOrigin = (state, { address_origin }: ReturnType<typeof mapSetAddressOrigin>): IMapReducer => ({
...state,
address_origin
});
export const MAP_HANDLERS = {
[MAP_ACTIONS.SET_MAP]: setMap,
[MAP_ACTIONS.SET_PROVIDER]: setProvider,
@ -99,4 +105,5 @@ export const MAP_HANDLERS = {
[MAP_ACTIONS.SET_OWNER]: setOwner,
[MAP_ACTIONS.SET_PUBLIC]: setPublic,
[MAP_ACTIONS.SET_LOGO]: setLogo,
[MAP_ACTIONS.SET_ADDRESS_ORIGIN]: setAddressOrigin,
};

View file

@ -12,6 +12,7 @@ export interface IMapReducer {
title: string;
logo: string;
address: string;
address_origin: string;
description: string;
owner: { id: string };
is_public: boolean;
@ -24,6 +25,7 @@ export const MAP_INITIAL_STATE: IMapReducer = {
stickers: [],
title: '',
address: '',
address_origin: '',
description: '',
owner: { id: null },
is_public: false,

View file

@ -1,39 +1,58 @@
import { takeEvery, select, put, call, TakeEffect, race, take, takeLatest } from 'redux-saga/effects';
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 {
mapClicked,
mapAddSticker,
mapSetProvider,
mapSet,
mapSetTitle,
mapSetAddressOrigin,
} from './actions';
import { 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';
editorSetMode,
editorSetChanged,
editorSetEditing,
editorSetReady,
editorSetActiveSticker,
editorSetSaveError,
editorSetSaveLoading,
editorSendSaveRequest,
editorSetSaveSuccess,
editorSetSaveOverwrite,
} from '~/redux/editor/actions';
import { pushLoaderState, getUrlData, pushPath, replacePath } from '~/utils/history';
import { setReadySaga, searchSetSagaWorker } from '~/redux/user/sagas';
import { 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 { selectMap, selectMapProvider } from './selectors';
import { TIPS } from '~/constants/tips';
import { delay } from 'redux-saga';
import { setReadySaga } from '../editor/sagas';
import { selectEditor } from '../editor/selectors';
import { EDITOR_ACTIONS } from '../editor/constants';
function* onMapClick({ latlng }: ReturnType<typeof mapClicked>) {
const mode = yield select(selectUserMode);
const { set, sticker } = yield select(selectUserActiveSticker);
const {
mode,
activeSticker: { set, sticker },
}: ReturnType<typeof selectEditor> = yield select(selectEditor);
switch (mode) {
case MODES.STICKERS:
yield put(mapAddSticker({ latlng, set, sticker, text: '', angle: 0 }));
yield put(setMode(MODES.NONE));
yield put(editorSetMode(MODES.NONE));
break;
default:
@ -64,7 +83,7 @@ function* onMapClick({ latlng }: ReturnType<typeof mapClicked>) {
export function* replaceAddressIfItsBusy(destination, original) {
if (original) {
yield put(setAddressOrigin(original));
yield put(mapSetAddressOrigin(original));
}
pushPath(`/${destination}/edit`);
@ -97,14 +116,15 @@ export function* loadMapSaga(path) {
function* startEmptyEditorSaga() {
const {
user: { id, random_url },
provider = DEFAULT_PROVIDER,
} = yield select(selectUser);
}: ReturnType<typeof selectUser> = yield select(selectUser);
const provider: ReturnType<typeof selectMapProvider> = yield select(selectMapProvider);
// TODO: set owner { id }
pushPath(`/${random_url}/edit`);
yield put(setChanged(false));
yield put(setEditing(true));
yield put(editorSetChanged(false));
yield put(editorSetEditing(true));
return yield call(setReadySaga);
}
@ -114,9 +134,9 @@ export function* mapInitSaga() {
const { path, mode, hash } = getUrlData();
const {
provider,
user: { id },
} = yield select(selectUser);
}: ReturnType<typeof selectUser> = yield select(selectUser);
const provider: ReturnType<typeof selectMapProvider> = yield select(selectMapProvider);
yield put(mapSetProvider(provider));
@ -139,14 +159,12 @@ export function* mapInitSaga() {
yield call(setReadySaga);
yield call(replaceAddressIfItsBusy, map.random_url, map.address);
} else {
yield put(setAddressOrigin(''));
yield put(mapSetAddressOrigin(''));
}
yield put(setEditing(true));
// TODO: start editing
yield put(editorSetEditing(true));
} else {
yield put(setEditing(false));
// TODO: stop editing
yield put(editorSetEditing(false));
}
yield call(setReadySaga);
@ -155,7 +173,7 @@ export function* mapInitSaga() {
}
yield call(startEmptyEditorSaga);
yield put(setReady(true));
yield put(editorSetReady(true));
pushLoaderState(100);
@ -163,7 +181,7 @@ export function* mapInitSaga() {
}
function* setActiveStickerSaga() {
yield put(setMode(MODES.STICKERS));
yield put(editorSetMode(MODES.STICKERS));
}
function* setTitleSaga({ title }: ReturnType<typeof mapSetTitle>) {
@ -172,10 +190,14 @@ function* setTitleSaga({ title }: ReturnType<typeof mapSetTitle>) {
}
}
function* startEditingSaga() {
const { path } = getUrlData();
yield pushPath(`/${path}/edit`);
}
function* clearSaga({ type }) {
switch (type) {
case USER_ACTIONS.CLEAR_POLY:
// TODO: clear router waypoints
case EDITOR_ACTIONS.CLEAR_POLY:
yield put(
mapSet({
route: [],
@ -183,7 +205,7 @@ function* clearSaga({ type }) {
);
break;
case USER_ACTIONS.CLEAR_STICKERS:
case EDITOR_ACTIONS.CLEAR_STICKERS:
yield put(
mapSet({
stickers: [],
@ -191,8 +213,8 @@ function* clearSaga({ type }) {
);
break;
case USER_ACTIONS.CLEAR_ALL:
yield put(setChanged(false));
case EDITOR_ACTIONS.CLEAR_ALL:
yield put(editorSetChanged(false));
yield put(
mapSet({
route: [],
@ -205,8 +227,8 @@ function* clearSaga({ type }) {
break;
}
yield put(setActiveSticker(null)); // TODO: move to maps
yield put(setMode(MODES.NONE));
yield put(editorSetActiveSticker(null));
yield put(editorSetMode(MODES.NONE));
}
function* sendSaveRequestSaga({
@ -215,17 +237,18 @@ function* sendSaveRequestSaga({
force,
is_public,
description,
}: ReturnType<typeof sendSaveRequest>) {
const { route, stickers, provider } = yield select(selectMap);
}: ReturnType<typeof editorSendSaveRequest>) {
const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
if (!route.length && !stickers.length) {
return yield put(setSaveError(TIPS.SAVE_EMPTY)); // TODO: move setSaveError to editor
return yield put(editorSetSaveError(TIPS.SAVE_EMPTY));
}
const { logo, distance } = yield select(selectUser);
const { token } = yield select(selectUserUser);
const { logo }: ReturnType<typeof selectMap> = yield select(selectMap);
const { distance }: ReturnType<typeof selectEditor> = yield select(selectEditor);
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
yield put(setSaveLoading(true)); // TODO: move setSaveLoading to maps
yield put(editorSetSaveLoading(true));
const {
result,
@ -250,20 +273,21 @@ function* sendSaveRequestSaga({
description,
}),
timeout: delay(10000),
cancel: take(USER_ACTIONS.RESET_SAVE_DIALOG),
cancel: take(EDITOR_ACTIONS.RESET_SAVE_DIALOG),
});
yield put(setSaveLoading(false));
yield put(editorSetSaveLoading(false));
if (cancel) return yield put(setMode(MODES.NONE));
if (cancel) return yield put(editorSetMode(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 (result && result.data.code === 'already_exist') return yield put(editorSetSaveOverwrite());
if (result && result.data.code === 'conflict')
return yield put(editorSetSaveError(TIPS.SAVE_EXISTS));
if (timeout || !result || !result.data.route || !result.data.route.address)
return yield put(setSaveError(TIPS.SAVE_TIMED_OUT));
return yield put(editorSetSaveError(TIPS.SAVE_TIMED_OUT));
return yield put( // TODO: move setSaveSuccess to editor
setSaveSuccess({
return yield put(
editorSetSaveSuccess({
address: result.data.route.address,
title: result.data.route.title,
is_public: result.data.route.is_public,
@ -279,18 +303,23 @@ function* setSaveSuccessSaga({
title,
is_public,
description,
}: ReturnType<typeof setSaveSuccess>) {
const { id } = yield select(selectUser);
const { dialog_active } = yield select(selectUser);
}: ReturnType<typeof editorSetSaveSuccess>) {
const { id }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const { dialog_active }: ReturnType<typeof selectEditor> = yield select(selectEditor);
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 }));
yield put(
mapSet({
title,
address,
is_public,
description,
owner: { id },
})
);
yield put(editorSetChanged(false));
if (dialog_active) {
yield call(searchSetSagaWorker);
@ -298,27 +327,25 @@ function* setSaveSuccessSaga({
// yield editor.setInitialData();
// TODO: set initial data here
return
return;
}
export function* mapSaga() {
// TODO: setChanged on set route, logo, provider, stickers
yield takeEvery(USER_ACTIONS.SET_ACTIVE_STICKER, setActiveStickerSaga); // TODO: move active sticker to maps
yield takeEvery(EDITOR_ACTIONS.START_EDITING, startEditingSaga);
yield takeEvery(EDITOR_ACTIONS.SET_ACTIVE_STICKER, setActiveStickerSaga);
yield takeEvery(MAP_ACTIONS.MAP_CLICKED, onMapClick);
yield takeEvery(MAP_ACTIONS.SET_TITLE, setTitleSaga);
// yield takeEvery(USER_ACTIONS.SET_LOGO, setLogoSaga);
yield takeLatest(USER_ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga);
yield takeLatest(USER_ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga);
yield takeLatest(EDITOR_ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga);
yield takeLatest(EDITOR_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,
EDITOR_ACTIONS.CLEAR_POLY,
EDITOR_ACTIONS.CLEAR_STICKERS,
EDITOR_ACTIONS.CLEAR_ALL,
EDITOR_ACTIONS.CLEAR_CANCEL,
],
clearSaga
);

View file

@ -4,13 +4,20 @@ import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import createSagaMiddleware from 'redux-saga';
import { createBrowserHistory } from 'history';
import { editorLocationChanged } from '~/redux/editor/actions';
import { PersistConfig, Persistor } from "redux-persist/es/types";
import { userReducer, IRootReducer } from '~/redux/user';
import { userSaga } from '~/redux/user/sagas';
import { mapSaga } from '~/redux/map/sagas';
import { createBrowserHistory } from 'history';
import { locationChanged } from '~/redux/user/actions';
import { PersistConfig, Persistor } from "redux-persist/es/types";
import { editor, IEditorState } from '~/redux/editor';
import { editorSaga } from '~/redux/editor/sagas';
import { map, IMapReducer } from '~/redux/map';
import { mapSaga } from '~/redux/map/sagas';
const userPersistConfig: PersistConfig = {
key: 'user',
@ -21,6 +28,7 @@ const userPersistConfig: PersistConfig = {
export interface IState {
user: IRootReducer
map: IMapReducer,
editor: IEditorState,
}
// create the saga middleware
export const sagaMiddleware = createSagaMiddleware();
@ -36,14 +44,15 @@ export const store = createStore(
combineReducers({
user: persistReducer(userPersistConfig, userReducer),
map,
// routing: routerReducer
editor,
}),
composeEnhancers(applyMiddleware(/* routerMiddleware(history), */ sagaMiddleware))
composeEnhancers(applyMiddleware(sagaMiddleware))
);
export function configureStore(): { store: Store<any>, persistor: Persistor } {
sagaMiddleware.run(userSaga);
sagaMiddleware.run(mapSaga);
sagaMiddleware.run(editorSaga);
const persistor = persistStore(store);
@ -54,5 +63,5 @@ export const history = createBrowserHistory();
history.listen((location, action) => {
if (action === 'REPLACE') return;
store.dispatch(locationChanged(location.pathname));
store.dispatch(editorLocationChanged(location.pathname));
});

View file

@ -1,81 +1,81 @@
import { USER_ACTIONS } from '~/redux/user/constants';
import { IUser } from "~/constants/auth";
import { IRootState } from "~/redux/user";
import { IRoute } from '~/redux/map/types';
// import { IRootState } from "~/redux/user";
// import { IRoute } from '~/redux/map/types';
export const setUser = (user: IUser) => ({ type: USER_ACTIONS.SET_USER, user });
export const userLogout = () => ({ type: USER_ACTIONS.USER_LOGOUT });
export const setEditing = (editing: IRootState['editing']) => ({ type: USER_ACTIONS.SET_EDITING, editing });
export const setMode = (mode: IRootState['mode']) => ({ type: USER_ACTIONS.SET_MODE, mode });
export const setDistance = (distance: IRootState['distance']) => ({ type: USER_ACTIONS.SET_DISTANCE, distance });
export const setChanged = (changed: IRootState['changed']) => ({ type: USER_ACTIONS.SET_CHANGED, changed });
export const setRouterPoints = routerPoints => ({ type: USER_ACTIONS.SET_ROUTER_POINTS, routerPoints });
export const setActiveSticker = activeSticker => ({ type: USER_ACTIONS.SET_ACTIVE_STICKER, activeSticker });
// export const setEditing = (editing: IRootState['editing']) => ({ type: USER_ACTIONS.SET_EDITING, editing });
// export const setMode = (mode: IRootState['mode']) => ({ type: USER_ACTIONS.SET_MODE, mode });
// export const setDistance = (distance: IRootState['distance']) => ({ type: USER_ACTIONS.SET_DISTANCE, distance });
// export const setChanged = (changed: IRootState['changed']) => ({ type: USER_ACTIONS.SET_CHANGED, changed });
// export const setRouterPoints = routerPoints => ({ type: USER_ACTIONS.SET_ROUTER_POINTS, routerPoints });
// export const setActiveSticker = activeSticker => ({ type: USER_ACTIONS.SET_ACTIVE_STICKER, activeSticker });
// export const setLogo = logo => ({ type: USER_ACTIONS.SET_LOGO, logo });
// export const setTitle = title => ({ type: USER_ACTIONS.SET_TITLE, title });
// export const setDescription = description => ({ type: USER_ACTIONS.SET_DESCRIPTION, description });
// export const setAddress = address => ({ type: USER_ACTIONS.SET_ADDRESS, address });
export const setAddressOrigin = address_origin => ({ type: USER_ACTIONS.SET_ADDRESS_ORIGIN, address_origin });
// export const setAddressOrigin = address_origin => ({ type: USER_ACTIONS.SET_ADDRESS_ORIGIN, address_origin });
// export const setPublic = is_public => ({ type: USER_ACTIONS.SET_PUBLIC, is_public });
export const setStarred = is_published => ({ type: USER_ACTIONS.SET_STARRED, is_published });
export const setSpeed = speed => ({ type: USER_ACTIONS.SET_SPEED, speed });
// export const setSpeed = speed => ({ type: USER_ACTIONS.SET_SPEED, speed });
export const startEditing = () => ({ type: USER_ACTIONS.START_EDITING });
export const stopEditing = () => ({ type: USER_ACTIONS.STOP_EDITING });
// export const startEditing = () => ({ type: USER_ACTIONS.START_EDITING });
// export const stopEditing = () => ({ type: USER_ACTIONS.STOP_EDITING });
export const routerCancel = () => ({ type: USER_ACTIONS.ROUTER_CANCEL });
export const routerSubmit = () => ({ type: USER_ACTIONS.ROUTER_SUBMIT });
// export const routerCancel = () => ({ type: USER_ACTIONS.ROUTER_CANCEL });
// export const routerSubmit = () => ({ type: USER_ACTIONS.ROUTER_SUBMIT });
export const clearPoly = () => ({ type: USER_ACTIONS.CLEAR_POLY });
export const clearStickers = () => ({ type: USER_ACTIONS.CLEAR_STICKERS });
export const clearAll = () => ({ type: USER_ACTIONS.CLEAR_ALL });
export const clearCancel = () => ({ type: USER_ACTIONS.CLEAR_CANCEL });
// export const clearPoly = () => ({ type: USER_ACTIONS.CLEAR_POLY });
// export const clearStickers = () => ({ type: USER_ACTIONS.CLEAR_STICKERS });
// export const clearAll = () => ({ type: USER_ACTIONS.CLEAR_ALL });
// export const clearCancel = () => ({ type: USER_ACTIONS.CLEAR_CANCEL });
export const sendSaveRequest = (payload: {
title: IRoute['title'],
address: IRoute['address'],
is_public: IRoute['is_public'],
description: IRoute['description'],
force: boolean,
}) => ({
type: USER_ACTIONS.SEND_SAVE_REQUEST,
...payload,
});
// export const sendSaveRequest = (payload: {
// title: IRoute['title'],
// address: IRoute['address'],
// is_public: IRoute['is_public'],
// description: IRoute['description'],
// force: boolean,
// }) => ({
// type: USER_ACTIONS.SEND_SAVE_REQUEST,
// ...payload,
// });
export const resetSaveDialog = () => ({ type: USER_ACTIONS.RESET_SAVE_DIALOG });
// export const resetSaveDialog = () => ({ type: USER_ACTIONS.RESET_SAVE_DIALOG });
export const setSaveLoading = (save_loading: IRootState['save_loading']) => ({ type: USER_ACTIONS.SET_SAVE_LOADING, save_loading });
// export const setSaveLoading = (save_loading: IRootState['save_loading']) => ({ type: USER_ACTIONS.SET_SAVE_LOADING, save_loading });
export const setSaveSuccess = (payload: {
address: IRoute['address'],
title: IRoute['address'],
is_public: IRoute['is_public'],
description: IRoute['description'],
// export const setSaveSuccess = (payload: {
// address: IRoute['address'],
// title: IRoute['address'],
// is_public: IRoute['is_public'],
// description: IRoute['description'],
save_error: string,
}) => ({ type: USER_ACTIONS.SET_SAVE_SUCCESS, ...payload });
// save_error: string,
// }) => ({ type: USER_ACTIONS.SET_SAVE_SUCCESS, ...payload });
export const setSaveError = (save_error: IRootState['save_error']) => ({ type: USER_ACTIONS.SET_SAVE_ERROR, save_error });
export const setSaveOverwrite = () => ({ type: USER_ACTIONS.SET_SAVE_OVERWRITE });
// export const setSaveError = (save_error: IRootState['save_error']) => ({ type: USER_ACTIONS.SET_SAVE_ERROR, save_error });
// export const setSaveOverwrite = () => ({ type: USER_ACTIONS.SET_SAVE_OVERWRITE });
export const hideRenderer = () => ({ type: USER_ACTIONS.HIDE_RENDERER });
export const setRenderer = payload => ({ type: USER_ACTIONS.SET_RENDERER, payload });
export const takeAShot = () => ({ type: USER_ACTIONS.TAKE_A_SHOT });
export const cropAShot = payload => ({ type: USER_ACTIONS.CROP_A_SHOT, ...payload });
// export const hideRenderer = () => ({ type: USER_ACTIONS.HIDE_RENDERER });
// export const setRenderer = payload => ({ type: USER_ACTIONS.SET_RENDERER, payload });
// export const takeAShot = () => ({ type: USER_ACTIONS.TAKE_A_SHOT });
// export const cropAShot = payload => ({ type: USER_ACTIONS.CROP_A_SHOT, ...payload });
// export const setProvider = provider => ({ type: USER_ACTIONS.SET_PROVIDER, provider });
// export const changeProvider = provider => ({ type: USER_ACTIONS.CHANGE_PROVIDER, provider });
export const setDialog = dialog => ({ type: USER_ACTIONS.SET_DIALOG, dialog });
export const setDialogActive = dialog_active => ({ type: USER_ACTIONS.SET_DIALOG_ACTIVE, dialog_active });
// export const setDialog = dialog => ({ type: USER_ACTIONS.SET_DIALOG, dialog });
// export const setDialogActive = dialog_active => ({ type: USER_ACTIONS.SET_DIALOG_ACTIVE, dialog_active });
export const openMapDialog = tab => ({ type: USER_ACTIONS.OPEN_MAP_DIALOG, tab });
export const locationChanged = location => ({ type: USER_ACTIONS.LOCATION_CHANGED, location });
export const setReady = ready => ({ type: USER_ACTIONS.SET_READY, ready });
// export const locationChanged = location => ({ type: USER_ACTIONS.LOCATION_CHANGED, location });
// export const setReady = ready => ({ type: USER_ACTIONS.SET_READY, ready });
export const gotVkUser = user => ({ type: USER_ACTIONS.GOT_VK_USER, user });
export const keyPressed = ({ key, target: { tagName } }) => ({ type: USER_ACTIONS.KEY_PRESSED, key, target: tagName });
// export const keyPressed = ({ key, target: { tagName } }) => ({ type: USER_ACTIONS.KEY_PRESSED, key, target: tagName });
export const searchSetTitle = title => ({ type: USER_ACTIONS.SEARCH_SET_TITLE, title });
export const searchSetDistance = distance => ({ type: USER_ACTIONS.SEARCH_SET_DISTANCE, distance });
@ -85,15 +85,15 @@ export const searchSetLoading = loading => ({ type: USER_ACTIONS.SEARCH_SET_LOAD
export const searchPutRoutes = payload => ({ type: USER_ACTIONS.SEARCH_PUT_ROUTES, ...payload });
export const setMarkersShown = markers_shown => ({ type: USER_ACTIONS.SET_MARKERS_SHOWN, markers_shown });
export const getGPXTrack = () => ({ type: USER_ACTIONS.GET_GPX_TRACK });
export const setIsEmpty = is_empty => ({ type: USER_ACTIONS.SET_IS_EMPTY, is_empty });
// export const getGPXTrack = () => ({ type: USER_ACTIONS.GET_GPX_TRACK });
// export const setMarkersShown = markers_shown => ({ type: USER_ACTIONS.SET_MARKERS_SHOWN, markers_shown });
// export const setIsEmpty = is_empty => ({ type: USER_ACTIONS.SET_IS_EMPTY, is_empty });
export const mapsLoadMore = () => ({ type: USER_ACTIONS.MAPS_LOAD_MORE });
export const mapsSetShift = (shift: number) => ({ type: USER_ACTIONS.MAPS_SET_SHIFT, shift });
export const setFeature = (features: { [x: string]: boolean }) => ({ type: USER_ACTIONS.SET_FEATURE, features });
export const setIsRouting = (is_routing: boolean) => ({ type: USER_ACTIONS.SET_IS_ROUTING, is_routing });
// export const setFeature = (features: { [x: string]: boolean }) => ({ type: USER_ACTIONS.SET_FEATURE, features });
// export const setIsRouting = (is_routing: boolean) => ({ type: USER_ACTIONS.SET_IS_ROUTING, is_routing });
export const dropRoute = (address: string) => ({ type: USER_ACTIONS.DROP_ROUTE, address });
export const modifyRoute = (address: string, { title, is_public }: { title: string, is_public: boolean }) => ({

View file

@ -1,61 +1,7 @@
export interface IActions {
[x: string]: string,
}
export const USER_ACTIONS: IActions = {
export const USER_ACTIONS = {
SET_USER: 'SET_USER',
USER_LOGOUT: 'USER_LOGOUT',
SET_EDITING: 'SET_EDITING',
SET_MODE: 'SET_MODE',
SET_DISTANCE: 'SET_DISTANCE',
SET_CHANGED: 'SET_CHANGED',
SET_ROUTER_POINTS: 'SET_ROUTER_POINTS',
SET_ACTIVE_STICKER: 'SET_ACTIVE_STICKER',
SET_LOGO: 'SET_LOGO',
SET_TITLE: 'SET_TITLE',
SET_ADDRESS: 'SET_ADDRESS',
SET_ADDRESS_ORIGIN: 'SET_ADDRESS_ORIGIN',
SET_PUBLIC: 'SET_PUBLIC',
SET_STARRED: 'SET_STARRED',
SET_DESCRIPTION: 'SET_DESCRIPTION',
START_EDITING: 'START_EDITING',
STOP_EDITING: 'STOP_EDITING',
ROUTER_CANCEL: 'ROUTER_CANCEL',
ROUTER_SUBMIT: 'ROUTER_SUBMIT',
CLEAR_POLY: 'CLEAR_POLY',
CLEAR_STICKERS: 'CLEAR_STICKERS',
CLEAR_ALL: 'CLEAR_ALL',
CLEAR_CANCEL: 'CLEAR_CANCEL',
SEND_SAVE_REQUEST: 'SEND_SAVE_REQUEST',
SET_SAVE_LOADING: 'SET_SAVE_LOADING',
CANCEL_SAVE_REQUEST: 'CANCEL_SAVE_REQUEST',
RESET_SAVE_DIALOG: 'RESET_SAVE_DIALOG',
SET_SAVE_SUCCESS: 'SET_SAVE_SUCCESS',
SET_SAVE_ERROR: 'SET_SAVE_ERROR',
SET_SAVE_OVERWRITE: 'SET_SAVE_OVERWRITE',
SHOW_RENDERER: 'SHOW_RENDERER',
HIDE_RENDERER: 'HIDE_RENDERER',
SET_RENDERER: 'SET_RENDERER',
TAKE_A_SHOT: 'TAKE_A_SHOT',
CROP_A_SHOT: 'CROP_A_SHOT',
SET_PROVIDER: 'SET_PROVIDER',
CHANGE_PROVIDER: 'CHANGE_PROVIDER',
SET_DIALOG: 'SET_DIALOG',
SET_DIALOG_ACTIVE: 'SET_DIALOG_ACTIVE',
LOCATION_CHANGED: 'LOCATION_CHANGED',
SET_READY: 'SET_READY',
GOT_VK_USER: 'GOT_VK_USER',
KEY_PRESSED: 'KEY_PRESSED',
IFRAME_LOGIN_VK: 'IFRAME_LOGIN_VK',
@ -68,21 +14,13 @@ export const USER_ACTIONS: IActions = {
SEARCH_SET_LOADING: 'SEARCH_SET_LOADING',
OPEN_MAP_DIALOG: 'OPEN_MAP_DIALOG',
SET_SPEED: 'SET_SPEED',
SET_MARKERS_SHOWN: 'SET_MARKERS_SHOWN',
GET_GPX_TRACK: 'GET_GPX_TRACK',
SET_IS_EMPTY: 'SET_IS_EMPTY',
MAPS_LOAD_MORE: 'MAPS_LOAD_MORE',
MAPS_SET_SHIFT: 'MAPS_SET_SHIFT',
SET_FEATURE: 'SET_FEATURE',
SET_IS_ROUTING: 'SET_IS_ROUTING',
DROP_ROUTE: 'DROP_ROUTE',
SET_STARRED: 'SET_STARRED',
MODIFY_ROUTE: 'MODIFY_ROUTE',
SET_ROUTE_STARRED: 'SET_ROUTE_STARRED',
TOGGLE_ROUTE_STARRED: 'TOGGLE_ROUTE_STARRED',
};

View file

@ -1,6 +1,5 @@
import { IRootState } from ".";
import * as ActionCreators from './actions'
import { TIPS } from "~/constants/tips";
import { TABS } from "~/constants/dialogs";
import { USER_ACTIONS } from "./constants";
@ -10,11 +9,6 @@ export interface ActionHandler<T> {
(state: IRootState, payload: UnsafeReturnType<T>): IRootState;
}
const getEstimated = (distance: number, speed: number = 15): number => {
const time = (distance && (distance / speed)) || 0;
return (time && parseFloat(time.toFixed(1)));
};
const setUser: ActionHandler<typeof ActionCreators.setUser> = (state, { user }) => ({
...state,
user: {
@ -23,121 +17,6 @@ const setUser: ActionHandler<typeof ActionCreators.setUser> = (state, { user })
},
});
const setEditing: ActionHandler<typeof ActionCreators.setEditing> = (state, { editing }) => ({
...state, editing
});
const setChanged: ActionHandler<typeof ActionCreators.setChanged> = (state, { changed }) => ({
...state,
changed
});
const setMode: ActionHandler<typeof ActionCreators.setMode> = (state, { mode }) => ({
...state,
mode
});
const setDistance: ActionHandler<typeof ActionCreators.setDistance> = (state, { distance }) => ({
...state,
distance,
estimated: getEstimated(distance, state.speed),
});
const setRouterPoints: ActionHandler<typeof ActionCreators.setRouterPoints> = (state, { routerPoints }) => ({
...state,
routerPoints,
});
const setActiveSticker: ActionHandler<typeof ActionCreators.setActiveSticker> = (state, { activeSticker }) => ({
...state,
activeSticker: activeSticker || { set: null, sticker: null }
});
// const setLogo: ActionHandler<typeof ActionCreators.setLogo> = (state, { logo }) => ({
// ...state,
// logo
// });
// const setTitle: ActionHandler<typeof ActionCreators.setTitle> = (state, { title }) => ({
// ...state,
// title
// });
// const setDescription: ActionHandler<typeof ActionCreators.setDescription> = (state, { description }) => ({
// ...state,
// description
// });
// const setAddress: ActionHandler<typeof ActionCreators.setAddress> = (state, { address }) => ({
// ...state,
// address
// });
const setAddressOrigin: ActionHandler<typeof ActionCreators.setAddressOrigin> = (state, { address_origin }) => ({
...state,
address_origin
});
const sendSaveRequest: ActionHandler<typeof ActionCreators.sendSaveRequest> = (state) => ({
...state,
save_processing: true,
});
const setSaveError: ActionHandler<typeof ActionCreators.setSaveError> = (state, { save_error }) => ({
...state, save_error, save_finished: false, save_processing: false
});
const setSaveLoading: ActionHandler<typeof ActionCreators.setSaveLoading> = (state, { save_loading }) => ({
...state, save_loading
});
const setSaveOverwrite: ActionHandler<typeof ActionCreators.setSaveOverwrite> = (state) => ({
...state,
save_overwriting: true,
save_finished: false,
save_processing: false,
save_error: TIPS.SAVE_OVERWRITE,
});
const setSaveSuccess: ActionHandler<typeof ActionCreators.setSaveSuccess> = (state, { save_error }) => ({
...state,
save_overwriting: false,
save_finished: true,
save_processing: false,
save_error,
});
const resetSaveDialog: ActionHandler<typeof ActionCreators.resetSaveDialog> = (state) => ({
...state, save_overwriting: false, save_finished: false, save_processing: false, save_error: '',
});
const hideRenderer: ActionHandler<typeof ActionCreators.hideRenderer> = (state) => ({
...state,
renderer: { ...state.renderer, renderer_active: false }
});
const setRenderer: ActionHandler<typeof ActionCreators.setRenderer> = (state, { payload }) => ({
...state,
renderer: { ...state.renderer, ...payload }
});
// const setProvider: ActionHandler<typeof ActionCreators.setProvider> = (state, { provider }) => ({ ...state, provider });
const setDialog: ActionHandler<typeof ActionCreators.setDialog> = (state, { dialog }) => ({
...state,
dialog,
});
const setDialogActive: ActionHandler<typeof ActionCreators.setDialogActive> = (state, { dialog_active }) => ({
...state,
dialog_active: dialog_active || !state.dialog_active,
});
const setReady: ActionHandler<typeof ActionCreators.setReady> = (state, { ready = true }) => ({
...state,
ready,
});
const searchSetTitle: ActionHandler<typeof ActionCreators.searchSetTitle> = (state, { title = '' }) => ({
...state,
routes: {
@ -199,17 +78,8 @@ const searchSetLoading: ActionHandler<typeof ActionCreators.searchSetLoading> =
}
});
// const setPublic: ActionHandler<typeof ActionCreators.setPublic> = (state, { is_public = false }) => ({ ...state, is_public });
const setStarred: ActionHandler<typeof ActionCreators.setStarred> = (state, { is_published = false }) => ({ ...state, is_published });
const setSpeed: ActionHandler<typeof ActionCreators.setSpeed> = (state, { speed = 15 }) => ({
...state,
speed,
estimated: getEstimated(state.distance, speed),
});
const setMarkersShown: ActionHandler<typeof ActionCreators.setMarkersShown> = (state, { markers_shown = true }) => ({ ...state, markers_shown });
const setIsEmpty: ActionHandler<typeof ActionCreators.setIsEmpty> = (state, { is_empty = true }) => ({ ...state, is_empty });
const mapsSetShift: ActionHandler<typeof ActionCreators.mapsSetShift> = (state, { shift = 0 }) => ({
...state,
routes: {
@ -218,19 +88,6 @@ const mapsSetShift: ActionHandler<typeof ActionCreators.mapsSetShift> = (state,
}
});
const setFeature: ActionHandler<typeof ActionCreators.setFeature> = (state, { features }) => ({
...state,
features: {
...state.features,
...features,
}
});
const setIsRouting: ActionHandler<typeof ActionCreators.setIsRouting> = (state, { is_routing }) => ({
...state,
is_routing,
});
const setRouteStarred: ActionHandler<typeof ActionCreators.setRouteStarred> = (state, { address, is_published }) => ({
...state,
routes: {
@ -248,33 +105,6 @@ const setRouteStarred: ActionHandler<typeof ActionCreators.setRouteStarred> = (s
export const USER_HANDLERS = ({
[USER_ACTIONS.SET_USER]: setUser,
[USER_ACTIONS.SET_EDITING]: setEditing,
[USER_ACTIONS.SET_CHANGED]: setChanged,
[USER_ACTIONS.SET_MODE]: setMode,
[USER_ACTIONS.SET_DISTANCE]: setDistance,
[USER_ACTIONS.SET_ROUTER_POINTS]: setRouterPoints,
[USER_ACTIONS.SET_ACTIVE_STICKER]: setActiveSticker,
// [USER_ACTIONS.SET_LOGO]: setLogo,
// [USER_ACTIONS.SET_TITLE]: setTitle,
// [USER_ACTIONS.SET_DESCRIPTION]: setDescription,
// [USER_ACTIONS.SET_ADDRESS]: setAddress,
[USER_ACTIONS.SET_ADDRESS_ORIGIN]: setAddressOrigin,
[USER_ACTIONS.SET_SAVE_ERROR]: setSaveError,
[USER_ACTIONS.SET_SAVE_LOADING]: setSaveLoading,
[USER_ACTIONS.SET_SAVE_OVERWRITE]: setSaveOverwrite,
[USER_ACTIONS.SET_SAVE_SUCCESS]: setSaveSuccess,
[USER_ACTIONS.SEND_SAVE_REQUEST]: sendSaveRequest,
[USER_ACTIONS.RESET_SAVE_DIALOG]: resetSaveDialog,
[USER_ACTIONS.HIDE_RENDERER]: hideRenderer,
[USER_ACTIONS.SET_RENDERER]: setRenderer,
// [USER_ACTIONS.SET_PROVIDER]: setProvider,
[USER_ACTIONS.SET_DIALOG]: setDialog,
[USER_ACTIONS.SET_DIALOG_ACTIVE]: setDialogActive,
[USER_ACTIONS.SET_READY]: setReady,
[USER_ACTIONS.SEARCH_SET_TITLE]: searchSetTitle,
[USER_ACTIONS.SEARCH_SET_DISTANCE]: searchSetDistance,
@ -282,16 +112,10 @@ export const USER_HANDLERS = ({
[USER_ACTIONS.SEARCH_SET_TAB]: searchSetTab,
[USER_ACTIONS.SEARCH_PUT_ROUTES]: searchPutRoutes,
[USER_ACTIONS.SEARCH_SET_LOADING]: searchSetLoading,
// [USER_ACTIONS.SET_PUBLIC]: setPublic,
[USER_ACTIONS.SET_STARRED]: setStarred,
[USER_ACTIONS.SET_SPEED]: setSpeed,
[USER_ACTIONS.SET_MARKERS_SHOWN]: setMarkersShown,
[USER_ACTIONS.SET_IS_EMPTY]: setIsEmpty,
[USER_ACTIONS.MAPS_SET_SHIFT]: mapsSetShift,
[USER_ACTIONS.SET_FEATURE]: setFeature,
[USER_ACTIONS.SET_IS_ROUTING]: setIsRouting,
[USER_ACTIONS.SET_STARRED]: setStarred,
[USER_ACTIONS.SET_ROUTE_STARRED]: setRouteStarred,
});

View file

@ -1,8 +1,5 @@
import { createReducer } from 'reduxsauce';
import { DEFAULT_USER, IUser } from '~/constants/auth';
import { MODES } from '~/constants/modes';
import { DIALOGS, IDialogs } from '~/constants/dialogs';
import { IStickers } from "~/constants/stickers";
import { USER_HANDLERS } from './handlers';
export interface IRouteListItem {
@ -15,50 +12,8 @@ export interface IRouteListItem {
}
export interface IRootReducer {
ready: boolean,
// ready: boolean,
user: IUser,
editing: boolean,
mode: typeof MODES[keyof typeof MODES],
// logo: keyof typeof LOGOS,
routerPoints: number,
distance: number,
// description: string,
estimated: number,
speed: number,
activeSticker: { set?: keyof IStickers, sticker?: string },
// title: string,
// address: string,
// address_origin: string,
changed: boolean,
// provider: keyof typeof PROVIDERS,
markers_shown: boolean,
is_published: boolean,
// is_public: boolean,
is_empty: boolean,
is_routing: boolean,
save_error: string,
save_finished: boolean,
save_overwriting: boolean,
save_processing: boolean,
save_loading: boolean,
dialog: IDialogs[keyof IDialogs],
dialog_active: boolean,
features: {
routing: boolean,
},
renderer: {
data: string,
width: number,
height: number
renderer_active: boolean,
info: string,
progress: number,
},
routes: {
limit: 0,
@ -81,53 +36,8 @@ export interface IRootReducer {
export type IRootState = Readonly<IRootReducer>;
export const INITIAL_STATE: IRootReducer = {
ready: false,
user: { ...DEFAULT_USER },
mode: MODES.NONE,
// logo: DEFAULT_LOGO,
routerPoints: 0,
distance: 0,
// description: '',
estimated: 0,
speed: 15,
activeSticker: { set: null, sticker: null },
// title: '',
// address: '',
// address_origin: '',
// provider: DEFAULT_PROVIDER,
markers_shown: true,
changed: false,
editing: false,
is_published: false,
// is_public: false,
is_empty: true,
is_routing: false,
save_error: '',
save_finished: false,
save_overwriting: false,
save_processing: false,
save_loading: false,
dialog: DIALOGS.NONE,
dialog_active: false,
features: {
routing: false,
},
renderer: {
data: '',
width: 0,
height: 0,
renderer_active: false,
info: '',
progress: 0,
},
routes: {
limit: 0,
loading: false, // <-- maybe delete this

View file

@ -19,23 +19,23 @@ import {
sendRouteStarred,
} from '~/utils/api';
import {
hideRenderer,
searchPutRoutes,
searchSetLoading,
setChanged,
setDialogActive,
setEditing,
setMode,
setReady,
setRenderer,
// hideRenderer,
// setChanged,
// setDialogActive,
// setEditing,
// setMode,
// setReady,
// setRenderer,
// setDialog,
// setAddressOrigin,
// clearAll,
// setFeature,
searchSetTab,
setUser,
setDialog,
setAddressOrigin,
mapsSetShift,
searchChangeDistance,
clearAll,
setFeature,
searchPutRoutes,
searchSetLoading,
searchSetTitle,
setRouteStarred,
} from '~/redux/user/actions';
@ -48,43 +48,22 @@ import {
pushPath,
} from '~/utils/history';
import { USER_ACTIONS } from '~/redux/user/constants';
import { MODES } from '~/constants/modes';
import { DEFAULT_USER } from '~/constants/auth';
import {
composeArrows,
composeDistMark,
composeImages,
composePoly,
composeStickers,
downloadCanvas,
fetchImages,
getPolyPlacement,
getStickersPlacement,
getTilePlacement,
imageFetcher,
} from '~/utils/renderer';
import { LOGOS } from '~/constants/logos';
import { DIALOGS, TABS } from '~/constants/dialogs';
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';
import { editorSetDialog, editorSetDialogActive } from '../editor/actions';
import { selectEditor } from '../editor/selectors';
import { EDITOR_ACTIONS } from '../editor/constants';
// 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';
return true;
};
function* generateGuestSaga() {
const {
data: { user, random_url },
@ -95,52 +74,43 @@ function* generateGuestSaga() {
return { ...user, random_url };
}
function* startEditingSaga() {
const { path } = getUrlData();
yield pushPath(`/${path}/edit`);
}
// function* stopEditingSaga() {
// const { changed, editing, mode, address_origin } = yield select(selectUser);
// const { path } = getUrlData();
function* stopEditingSaga() {
const { changed, editing, mode, address_origin } = yield select(selectUser);
const { path } = getUrlData();
// if (!editing) return;
// if (changed && mode !== MODES.CONFIRM_CANCEL) {
// yield put(setMode(MODES.CONFIRM_CANCEL));
// return;
// }
if (!editing) return;
if (changed && mode !== MODES.CONFIRM_CANCEL) {
yield put(setMode(MODES.CONFIRM_CANCEL));
return;
}
// yield put(setMode(MODES.NONE));
// yield put(setChanged(false));
// TODO: cancel editing?
// yield editor.cancelEditing();
yield put(setMode(MODES.NONE));
yield put(setChanged(false));
// 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}/`);
// }
yield pushPath(`/${address_origin || path}/`);
}
// function* checkOSRMServiceSaga() {
// const routing = yield call(checkOSRMService, [new LatLng(1,1), new LatLng(2,2)]);
function* checkOSRMServiceSaga() {
const routing = yield call(checkOSRMService, [new LatLng(1,1), new LatLng(2,2)]);
// yield put(setFeature({ routing }));
// }
yield put(setFeature({ routing }));
}
// export function* setReadySaga() {
// yield put(setReady(true));
// hideLoader();
export function* setReadySaga() {
yield put(setReady(true));
hideLoader();
yield call(checkOSRMServiceSaga);
yield put(searchSetTab(TABS.MY));
}
// yield call(checkOSRMServiceSaga);
// yield put(searchSetTab(TABS.MY));
// }
function* authCheckSaga({ key }: RehydrateAction) {
if (key !== 'user') return;
pushLoaderState(70);
const { id, token } = yield select(selectUserUser);
const { ready } = yield select(selectUser);
const { id, token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const { ready }: ReturnType<typeof selectEditor> = yield select(selectEditor);
if (window.location.search || true) {
const { viewer_id, auth_key } = yield parseQuery(window.location.search);
@ -215,128 +185,127 @@ function* authCheckSaga({ key }: RehydrateAction) {
// return true;
// }
// function* getRenderData() {
// yield put(setRenderer({ info: 'Загрузка тайлов', progress: 0.1 }));
function* getRenderData() {
yield put(setRenderer({ info: 'Загрузка тайлов', progress: 0.1 }));
// const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
// const canvas = <HTMLCanvasElement>document.getElementById('renderer');
// canvas.width = window.innerWidth;
// canvas.height = window.innerHeight;
// const ctx = canvas.getContext('2d');
const canvas = <HTMLCanvasElement>document.getElementById('renderer');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
// const geometry = getTilePlacement();
// const points = getPolyPlacement(route);
// const sticker_points = getStickersPlacement(stickers);
// // TODO: get distance:
// const distance = 0;
// // const distance = editor.poly.poly.distance;
const geometry = getTilePlacement();
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);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// const images = yield fetchImages(ctx, geometry, provider);
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: sticker_points, ctx });
yield composeImages({ geometry, images, ctx });
yield composePoly({ points, ctx });
yield composeArrows({ points, ctx });
yield composeDistMark({ ctx, points, distance });
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() {
// const worker = call(getRenderData);
function* takeAShotSaga() {
const worker = call(getRenderData);
// const { result, timeout } = yield race({
// result: worker,
// timeout: delay(500),
// });
const { result, timeout } = yield race({
result: worker,
timeout: delay(500),
});
// if (timeout) yield put(setMode(MODES.SHOT_PREFETCH));
if (timeout) yield put(setMode(MODES.SHOT_PREFETCH));
// const data = yield result || worker;
const data = yield result || worker;
// yield put(setMode(MODES.NONE));
// yield put(
// setRenderer({
// data,
// renderer_active: true,
// width: window.innerWidth,
// height: window.innerHeight,
// })
// );
// }
yield put(setMode(MODES.NONE));
yield put(
setRenderer({
data,
renderer_active: true,
width: window.innerWidth,
height: window.innerHeight,
})
);
}
// function* getCropData({ x, y, width, height }) {
// const {
// logo,
// renderer: { data },
// } = yield select(selectUser);
// const canvas = <HTMLCanvasElement>document.getElementById('renderer');
// canvas.width = width;
// canvas.height = height;
// const ctx = canvas.getContext('2d');
// const image = yield imageFetcher(data);
function* getCropData({ x, y, width, height }) {
const {
logo,
renderer: { data },
} = yield select(selectUser);
const canvas = <HTMLCanvasElement>document.getElementById('renderer');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const image = yield imageFetcher(data);
// ctx.drawImage(image, -x, -y);
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);
// }
if (logo && LOGOS[logo][1]) {
const logoImage = yield imageFetcher(LOGOS[logo][1]);
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(selectUser);
// yield call(getCropData, params);
// const canvas = document.getElementById('renderer') as HTMLCanvasElement;
function* cropAShotSaga(params) {
const { title, address } = yield select(selectUser);
yield call(getCropData, params);
const canvas = document.getElementById('renderer') as HTMLCanvasElement;
// downloadCanvas(canvas, (title || address).replace(/\./gi, ' '));
downloadCanvas(canvas, (title || address).replace(/\./gi, ' '));
// return yield put(hideRenderer());
// }
return yield put(hideRenderer());
}
// function* locationChangeSaga({ location }: ReturnType<typeof ActionCreators.locationChanged>) {
// const {
// address,
// ready,
// user: { id, random_url },
// } = yield select(selectUser);
function* locationChangeSaga({ location }: ReturnType<typeof ActionCreators.locationChanged>) {
const {
address,
ready,
user: { id, random_url },
} = yield select(selectUser);
// const { owner }: ReturnType<typeof selectMap> = yield select(selectMap)
const { owner }: ReturnType<typeof selectMap> = yield select(selectMap)
// if (!ready) return;
if (!ready) return;
// const { path, mode } = getUrlData(location);
const { path, mode } = getUrlData(location);
// if (address !== path) {
// const map = yield call(loadMapSaga, path);
if (address !== path) {
const map = yield call(loadMapSaga, path);
// 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' && owner.id !== id) {
// return yield call(replaceAddressIfItsBusy, random_url, address);
// } else {
// yield put(setAddressOrigin(''));
// }
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' && owner.id !== id) {
return yield call(replaceAddressIfItsBusy, random_url, address);
} else {
yield put(setAddressOrigin(''));
}
if (mode !== 'edit') {
yield put(setEditing(false));
// editor.stopEditing();
} else {
yield put(setEditing(true));
// editor.startEditing();
}
}
// if (mode !== 'edit') {
// yield put(setEditing(false));
// // editor.stopEditing();
// } else {
// yield put(setEditing(true));
// // editor.startEditing();
// }
// }
function* gotVkUserSaga({ user: u }: ReturnType<typeof ActionCreators.gotVkUser>) {
const {
@ -346,40 +315,40 @@ function* gotVkUserSaga({ user: u }: ReturnType<typeof ActionCreators.gotVkUser>
yield put(setUser({ ...user, random_url }));
}
function* keyPressedSaga({ key, target }: ReturnType<typeof ActionCreators.keyPressed>): any {
if (target === 'INPUT' || target === 'TEXTAREA') {
return;
}
// function* keyPressedSaga({ key, target }: ReturnType<typeof ActionCreators.keyPressed>): any {
// if (target === 'INPUT' || target === 'TEXTAREA') {
// return;
// }
if (key === 'Escape') {
const {
dialog_active,
mode,
renderer: { renderer_active },
} = yield select(selectUser);
// if (key === 'Escape') {
// const {
// dialog_active,
// mode,
// 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') {
const {
user: { editing },
} = yield select();
// 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') {
// const {
// user: { editing },
// } = yield select();
if (!editing) return;
// if (!editing) return;
const { mode } = yield select(selectUser);
// const { mode } = yield select(selectUser);
if (mode === MODES.TRASH) {
yield put(clearAll());
} else {
yield put(setMode(MODES.TRASH));
}
}
}
// if (mode === MODES.TRASH) {
// yield put(clearAll());
// } else {
// yield put(setMode(MODES.TRASH));
// }
// }
// }
function* searchGetRoutes() {
const { token } = yield select(selectUserUser);
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const {
routes: {
@ -387,7 +356,7 @@ function* searchGetRoutes() {
shift,
filter: { title, distance, tab },
},
} = yield select(selectUser);
}: ReturnType<typeof selectUser> = yield select(selectUser);
const result: Unwrap<typeof getRouteList> = yield getRouteList({
token,
@ -442,14 +411,17 @@ function* searchSetSaga() {
function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDialog>) {
const {
dialog_active,
routes: {
filter: { tab: current },
},
} = yield select(selectUser);
}: ReturnType<typeof selectUser> = yield select(selectUser);
const {
dialog_active,
}: ReturnType<typeof selectEditor> = yield select(selectEditor);
if (dialog_active && tab === current) {
return yield put(setDialogActive(false));
return yield put(editorSetDialogActive(false));
}
if (tab !== current) {
@ -457,8 +429,8 @@ function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDia
yield put(searchSetTab(tab));
}
yield put(setDialog(DIALOGS.MAP_LIST));
yield put(setDialogActive(true));
yield put(editorSetDialog(DIALOGS.MAP_LIST));
yield put(editorSetDialogActive(true));
return tab;
}
@ -476,28 +448,28 @@ function* userLogoutSaga(): SagaIterator {
}
function* setUserSaga() {
const { dialog_active } = yield select(selectUser);
const { dialog_active }: ReturnType<typeof selectEditor> = yield select(selectEditor);
if (dialog_active) yield call(searchSetSagaWorker);
return true;
}
function* getGPXTrackSaga(): SagaIterator {
const { route, stickers }: ReturnType<typeof selectMap> = yield select(selectMap);
const { title, address } = yield select(selectUser);
// function* getGPXTrackSaga(): SagaIterator {
// const { route, stickers }: ReturnType<typeof selectMap> = yield select(selectMap);
// const { title, address } = yield select(selectUser);
if (!route || route.length <= 0) return;
// if (!route || route.length <= 0) return;
const track = getGPXString({ route, stickers, title: title || address });
// const track = getGPXString({ route, stickers, title: title || address });
return downloadGPXTrack({ track, title });
}
// return downloadGPXTrack({ track, title });
// }
function* mapsLoadMoreSaga() {
const {
routes: { limit, list, shift, step, loading, filter },
} = yield select(selectUser);
}: ReturnType<typeof selectUser> = yield select(selectUser);
if (loading || list.length >= limit || limit === 0) return;
@ -540,7 +512,7 @@ function* mapsLoadMoreSaga() {
}
function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>): SagaIterator {
const { token } = yield select(selectUserUser);
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const {
routes: {
list,
@ -549,7 +521,7 @@ function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>
limit,
filter: { min, max },
},
} = yield select(selectUser);
}: ReturnType<typeof selectUser> = yield select(selectUser);
const index = list.findIndex(el => el.address === address);
@ -574,7 +546,7 @@ function* modifyRouteSaga({
title,
is_public,
}: ReturnType<typeof ActionCreators.modifyRoute>): SagaIterator {
const { token } = yield select(selectUserUser);
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const {
routes: {
list,
@ -606,12 +578,12 @@ function* modifyRouteSaga({
function* toggleRouteStarredSaga({
address,
}: ReturnType<typeof ActionCreators.toggleRouteStarred>) {
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const {
routes: { list },
}: IState['user'] = yield select(selectUser);
}: ReturnType<typeof selectUser> = yield select(selectUser);
const route = list.find(el => el.address === address);
const { token } = yield select(selectUserUser);
yield put(setRouteStarred(address, !route.is_published));
const result = yield sendRouteStarred({
@ -624,24 +596,24 @@ function* toggleRouteStarredSaga({
}
export function* userSaga() {
// yield takeEvery(USER_ACTIONS.STOP_EDITING, stopEditingSaga);
// yield takeLatest(USER_ACTIONS.TAKE_A_SHOT, takeAShotSaga);
// yield takeLatest(USER_ACTIONS.CROP_A_SHOT, cropAShotSaga);
// yield takeLatest(USER_ACTIONS.LOCATION_CHANGED, locationChangeSaga);
// yield takeLatest(USER_ACTIONS.KEY_PRESSED, keyPressedSaga);
// yield takeLatest(USER_ACTIONS.GET_GPX_TRACK, getGPXTrackSaga);
yield takeLatest(REHYDRATE, authCheckSaga);
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.ROUTER_CANCEL, routerCancelSaga);
// yield takeEvery(USER_ACTIONS.ROUTER_SUBMIT, routerSubmitSaga);
yield takeLatest(USER_ACTIONS.TAKE_A_SHOT, takeAShotSaga);
yield takeLatest(USER_ACTIONS.CROP_A_SHOT, cropAShotSaga);
// 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);
@ -654,7 +626,6 @@ export function* userSaga() {
yield takeLatest(USER_ACTIONS.SEARCH_SET_TAB, searchSetTabSaga);
yield takeLatest(USER_ACTIONS.SET_USER, setUserSaga);
yield takeLatest(USER_ACTIONS.GET_GPX_TRACK, getGPXTrackSaga);
yield takeLatest(USER_ACTIONS.MAPS_LOAD_MORE, mapsLoadMoreSaga);
yield takeLatest(USER_ACTIONS.DROP_ROUTE, dropRouteSaga);

View file

@ -1,8 +1,4 @@
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;
export const selectUserRenderer = (state: IState) => state.user.renderer;
export const selectUserUser = (state: IState) => state.user.user;

View file

@ -164,4 +164,7 @@ export const toTranslit = (string: string = ''): string =>
export const parseDesc = (text: string = ''): string => text.replace(/(\n{2,})/gi, '\n\n');
// export const colorizeTitle = (text: string): string => text.replace(/(\[[^\]^]+\])/, ``)
export const getEstimated = (distance: number, speed: number = 15): number => {
const time = (distance && (distance / speed)) || 0;
return (time && parseFloat(time.toFixed(1)));
};

View file

@ -23,5 +23,8 @@
"./src/index.tsx",
"./backend/**/*",
"./custom.d.ts"
],
"exclude": [
"./src/_modules/**/*"
]
}