map_list: initial

urls: editing now handled through urls
This commit is contained in:
muerwre 2018-12-05 14:16:23 +07:00
parent 3771e5d338
commit 0d9bad9095
23 changed files with 386 additions and 68 deletions

View file

@ -2,17 +2,17 @@
module.system.node.resolve_dirname=node_modules
module.system.node.resolve_dirname=src
module.name_mapper='^$redux/\([-a-zA-Z0-9$_/]+\)$' -> '<PROJECT_ROOT>/src/redux/\1'
module.name_mapper='^components\/\(.*\)$' ->'<PROJECT_ROOT>/src/components/\1'
module.name_mapper='^containers\/\(.*\)$' ->'<PROJECT_ROOT>/src/containers/\1'
module.name_mapper='^constants\/\(.*\)$' ->'<PROJECT_ROOT>/src/constants/\1'
module.name_mapper='^sprites\/\(.*\)$' ->'<PROJECT_ROOT>/src/sprites/\1'
module.name_mapper='^config\/\(.*\)$' ->'<PROJECT_ROOT>/src/config/\1'
module.name_mapper='^styles\/\(.*\)$' ->'<PROJECT_ROOT>/src/styles/\1'
module.name_mapper='^utils\/\(.*\)$' ->'<PROJECT_ROOT>/src/utils/\1'
module.name_mapper='^$config/\([-a-zA-Z0-9$_/]+\)$' -> '<PROJECT_ROOT>/config/\1'
module.name_mapper='^$components\/\(.*\)$' ->'<PROJECT_ROOT>/src/components/\1'
module.name_mapper='^$containers\/\(.*\)$' ->'<PROJECT_ROOT>/src/containers/\1'
module.name_mapper='^$constants\/\(.*\)$' ->'<PROJECT_ROOT>/src/constants/\1'
module.name_mapper='^$sprites\/\(.*\)$' ->'<PROJECT_ROOT>/src/sprites/\1'
module.name_mapper='^$config\/\(.*\)$' ->'<PROJECT_ROOT>/src/config/\1'
module.name_mapper='^$styles\/\(.*\)$' ->'<PROJECT_ROOT>/src/styles/\1'
module.name_mapper='^$utils\/\(.*\)$' ->'<PROJECT_ROOT>/src/utils/\1'
[ignore]
.*/node_modules
.*/node_modules/styled-components/.*
[include]
public

View file

@ -13,6 +13,7 @@ const RouteSchema = new Schema(
owner: { type: Schema.Types.ObjectId, ref: 'User' },
logo: { type: String, default: 'DEFAULT' },
distance: { type: Number, default: 0 },
public: { type: Boolean, default: true },
},
{
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }

View file

