styled panels

This commit is contained in:
muerwre 2018-08-22 15:20:39 +07:00
parent 7229a48297
commit d17a7b6aef
24 changed files with 470 additions and 421 deletions

10
package-lock.json generated
View file

@ -11923,6 +11923,11 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
"typeface-montserrat": {
"version": "0.0.54",
"resolved": "https://registry.npmjs.org/typeface-montserrat/-/typeface-montserrat-0.0.54.tgz",
"integrity": "sha512-Typhap0PWT299+Va0G/8ZtycHMXrH4gBWKfiW977KEBx5rXUUCa70gvqLx1fdA0WAo6bhSAQmo8uc+QFAmjPww=="
},
"ua-parser-js": {
"version": "0.7.18",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz",
@ -12993,6 +12998,11 @@
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
"dev": true
},
"wfk-montserrat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wfk-montserrat/-/wfk-montserrat-1.0.0.tgz",
"integrity": "sha512-XgRPn20Qp20BHs1UXyvMVQgblHSwl3bMhrNeXF921fqxIOT0ZPMynqWa60q5wU5RkRTxQfZsgv0Rt6prMSpvhQ=="
},
"whatwg-fetch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",

View file

@ -63,7 +63,9 @@
"scrypt": "^6.0.3",
"styled-components": "^3.2.6",
"styled-theming": "^2.2.0",
"webpack-git-hash": "^1.0.2"
"typeface-montserrat": "0.0.54",
"webpack-git-hash": "^1.0.2",
"wfk-montserrat": "^1.0.0"
},
"flow-coverage-report": {
"includeGlob": [

View file

@ -11,7 +11,7 @@ export const Fills = () => (
<defs>
<linearGradient id="activePathGradient" x1="-20%" x2="50%" y1="0%" y2="140%">
<stop offset="0%" stopColor="#ff9900" />
<stop offset="0%" stopColor="#ff7700" />
<stop offset="100%" stopColor="#ff3344" />
</linearGradient>
</defs>

View file

@ -0,0 +1,14 @@
import React from 'react';
import { MODES } from '$constants/modes';
import { RouterHelper } from '$components/router/RouterHelper';
export const EditorDialog = ({ mode, routerPoints }) => {
const showDialog = (mode === MODES.ROUTER);
return (
showDialog &&
<div id="control-dialog">
{ mode === MODES.ROUTER && <RouterHelper routerPoints={routerPoints} /> }
</div>
);
};

View file

@ -2,7 +2,8 @@ import React from 'react';
import { MODES } from '$constants/modes';
import classnames from 'classnames';
import { Icon } from '$components/Icon';
import { Icon } from '$components/panels/Icon';
import { EditorDialog } from '$components/panels/EditorDialog';
export class EditorPanel extends React.PureComponent {
startPolyMode = () => this.props.editor.changeMode(MODES.POLY);
@ -14,10 +15,28 @@ export class EditorPanel extends React.PureComponent {
startShotterMode = () => this.props.editor.changeMode(MODES.SHOTTER);
render() {
const { mode } = this.props;
const { mode, routerPoints } = this.props;
return (
<div id="control-screen">
<div>
<EditorDialog
mode={mode}
routerPoints={routerPoints}
/>
<div className="panel">
<div className="control-bar">
<button
onClick={this.startShotterMode}
>
<span>РЕДАКТОР</span>
</button>
</div>
</div>
<div className="panel right">
<div className="control-bar">
<button
className={classnames({ active: mode === MODES.ROUTER })}
onClick={this.startRouterMode}
@ -37,12 +56,28 @@ export class EditorPanel extends React.PureComponent {
<Icon icon="icon-sticker" />
</button>
</div>
<div className="control-sep" />
<div className="control-bar">
<button
className={classnames({ active: mode === MODES.SHOTTER })}
onClick={this.startShotterMode}
>
<Icon icon="icon-shooter" />
</button>
<button
className={classnames('highlighted', { active: mode === MODES.SHOTTER })}
onClick={this.startShotterMode}
>
<span>СХОРОНИТЬ</span>
<Icon icon="icon-shooter" />
</button>
</div>
</div>
</div>
);
}

View file

@ -2,7 +2,7 @@ import React from 'react';
import sprite from '$sprites/icon.svg';
export const Icon = ({ icon, size = 32 }) => (
<svg width={size} height={size} viewBox="0 0 32 32" preserveAspectRatio="midXmidY meet">
<svg width={size} height={size} viewBox="0 0 32 32">
<use xlinkHref={`${sprite}#${icon}`} x={0} y={0} />
</svg>
);

View file

@ -0,0 +1,54 @@
import React from 'react';
const noPoints = () => (
<div className="router-helper">
<div className="router-helper__text">
<div className="big white">Укажите на карте первую точку маршрута</div>
<div className="small gray">Путь прокладывается по улицам, тротуарам и тропинкам</div>
</div>
<div className="router-helper__buttons">
<div className="button router-helper__button">
Отмена
</div>
</div>
</div>
);
const firstPoint = () => (
<div className="router-helper">
<div className="router-helper__text">
<div className="big white">Укажите на карте конечную точку маршрута</div>
<div className="small gray"> Вы сможете добавить уточняющие точки</div>
</div>
<div className="router-helper__buttons">
<div className="button router-helper__button">
Отмена
</div>
</div>
</div>
);
const draggablePoints = () => (
<div className="router-helper">
<div className="router-helper__text">
<div className="big white">Продолжите маршрут, щелкая по карте</div>
<div className="small gray">Потяните линию, чтобы указать промежуточные точки</div>
</div>
<div className="router-helper__buttons button-group">
<div className="button button_red router-helper__button">
Отмена
</div>
<div className="button primary router-helper__button">
Применить
</div>
</div>
</div>
);
export const RouterHelper = ({ routerPoints }) => (
<div>
{ !routerPoints && noPoints() }
{ routerPoints === 1 && firstPoint() }
{ routerPoints >= 2 && draggablePoints() }
</div>
);

View file

@ -7,37 +7,38 @@ import { Fills } from '$components/Fills';
export class App extends React.Component {
state = {
mode: 'none',
editor: null,
routerPoints: 0,
};
componentDidMount() {
const container = 'map';
const { mode } = this.state;
const editor = new Editor({
container,
mode,
setMode: this.setMode,
});
this.setState({ editor })
}
setMode = mode => {
this.setState({ mode });
};
setRouterPoints = routerPoints => {
this.setState({ routerPoints });
};
editor = new Editor({
container: 'map',
mode: this.state.mode,
setMode: this.setMode,
setRouterPoints: this.setRouterPoints,
});
render() {
const {
state: { mode, editor },
editor,
state: { mode, routerPoints },
} = this;
return (
<div>
<Fills />
<div id="map" />
<EditorPanel editor={editor} mode={mode} />
<EditorPanel
editor={editor}
mode={mode}
routerPoints={routerPoints}
/>
</div>
);
}

View file

@ -11,5 +11,7 @@
<meta content="/misc/vk_preview.png">
</head>
<body>
<section id="index"></section>
<div id="map" />
</body>

View file

@ -3,8 +3,7 @@ import ReactDOM from 'react-dom';
import { App } from '$containers/App';
import '$styles/map.less';
import '$styles/controls.less';
import '$styles/main.less';
export const Index = () => (
<App />

View file

@ -9,7 +9,8 @@ export class Editor {
constructor({
container,
mode,
setMode
setMode,
setRouterPoints,
}) {
this.map = new Map({ container });
@ -17,7 +18,7 @@ export class Editor {
this.poly = new Poly({ map, routerMoveStart, lockMapClicks });
this.stickers = new Stickers({ map, lockMapClicks });
this.router = new Router({ map, lockMapClicks });
this.router = new Router({ map, lockMapClicks, setRouterPoints });
this.shotter = new Shotter({ map });
this.setMode = setMode;
@ -91,7 +92,7 @@ export class Editor {
if (!latlngs || !latlngs.length) return;
this.router.startFrom(latlngs.pop());
this.router.startFrom(latlngs[latlngs.length - 1]);
};
routerMoveStart = () => {

View file

@ -8,7 +8,7 @@ export class Map {
constructor({ container }) {
this.map = map(container, { editable: true }).setView([55.0153275, 82.9071235], 13);
this.tileLayer = tileLayer(providers.default, {
this.tileLayer = tileLayer(providers.dgis, {
attribution: 'Независимое Велосообщество',
maxNativeZoom: 18,
maxZoom: 18,

View file

@ -1,6 +1,7 @@
import { polyline } from "leaflet";
const polyStyle = { color: 'url(#activePathGradient)', weight: '5' };
const polyStyle = { color: 'url(#activePathGradient)', weight: '6' };
// const polyStyle = { color: '#ff3344', weight: '5' };
export class Poly {
constructor({ map, routerMoveStart, lockMapClicks }) {

View file

@ -4,11 +4,13 @@ import { CONFIG } from '$config';
import { DomMarker } from '$utils/DomMarker';
export class Router {
constructor({ map, lockMapClicks }) {
constructor({ map, lockMapClicks, setRouterPoints }) {
const routeLine = r => Routing.line(r, {
styles: [
{ color: 'white', opacity: 0.8, weight: 6 },
{ color: '#4597d0', opacity: 1, weight: 4, dashArray: '15,10' }
{
color: '#4597d0', opacity: 1, weight: 4, dashArray: '15,10'
}
],
addWaypoints: true,
}).on('linetouched', this.lockPropagations);
@ -30,14 +32,13 @@ export class Router {
routeWhileDragging: true,
}),
routeWhileDragging: true
});
// .on('waypointschanged', this.updateWaypointsByEvent);
}).on('waypointschanged', this.updateWaypointsCount);
this.router.addTo(map);
this.waypoints = [];
this.lockMapClicks = lockMapClicks;
this.setRouterPoints = setRouterPoints;
// this.router._line.on('mousedown', console.log);
}
//
@ -46,22 +47,7 @@ export class Router {
console.log('push', waypoints);
this.router.setWaypoints([...waypoints, { lat, lng }]);
};
//
// pushWaypoint = latlng => {
// this.waypoints.push(latlng);
// this.updateWaypoints();
// };
//
// updateWaypointsByEvent = (e) => {
// console.log('upd', e);
// // this.waypoints = waypoints.map(({ latlng }) => latlng);
//
// };
//
// updateWaypoints = () => {
// this.router.setWaypoints(this.waypoints);
// };
//
createWaypointMarker = () => {
const element = document.createElement('div');
@ -119,4 +105,9 @@ export class Router {
this.router.setWaypoints(waypoints);
};
updateWaypointsCount = () => {
const waypoints = this.router.getWaypoints().filter(({ latLng }) => !!latLng);
this.setRouterPoints(waypoints.length);
}
}

View file

@ -34,5 +34,6 @@
<g transform="translate(18 6)">
<path d="m0 0l-4.391.054c-1.418.531-2.34 1.756-3.176 3.102h-5.178c-.68.317-1.351.655-1.455 2.584v11.49c.17 1.001.58 1.765 1.455 2.06h22.537c.746-.044 1.288-.426 1.68-1.06v-13.517c-.185-1.643-.916-1.65-1.68-1.557h-6.62c-.326-1.26-1.91-2.247-3.172-3.156zm-2.122 5.289c3.227 0 5.87 2.626 5.87 5.846s-2.643 5.845-5.87 5.845c-3.227 0-5.869-2.626-5.869-5.845 0-3.22 2.642-5.846 5.87-5.846zm0 1.998a3.844 3.844 0 0 0-3.869 3.848 3.842 3.842 0 0 0 3.87 3.845 3.84 3.84 0 0 0 3.866-3.845 3.842 3.842 0 0 0-3.867-3.848z" />
</g>
</g>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before After
Before After

35
src/styles/button.less Normal file
View file

@ -0,0 +1,35 @@
.button {
background: #444444;
padding: 4px 16px;
height: 18px;
line-height: 1em;
border-radius: 2px;
font-family: sans-serif;
font-size: 14px;
display: inline-flex;
align-items: center;
cursor: pointer;
user-select: none;
&.primary {
background: #3c78db;
}
&.danger {
background: #ed2f3b;
}
}
.button-group {
.button {
border-radius: 0;
&:first-child {
border-radius: 4px 0 0 4px;
}
&:last-child {
border-radius: 0 4px 4px 0;
}
}
}

View file

@ -1,35 +0,0 @@
#control-screen {
position: fixed;
right: 10px;
bottom: 0px;
height: 48px;
min-width: 120px;
background: #333333;
z-index: 2;
color: white;
border-radius: 4px 4px 0 0;
padding: 0 4px 0 4px;
button {
border: none;
background: transparent;
width: 48px;
height: 48px;
padding: 8px;
outline: none;
cursor: pointer;
&.active {
svg {
fill: url(#activeButtonGradient);
stroke: url(#activeButtonGradient);
}
}
svg {
fill: white;
stroke: white;
}
}
}

View file

@ -1,13 +0,0 @@
import styled from 'styled-components';
export const ControlsScreen = styled.div`
position: fixed;
right: 10px;
bottom: 10px;
height: 48px;
min-width: 120px;
background: #333333;
border-radius: 2px;
z-index: 2;
color: white;
`;

26
src/styles/main.less Normal file
View file

@ -0,0 +1,26 @@
@import 'map.less';
@import 'panel.less';
@import 'router.less';
@import 'stickers.less';
@import 'button.less';
body {
font-family: sans-serif;
font-size: 14px;
}
.gray {
opacity: 0.75;
}
.big {
font-size: 14px;
}
.small {
font-size: 12px;
}
.white {
color: white;
}

View file

@ -38,139 +38,3 @@
.leaflet-control-container .leaflet-routing-container-hide {
display: none;
}
.router-waypoint {
width: 40px;
height: 40px;
margin-left: -20px;
margin-top: -20px;
outline: none;
z-index: 10001;
&:after {
content: ' ';
display: block;
width: 20px;
height: 20px;
border-radius: 10px;
box-shadow: 0 0 0 2px #4597d0;
margin-left: 10px;
margin-top: 10px;
}
}
.sticker-container {
outline: none;
position: relative;
&:before {
content: ' ';
box-shadow: 0 0 10px 1px #ff3344;
background: #ff334422;
width: 48px;
height: 48px;
left: -24px;
top: -24px;
position: absolute;
border-radius: 40px;
opacity: 0;
transition: opacity 250ms, transform 500ms;
transform: scale(0);
}
&:hover, &:active {
.sticker-delete {
transform: scale(1);
pointer-events: all;
}
&:before {
opacity: 1;
transform: scale(1);
}
}
}
.sticker-label {
width: 48px;
height: 48px;
position: absolute;
background: white;
border-radius: 32px;
left: 0;
top: 0;
outline: none;
&:after {
content: ' ';
box-shadow: 0 0 0 1px #ff3344;
width: 80px;
height: 80px;
left: -16px;
top: -16px;
position: absolute;
border-radius: 40px;
pointer-events: none;
opacity: 0;
}
}
.sticker-arrow {
position: absolute;
background: red;
transform-origin: 0 0;
left: 0;
top: 0;
&:after {
content: ' ';
background: #ff3344;
width: 24px;
height: 24px;
transform-origin: 0 0;
transform: rotate(-45deg);
left: 0;
top: 0;
position: absolute;
}
}
.sticker-delete {
position: absolute;
width: 24px;
height: 24px;
background: red;
border-radius: 24px;
transition: transform 500ms;
transform: scale(0);
opacity: 1;
pointer-events: none;
&:hover {
transform: scale(1.2) !important;
}
}
.leaflet-control-container .leaflet-routing-container-hide {
display: none;
}
.router-waypoint {
width: 40px;
height: 40px;
margin-left: -20px;
margin-top: -20px;
outline: none;
z-index: 10001;
&:after {
content: ' ';
display: block;
width: 20px;
height: 20px;
border-radius: 10px;
box-shadow: 0 0 0 2px #4597d0;
margin-left: 10px;
margin-top: 10px;
}
}

View file

@ -1,162 +0,0 @@
import styled, { css } from 'styled-components';
const vertexMixin = css`
.leaflet-vertex-icon, .leaflet-middle-icon {
border-radius: 10px;
opacity :1;
border: none;
width: 16px !important;
height: 16px !important;margin-left:-8px !important;margin-top:-8px !important;
background: transparent;
}
.leaflet-vertex-icon::after, .leaflet-middle-icon::after {
content: ' ';
position:absolute;top:4px;left:4px;width:8px;height:8px;
background:white;border-radius: 8px;transform:scale(1);
transition:transform 150ms;
}
.leaflet-vertex-icon:hover, .leaflet-middle-icon:hover {
opacity: 1 !important;
}
.leaflet-vertex-icon:hover::after, .leaflet-middle-icon:hover::after,
.leaflet-vertex-icon:active::after, .leaflet-middle-icon:active::after {
transform: scale(2);
box-shadow: #999 0 0 5px 2px;
}
`;
const routerMixin = css`
.leaflet-control-container .leaflet-routing-container-hide {
display: none;
}
.router-waypoint {
width: 40px;
height: 40px;
margin-left: -20px;
margin-top: -20px;
outline: none;
z-index: 10001;
::after {
content: ' ';
display: block;
width: 20px;
height: 20px;
border-radius: 10px;
box-shadow: 0 0 0 2px #4597d0;
margin-left: 10px;
margin-top: 10px;
}
}
`;
const stickers = css`
.sticker-container {
outline: none;
position: relative;
::before {
content: ' ';
box-shadow: 0 0 10px 1px #ff3344;
background: #ff334422;
width: 48px;
height: 48px;
left: -24px;
top: -24px;
position: absolute;
border-radius: 40px;
opacity: 0;
transition: opacity 250ms;
}
:hover {
.sticker-delete {
transform: scale(1);
pointer-events: all;
}
::before {
opacity: 1;
}
}
}
.sticker-label {
width: 48px;
height: 48px;
position: absolute;
background: white;
border-radius: 32px;
left: 0;
top: 0;
outline: none;
::after {
content: ' ';
box-shadow: 0 0 0 1px #ff3344;
width: 80px;
height: 80px;
left: ${24 - 40}px;
top: ${24 - 40}px;
position: absolute;
border-radius: 40px;
pointer-events: none;
opacity: 0;
}
}
.sticker-arrow {
position: absolute;
background: red;
transform-origin: 0 0;
left: 0;
top: 0;
::after {
content: ' ';
background: #ff3344;
width: 24px;
height: 24px;
transform-origin: 0 0;
transform: rotate(-45deg);
left: 0;
top: 0;
position: absolute;
}
}
.sticker-delete {
position: absolute;
width: 24px;
height: 24px;
background: red;
border-radius: 24px;
transition: transform 500ms;
transform: scale(0);
opacity: 1;
pointer-events: none;
:hover {
transform: scale(1.2) !important;
}
}
`;
export const MapScreen = styled.div.attrs({ id: 'map' })`
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
left: 0;
top: 0;
${vertexMixin}
${stickers}
${routerMixin}
`;

92
src/styles/panel.less Normal file
View file

@ -0,0 +1,92 @@
.control-bar {
background: #333333;
border-radius: 3px;
display: flex;
box-shadow: rgba(0,0,0,0.3) 0 2px 0, inset rgba(255, 255, 255, 0.05) 1px 1px;
}
.control-sep {
height: 44px;
background: #222222;
width: 3px;
}
.panel {
position: fixed;
left: 10px;
bottom: 10px;
z-index: 3;
color: white;
display: flex;
align-items: center;
&.right {
left: auto;
right: 10px;
}
button {
border: none;
background: transparent;
padding: 8px;
outline: none;
cursor: pointer;
display: inline-flex;
color: white;
align-items: center;
transition: background-color 500ms;
height: 48px;
box-sizing: border-box;
&:hover {
background: rgba(100, 100, 100, 0.2);
}
span {
margin-right: 8px;
font-size: 14px;
font-weight: 200;
margin-left: 8px;
}
&:last-child {
border-radius: 0 4px 4px 0;
}
&.active {
svg {
fill: url(#activeButtonGradient);
stroke: url(#activeButtonGradient);
}
}
&.highlighted {
background: linear-gradient(150deg, #05a4ff, #7137c8);
}
svg {
fill: white;
stroke: white;
display: inline;
}
}
}
.panel-separator {
height: 48px;
width: 4px;
background: #222222;
}
#control-dialog {
background: #222222;
position: absolute;
right: 10px;
bottom: 10px;
border-radius: 3px;
z-index: 2;
color: white;
box-sizing: border-box;
padding-bottom: 48px;
box-shadow: rgba(0,0,0,0.3) 0 2px 0, inset rgba(255, 255, 255, 0.05) 1px 1px;
}

36
src/styles/router.less Normal file
View file

@ -0,0 +1,36 @@
.router-waypoint {
width: 40px;
height: 40px;
margin-left: -20px;
margin-top: -20px;
outline: none;
z-index: 10001;
&:after {
content: ' ';
display: block;
width: 20px;
height: 20px;
border-radius: 10px;
box-shadow: 0 0 0 2px #4597d0;
margin-left: 10px;
margin-top: 10px;
}
}
.router-helper {
width: 500px;
padding: 10px;
font-weight: 200;
font-size: 14px;
display: flex;
}
.router-helper__text {
width: 100%;
}
.router-helper__buttons {
display: flex;
align-items: center;
}

95
src/styles/stickers.less Normal file
View file

@ -0,0 +1,95 @@
.sticker-container {
outline: none;
position: relative;
&:before {
content: ' ';
box-shadow: 0 0 10px 1px #ff3344;
background: #ff334422;
width: 48px;
height: 48px;
left: -24px;
top: -24px;
position: absolute;
border-radius: 40px;
opacity: 0;
transition: opacity 250ms, transform 500ms;
transform: scale(0);
}
&:hover, &:active {
.sticker-delete {
transform: scale(1);
pointer-events: all;
}
&:before {
opacity: 1;
transform: scale(1);
}
}
}
.sticker-label {
width: 48px;
height: 48px;
position: absolute;
background: white;
border-radius: 32px;
left: 0;
top: 0;
outline: none;
&:after {
content: ' ';
box-shadow: 0 0 0 1px #ff3344;
width: 80px;
height: 80px;
left: -16px;
top: -16px;
position: absolute;
border-radius: 40px;
pointer-events: none;
opacity: 0;
}
}
.sticker-arrow {
position: absolute;
background: red;
transform-origin: 0 0;
left: 0;
top: 0;
&:after {
content: ' ';
background: #ff3344;
width: 24px;
height: 24px;
transform-origin: 0 0;
transform: rotate(-45deg);
left: 0;
top: 0;
position: absolute;
}
}
.sticker-delete {
position: absolute;
width: 24px;
height: 24px;
background: red;
border-radius: 24px;
transition: transform 500ms;
transform: scale(1);
opacity: 1;
pointer-events: none;
&:hover {
transform: scale(1.2) !important;
}
}
.leaflet-control-container .leaflet-routing-container-hide {
display: none;
}