@ -1,3 +1,4 @@
const { generateRandomUrl } = require('../auth/guest');
const { Route } = require('../../models');
module.exports = async (req, res) => {
@ -9,6 +10,7 @@ module.exports = async (req, res) => {
if (!exists) return res.send({ success: false, mode: 'not_found_2' });
const data = exists.toObject();
const random_url = await generateRandomUrl();
return res.send({
success: true,
@ -17,7 +19,8 @@ module.exports = async (req, res) => {
owner: {
...data.owner,
id: data.owner._id,
}
},
random_url,
});
};

View file

@ -0,0 +1,35 @@
// @flow
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { RouteRow } from '$components/maps/RouteRow';
import type { Route } from '$constants/types';
type Props = {
routes: { [id: String]: Route },
editing: Boolean,
};
const Component = ({ routes, editing }: Props) => (
<div className="dialog-maplist">
{
Object.keys(routes).map(id => (
<RouteRow
editing={editing}
{...routes[id]}
key={id}
/>
))
}
</div>
);
const mapStateToProps = ({ user: { editing, user: { routes } } }) => ({
routes, editing,
});
const mapDispatchToProps = dispatch => bindActionCreators({
}, dispatch);
export const MapListDialog = connect(mapStateToProps, mapDispatchToProps)(Component);

View file

@ -0,0 +1,38 @@
// @flow
import React from 'react';
import { Icon } from '$components/panels/Icon';
import { pushPath } from '$utils/history';
type Props = {
title: String,
distance: Number,
created_at: String,
_id: String,
editing: Boolean,
};
export const RouteRow = ({
title, distance, created_at, _id, editing
}: Props) => (
<div
className="route-row"
onClick={() => pushPath(`/${_id}/${editing ? 'edit' : ''}`)}
>
<div className="route-title">
{title || _id}
</div>
<div className="route-description">
<span>
<Icon icon="icon-link-1" />
{_id}
</span>
</div>
<div className="route-description">
<span>
<Icon icon="icon-cycle-1" />
{(distance && `${distance} km`) || 'N/A'}
</span>
</div>
</div>
);

View file

@ -4,7 +4,7 @@ import { GuestButton } from '$components/user/GuestButton';
import { DEFAULT_USER, ROLES } from '$constants/auth';
import { UserButton } from '$components/user/UserButton';
import { UserMenu } from '$components/user/UserMenu';
import { setUser, userLogout, takeAShot } from '$redux/user/actions';
import { setUser, userLogout, takeAShot, setDialog } from '$redux/user/actions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import type { UserType } from '$constants/types';
@ -12,12 +12,14 @@ import { Icon } from '$components/panels/Icon';
import classnames from 'classnames';
import { CLIENT } from '$config/frontend';
import { DIALOGS } from '$constants/dialogs';
type Props = {
user: UserType,
userLogout: Function,
setUser: Function,
takeAShot: Function,
setDialog: Function,
dialog: String,
};
export class Component extends React.PureComponent<Props, void> {
@ -54,6 +56,9 @@ export class Component extends React.PureComponent<Props, void> {
}
setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened });
openMapsDialog = () => {
this.props.setDialog({ dialog: DIALOGS.MAP_LIST });
};
openOauthFrame = () => {
const width = parseInt(window.innerWidth, 10);
@ -78,7 +83,7 @@ export class Component extends React.PureComponent<Props, void> {
return (
<div>
<div className="panel active">
<div className="panel active panel-user">
<div className="user-panel">
{
!user || user.role === ROLES.guest
@ -96,7 +101,7 @@ export class Component extends React.PureComponent<Props, void> {
<div className="control-bar">
<button
className={classnames({ disabled: route_count <= 0 })}
// onClick={this.props.takeAShot}
onClick={this.openMapsDialog}
>
<Icon icon="icon-folder-1" />
</button>
@ -108,7 +113,12 @@ export class Component extends React.PureComponent<Props, void> {
}
const mapStateToProps = ({ user: { user } }) => ({ user });
const mapDispatchToProps = dispatch => bindActionCreators({ setUser, userLogout, takeAShot }, dispatch);
const mapStateToProps = ({ user: { dialog, user } }) => ({ dialog, user });
const mapDispatchToProps = dispatch => bindActionCreators({
setUser,
userLogout,
takeAShot,
setDialog,
}, dispatch);
export const UserPanel = connect(mapStateToProps, mapDispatchToProps)(Component);

5
src/constants/dialogs.js Normal file
View file

@ -0,0 +1,5 @@
// @flow
export const DIALOGS = ({
NONE: 'NONE',
MAP_LIST: 'MAP_LIST',
}: { [key: String]: String });

View file

@ -1,4 +1,6 @@
export const MODES = {
// @flow
export const MODES = ({
POLY: 'POLY',
STICKERS: 'STICKERS',
ROUTER: 'ROUTER',
@ -9,4 +11,4 @@ export const MODES = {
SAVE: 'SAVE',
CONFIRM_CANCEL: 'CONFIRM_CANCEL',
PROVIDER: 'PROVIDER',
};
}: { [key: String]: String });

View file

@ -14,3 +14,18 @@ export type UserType = {
photo: String,
}
};
type Path = Array<{ lat: Number, lng: Number }>;
type Stickers = Array<Object>;
export type Route = {
_id: String,
title: String,
version: Number,
stickers: Array<Stickers>,
route: Array<Path>,
logo: String,
distance: Number,
created_at: String,
updated_at: String,
}

View file

@ -12,11 +12,15 @@ import { hot } from 'react-hot-loader';
import { Renderer } from '$components/renderer/Renderer';
import { hideRenderer } from '$redux/user/actions';
import { Cursor } from '$components/Cursor';
import { LeftDialog } from '$containers/LeftDialog';
import { DIALOGS } from '$constants/dialogs';
type Props = {
renderer_active: Boolean,
hideRenderer: Function,
mode: String,
dialog: String,
dialog_active: Boolean,
}
const Component = (props: Props) => (
@ -26,15 +30,20 @@ const Component = (props: Props) => (
<UserPanel />
<EditorPanel />
<Cursor mode={props.mode} />
<LeftDialog dialog={props.dialog} dialog_active={props.dialog_active} />
{ props.renderer_active && <Renderer onClick={props.hideRenderer} /> }
{ props.renderer_active &&
<Renderer onClick={props.hideRenderer} />
}
</div>
);
const mapStateToProps = ({ user }) => ({
renderer_active: user.renderer.renderer_active,
mode: user.mode,
const mapStateToProps = ({ user: { mode, dialog, dialog_active, renderer } }) => ({
renderer_active: renderer.renderer_active,
mode,
dialog,
dialog_active,
});
const mapDispatchToProps = dispatch => bindActionCreators({ hideRenderer }, dispatch);
export const App = connect(mapStateToProps, mapDispatchToProps)(hot(module)(Component));

View file

@ -0,0 +1,16 @@
// @flow
import React from 'react';
import { DIALOGS } from '$constants/dialogs';
import { MapListDialog } from '$components/dialogs/MapListDialog';
import classnames from 'classnames';
type Props = {
dialog: String,
dialog_active: Boolean,
}
export const LeftDialog = ({ dialog, dialog_active }: Props) => (
<div className={classnames('dialog', { active: dialog_active })}>
{ dialog === DIALOGS.MAP_LIST && <MapListDialog /> }
</div>
);

View file

@ -218,7 +218,6 @@ export class Editor {
this.stickers.clearAll();
};
setData = ({
route, stickers, version = 1, owner, title, address
}) => {
@ -272,19 +271,21 @@ export class Editor {
this.setInitialData();
const url = (this.owner && this.owner.id === id) ? path : random_url;
// const url = (this.owner && this.owner.id === id) ? path : random_url;
this.owner = { id };
pushPath(`/${url}/edit`);
// console.log('PATH IS', path);
// pushPath(`/${url}/edit`);
if (this.poly.latlngs && this.poly.latlngs.length > 1) this.poly.poly.enableEdit();
this.stickers.startEditing();
};
stopEditing = () => {
const { path } = getUrlData();
// const { path } = getUrlData();
pushPath(`/${(this.initialData && this.initialData.path) || path}`);
// console.log('PATH IS', path, this.initialData.path);
// pushPath(`/${(this.initialData && this.initialData.path) || path}`);
this.poly.poly.disableEdit();
this.stickers.stopEditing();

View file

@ -6,6 +6,8 @@ import createSagaMiddleware from 'redux-saga';
import { userReducer } from '$redux/user/reducer';
import { userSaga } from '$redux/user/sagas';
import { createBrowserHistory } from 'history';
import { locationChanged } from '$redux/user/actions';
const userPersistConfig = {
key: 'user',
@ -42,3 +44,8 @@ export function configureStore() {
return { store, persistor };
}
export const history = createBrowserHistory();
export const historyListener = history.listen(location => {
store.dispatch(locationChanged(location.pathname));
});

View file

@ -39,3 +39,7 @@ export const takeAShot = () => ({ type: ACTIONS.TAKE_A_SHOT });
export const cropAShot = payload => ({ type: ACTIONS.CROP_A_SHOT, ...payload });
export const setProvider = provider => ({ type: ACTIONS.SET_PROVIDER, provider });
export const setDialog = ({ dialog, dialog_active }) => ({ type: ACTIONS.SET_DIALOG, dialog });
export const locationChanged = location => ({ type: ACTIONS.LOCATION_CHANGED, location });
export const setReady = ready => ({ type: ACTIONS.SET_READY, ready });

View file

@ -1,4 +1,6 @@
export const ACTIONS = {
// @flow
export const ACTIONS = ({
SET_USER: 'SET_USER',
USER_LOGOUT: 'USER_LOGOUT',
@ -38,4 +40,8 @@ export const ACTIONS = {
CROP_A_SHOT: 'CROP_A_SHOT',
SET_PROVIDER: 'SET_PROVIDER',
};
SET_DIALOG: 'SET_DIALOG',
LOCATION_CHANGED: 'LOCATION_CHANGED',
SET_READY: 'SET_READY',
}: { [key: String]: String });

View file

@ -1,3 +1,4 @@
// @flow
import { createReducer } from 'reduxsauce';
import { ACTIONS } from '$redux/user/constants';
import { DEFAULT_USER } from '$constants/auth';
@ -5,6 +6,7 @@ import { MODES } from '$constants/modes';
import { DEFAULT_LOGO } from '$constants/logos';
import { TIPS } from '$constants/tips';
import { DEFAULT_PROVIDER } from '$constants/providers';
import { DIALOGS } from '$constants/dialogs';
const getEstimated = distance => {
const time = (distance && (distance / 15)) || 0;
@ -73,7 +75,18 @@ const setRenderer = (state, { payload }) => ({
const setProvider = (state, { provider }) => ({ ...state, provider });
const HANDLERS = {
const setDialog = (state, { dialog, dialog_active }) => ({
...state,
dialog: dialog || state.dialog,
dialog_active: typeof dialog_active !== 'undefined' ? dialog_active : !state.dialog_active,
});
const setReady = (state, { ready = true }) => ({
...state,
ready,
});
const HANDLERS = ({
[ACTIONS.SET_USER]: setUser,
[ACTIONS.SET_EDITING]: setEditing,
[ACTIONS.SET_CHANGED]: setChanged,
@ -96,9 +109,13 @@ const HANDLERS = {
[ACTIONS.SET_RENDERER]: setRenderer,
[ACTIONS.SET_PROVIDER]: setProvider,
};
[ACTIONS.SET_DIALOG]: setDialog,
[ACTIONS.SET_READY]: setReady,
}: { [key: String]: Function });
export const INITIAL_STATE = {
ready: false,
user: { ...DEFAULT_USER },
editing: false,
mode: MODES.NONE,
@ -117,6 +134,9 @@ export const INITIAL_STATE = {
save_overwriting: false,
save_processing: false,
dialog: DIALOGS.NONE,
dialog_active: false,
renderer: {
data: '',
width: 0,

View file

@ -7,7 +7,7 @@ import {
setActiveSticker, setAddress,
setChanged,
setEditing,
setMode, setRenderer,
setMode, setReady, setRenderer,
setSaveError,
setSaveOverwrite, setSaveSuccess, setTitle,
setUser
@ -62,12 +62,15 @@ function* startEmptyEditorSaga() {
}
function* startEditingSaga() {
yield editor.startEditing();
yield put(setEditing(true));
// yield put(setEditing(true));
// yield editor.startEditing();
const { path } = getUrlData();
yield pushPath(`/${path}/edit`);
}
function* stopEditingSaga() {
const { changed, editing, mode } = yield select(getState);
const { path } = getUrlData();
if (!editing) return;
if (changed && mode !== MODES.CONFIRM_CANCEL) {
@ -76,40 +79,59 @@ function* stopEditingSaga() {
}
yield editor.cancelEditing();
yield put(setMode(MODES.NONE));
yield put(setChanged(false));
yield put(setEditing(editor.hasEmptyHistory)); // don't close editor if no previous map
yield pushPath(`/${path}/`);
}
function* loadMapSaga(path) {
const map = yield call(getStoredMap, { name: path });
if (map) {
yield editor.setData(map);
yield editor.fitDrawing();
yield put(setChanged(false));
}
return map;
}
function* mapInitSaga() {
const { path, mode } = getUrlData();
if (path) {
const map = yield call(getStoredMap, { name: path });
const map = yield call(loadMapSaga, path);
// const map = yield call(getStoredMap, { name: path });
if (map) {
yield editor.setData(map);
yield editor.fitDrawing();
yield put(setChanged(false));
// yield editor.setData(map);
// yield editor.fitDrawing();
// yield put(setChanged(false));
if (mode && mode === 'edit') {
yield call(startEditingSaga);
yield put(setEditing(true));
editor.startEditing();
// yield call(startEditingSaga); // <-- this is working
// yield put(setEditing(true));
// editor.startEditing();
} else {
yield put(setEditing(false));
// yield put(setEditing(false)); // <-- this is working
// yield call(stopEditingSaga);
yield put(setEditing(false));
editor.stopEditing();
}
return hideLoader();
yield put(setReady(true));
hideLoader();
return true;
}
}
return yield call(startEmptyEditorSaga);
yield call(startEmptyEditorSaga);
yield put(setReady(true));
return true;
}
function* authChechSaga() {
@ -120,7 +142,6 @@ function* authChechSaga() {
if (user) {
yield put(setUser(user));
return yield call(mapInitSaga);
}
}
@ -294,9 +315,37 @@ function setProviderSaga({ provider }) {
return put(setMode(MODES.NONE));
}
export function* userSaga() {
// ASYNCHRONOUS!!! :-)
function* locationChangeSaga({ location }) {
const { address, editing, ready, user: { id, random_url } } = yield select(getState);
if (!ready) return;
const { path, mode } = getUrlData(location);
if (address !== path) {
const map = yield call(loadMapSaga, path);
if (map && map.owner && mode === 'edit' && map.owner.id !== id) {
pushPath(`/${map.random_url}/edit`);
return;
}
} else if (mode === 'edit' && editor.owner.id !== id) {
pushPath(`/${random_url}/edit`);
return;
}
if (editing !== (mode === 'edit')) {
if (mode === 'edit') {
yield put(setEditing(true));
editor.startEditing();
} else {
yield put(setEditing(false));
editor.stopEditing();
}
}
}
export function* userSaga() {
yield takeLatest(REHYDRATE, authChechSaga);
yield takeEvery(ACTIONS.SET_MODE, setModeSaga);
@ -322,4 +371,5 @@ export function* userSaga() {
yield takeLatest(ACTIONS.CROP_A_SHOT, cropAShotSaga);
yield takeEvery(ACTIONS.SET_PROVIDER, setProviderSaga);
yield takeLatest(ACTIONS.LOCATION_CHANGED, locationChangeSaga);
}

View file

@ -118,6 +118,22 @@
</g>
</g>
<g id="icon-cycle-2">
<path fill="black" d="M0 0h32v32H0z"/>
<g id="g5604" fill="#fff" transform="matrix(-.36615 0 0 .36615 24.543 7.457)">
<g id="g5527">
<g id="g5495">
<path id="path5483" d="M37.687 24.66c-4.945 0-8.973 4.025-8.973 8.975 0 4.95 4.026 8.974 8.973 8.974 4.95 0 8.977-4.024 8.977-8.974s-4.027-8.975-8.977-8.975zm0 15.384a6.417 6.417 0 0 1-6.409-6.409 6.418 6.418 0 0 1 6.409-6.41 6.417 6.417 0 0 1 6.411 6.41 6.417 6.417 0 0 1-6.411 6.409z"/>
<path id="path5485" d="M23.588 15.501v.01h8.047c-2.375-2.4-5.273-5.251-5.99-5.578-1.257-.575-3.966 1.462-4.547 1.748-.532.283-.954.699-1.043 1.236l-2.112 5.927-6.167.069c-1.811.485-.465 2.065-.465 2.065l3.097.041-.295 1.405-1.803 2.989a8.914 8.914 0 0 0-3.333-.648C4.027 24.765 0 28.792 0 33.741c0 4.947 4.026 8.974 8.977 8.974 4.948 0 8.975-4.026 8.975-8.974a8.956 8.956 0 0 0-3.423-7.038l2.005-3.326.487-2.322 2.41.03c.479-.136.833-.538.912-1.029l2.603-4.074zM15.386 33.74a6.417 6.417 0 0 1-6.409 6.409c-3.534 0-6.411-2.876-6.411-6.409a6.419 6.419 0 0 1 6.411-6.411c.694 0 1.36.114 1.986.32l-3.579 5.939 2.197 1.323 3.607-5.989a6.386 6.386 0 0 1 2.198 4.818z"/>
<path id="path5487" d="M32.24 23.139s2.468-2.578 2.691-2.968c.225-.392.229-.872.007-1.265 0 0-.725-.76-1.771-1.832v.014h-8.949l4.281 3.716-6.367 9.947a1.264 1.264 0 0 0-.104 1.045l2.563 7.692 3.072-.17-1.741-7.787z"/>
<circle id="circle5489" cx="19.703" cy="7.384" r="3.435"/>
<path id="path5491" d="M32.845 5.919c-1.06-.744-2.366-.789-3.123-.034l-1.484 1.487c-.756.756-.711 2.062.03 3.124z"/>
<path id="path5493" d="M33.007 15.317c1.116 1.116 2.73 1.311 3.607.436l1.485-1.488c.877-.876.685-2.491-.434-3.606l-4.081-4.081-4.659 4.658z"/>
</g>
</g>
</g>
</g>
<g id="icon-logo">
<path fill="black" stroke="none" d="M0 0h32v32H0z"/>
<g transform="matrix(1 0 0 1 2 -1)">
@ -289,28 +305,34 @@
</g>
<g id="icon-poly-4" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black"/>
<path stroke="none" fill="black"/>
<path xmlns="http://www.w3.org/2000/svg" d="M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2zm0 0c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z" fill="white" stroke="white" stroke-width="1" transform="translate(4 4)"/>
</g>
<g id="icon-get-1" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black"/>
<path stroke="none" fill="black"/>
<path xmlns="http://www.w3.org/2000/svg" d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" fill="white" stroke="white" stroke-width="1" transform="translate(4 4)"/>
</g>
<g id="icon-folder-1" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black"/>
<path stroke="none" fill="black"/>
<path xmlns="http://www.w3.org/2000/svg" d="M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" fill="white" stroke="white" stroke-width="0" transform="translate(4 4)"/>
</g>
<g id="icon-folder-1" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black"/>
<path stroke="none" fill="black"/>
<path xmlns="http://www.w3.org/2000/svg" d="M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" fill="white" stroke="white" stroke-width="0" transform="translate(4 4)"/>
</g>
<g id="icon-cycle-1" stroke="none">
<path stroke="none" fill="black"/>
<path xmlns="http://www.w3.org/2000/svg" d="M15.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zM5 12c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 8.5c-1.9 0-3.5-1.6-3.5-3.5s1.6-3.5 3.5-3.5 3.5 1.6 3.5 3.5-1.6 3.5-3.5 3.5zm5.8-10l2.4-2.4.8.8c1.3 1.3 3 2.1 5.1 2.1V9c-1.5 0-2.7-.6-3.6-1.5l-1.9-1.9c-.5-.4-1-.6-1.6-.6s-1.1.2-1.4.6L7.8 8.4c-.4.4-.6.9-.6 1.4 0 .6.2 1.1.6 1.4L11 14v5h2v-6.2l-2.2-2.3zM19 12c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 8.5c-1.9 0-3.5-1.6-3.5-3.5s1.6-3.5 3.5-3.5 3.5 1.6 3.5 3.5-1.6 3.5-3.5 3.5z" fill="white" stroke="white" stroke-width="0" transform="translate(4 4)"/>
</g>
<g id="icon-link-1" stroke="none">
<path stroke="none" fill="black"/>
<path xmlns="http://www.w3.org/2000/svg" d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z" fill="white" stroke="white" stroke-width="0" transform="translate(4 4)"/>
</g>
</svg>
</defs>

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

View file

@ -3,7 +3,6 @@
@router_line: #4597d0;
@bar_shadow: rgba(0,0,0,0.3) 0 2px 0, inset rgba(255, 255, 255, 0.05) 1px 1px;
@bar_background: #333333;
@dialog_background: #222222;
@ -21,3 +20,6 @@
@color_primary: #4597d0;
@color_success: #7cd766;
@color_danger: #ff3344;
@bar_shadow: rgba(0,0,0,0.3) 0 2px 0, inset rgba(255, 255, 255, 0.05) 1px 1px;
@dialog_shadow: rgba(0,0,0,0.3) 2px 0 0;

73
src/styles/dialogs.less Normal file
View file

@ -0,0 +1,73 @@
.dialog {
background: #222222;
//background: linear-gradient(130deg, #320523, #020d2b);
position: fixed;
left: 0;
top: 0;
width: 360px;
height: 100%;
z-index: 3;
box-shadow: @dialog_shadow;
padding: 10px 10px 72px 10px;
overflow: hidden;
box-sizing: border-box;
transform: translate3d(-100%, 0, 0);
pointer-events: none;
transition: transform 500ms;
&.active {
transform: translate3d(0, 0, 0);
pointer-events: all;
}
&::after {
content: ' ';
height: 100px;
width: 100%;
left: 0;
bottom: 0;
//background: linear-gradient(0deg, rgba(2, 13, 43, 1) 50%, rgba(2,13,43,0));
background: linear-gradient(0deg, rgba(34, 34, 34, 1) 50%, rgba(34, 34, 34, 0));
position: absolute;
z-index: 5;
}
}
.route-row {
margin-bottom: 10px;
background: rgba(255, 255, 255, 0.05);
padding: 10px 10px 5px 10px;
color: white;
user-select: none;
cursor: pointer;
transition: background-color 250ms;
&:hover {
background: rgba(255, 255, 255, 0.1);
}
}
.route-title {
margin-bottom: 5px;
font-weight: bold;
}
.route-description {
font-size: 0.9em;
opacity: 0.3;
margin-bottom: 5px;
svg {
width: 20px;
height: 20px;
fill: white;
vertical-align: text-bottom;
margin-bottom: -2px;
}
span {
padding-right: 10px;
}
}

View file

@ -9,6 +9,7 @@
@import 'user-button.less';
@import 'save.less';
@import 'renderer.less';
@import 'dialogs.less';
body {
font-family: 'Rubik', sans-serif;
@ -54,8 +55,8 @@ body {
.cursor-tooltip {
position: fixed;
top: 2px;
left: 2px;
top: 4px;
left: 4px;
width: 20px;
height: 20px;
z-index: 10;

View file

@ -40,7 +40,7 @@
position: fixed;
left: 10px;
bottom: 10px;
z-index: 3;
z-index: 6;
color: white;
display: flex;
align-items: center;

View file

@ -1,12 +1,10 @@
export const getPath = () => (window.location && window.location.pathname &&
window.location.pathname.replace(/^\//, ''));
import { history } from '$redux/store';
export const pushPath = url => window.history.pushState(url, 'Редактирование маршрута', url);
export const getPath = () => (window.location && window.location.pathname);
export const pushPath = url => history.push(url);
export const getUrlData = () => {
const url = getPath();
const [path, mode] = url.split('/');
export const getUrlData = (url = getPath()) => {
const [, path, mode] = url.split('/');
const { host } = window.location;
return { path, mode, host };