Compare commits

...

329 commits
1 ... master

Author SHA1 Message Date
Fedor Katurov
322337edea add analytics 2023-12-29 12:34:09 +07:00
Fedor Katurov
1dcfd7e9c0 change backend and osrm urls 2023-12-01 10:44:15 +07:00
Fedor Katurov
49a2015510 pass additional args on a build stage 2023-11-30 16:09:23 +07:00
Fedor Katurov
12f27b8307 debug REACT_APP_API_ADDR 2023-11-30 16:03:45 +07:00
Fedor Katurov
93eefa3b3c removed .env 2021-12-17 09:13:52 +07:00
Fedor Katurov
f48b724b9c fixed yaml file 2021-10-18 15:47:36 +07:00
Fedor Katurov
151b26ebd3 added drone config 2021-10-18 15:38:55 +07:00
muerwre
ccf7b606bf
Merge pull request #34 from muerwre/dependabot/npm_and_yarn/sockjs-0.3.21
Bump sockjs from 0.3.19 to 0.3.21
2021-08-14 09:34:21 +07:00
muerwre
b199c99519
Merge pull request #35 from muerwre/dependabot/npm_and_yarn/ws-6.2.2
Bump ws from 6.2.1 to 6.2.2
2021-08-14 09:34:09 +07:00
Fedor Katurov
738f69fff4 fixed stickers scroll 2021-08-14 09:27:30 +07:00
muerwre
bc5892120f
Merge pull request #38 from muerwre/develop
Develop
2021-08-06 11:39:07 +07:00
Fedor Katurov
91fc203b0a added zoom icons 2021-08-06 11:37:45 +07:00
Fedor Katurov
afc1b084e1 fixed styles 2021-08-06 11:33:34 +07:00
dependabot[bot]
11a0556f99
Bump ws from 6.2.1 to 6.2.2
Bumps [ws](https://github.com/websockets/ws) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/commits)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-05 19:57:28 +00:00
dependabot[bot]
a03a906648
Bump sockjs from 0.3.19 to 0.3.21
Bumps [sockjs](https://github.com/sockjs/sockjs-node) from 0.3.19 to 0.3.21.
- [Release notes](https://github.com/sockjs/sockjs-node/releases)
- [Changelog](https://github.com/sockjs/sockjs-node/blob/v0.3.21/Changelog)
- [Commits](https://github.com/sockjs/sockjs-node/compare/v0.3.19...v0.3.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-20 22:57:36 +00:00
Fedor Katurov
a3982eddae fixed provider and logo persistence 2021-04-20 18:01:28 +07:00
Fedor Katurov
b421005b7d fixed change detection by stickers 2021-04-20 17:54:18 +07:00
Fedor Katurov
10d5fa1b98 fixed dash on renderer 2021-04-20 17:49:35 +07:00
Fedor Katurov
7b24499f49 fixed svg colors 2021-04-20 17:36:49 +07:00
Fedor Katurov
8346c2533f fixed sticker deletion bug 2021-04-20 17:34:53 +07:00
Fedor Katurov
0018465f15 fixed token checking and some types 2021-04-20 17:26:36 +07:00
Fedor Katurov
48cf0b93ee fixed token checking 2021-04-20 17:21:05 +07:00
Fedor Katurov
9c436d348c added post map interceptor 2021-04-20 16:39:51 +07:00
Fedor Katurov
fda24c78b0 Merge branch 'master' into develop 2021-04-20 16:18:57 +07:00
Fedor Katurov
b563b51121 fixed router points position 2021-04-13 17:59:05 +07:00
Fedor Katurov
5f4d2f9cd2 fixed router stickers 2021-04-13 17:33:36 +07:00
Fedor Katurov
fe311e7839 fixed typescript errors 2021-04-08 16:25:25 +07:00
Fedor Katurov
9a7a038032 added favicon 2021-04-08 15:13:04 +07:00
Fedor Katurov
dc01dfe3c0 disabled telegram notifications 2021-04-08 15:00:35 +07:00
Fedor Katurov
c1c99b4562 cleaned providers list 2021-04-08 14:49:28 +07:00
Fedor Katurov
5e3c416824 replaced tile provider from cacheable to osm 2021-04-08 14:46:10 +07:00
Fedor Katurov
a4b8d70cc8 removed unused deps 2021-04-08 14:44:31 +07:00
Fedor Katurov
4e167c9759 fixed marker dragging 2021-04-08 14:44:13 +07:00
Fedor Katurov
3109f49cc4 fixed images 2021-04-08 14:15:09 +07:00
Fedor Katurov
1eefa0ebbc fixed slider styles 2021-04-08 14:07:41 +07:00
Fedor Katurov
faed11bb79 copying build, not dist 2021-04-08 11:40:08 +07:00
Fedor Katurov
7735d11019 set workdir to / at dockerfile 2021-04-08 11:39:39 +07:00
Fedor Katurov
fd65653584 fixed dicker output folder 2021-04-08 11:32:09 +07:00
Fedor Katurov
1d1eedd911 added eslint-loader 2021-04-08 11:30:44 +07:00
Fedor Katurov
82cd56fee1 added configs 2021-04-08 11:25:41 +07:00
Fedor Katurov
478c557a2b removed reactrangeslider dependency 2021-04-08 11:24:17 +07:00
Fedor Katurov
9b52ff10e5 fixed styles 2021-04-08 11:21:13 +07:00
Fedor Katurov
4e18eba558 made global less styles 2021-04-08 10:59:49 +07:00
Fedor Katurov
c2f42ea721 added create-react-app 2021-04-08 10:35:39 +07:00
Fedor Katurov
b5b7c82ea4 changed target node 2021-04-08 10:11:41 +07:00
Fedor Katurov
6db5b62a74 removed expandable textarea 2021-04-08 10:09:19 +07:00
Fedor Katurov
4f65aa8f89 replaced leaflet-routing-machine 2021-04-08 10:07:34 +07:00
Fedor Katurov
91c9adba83 removed leaflet-editable-polyline 2021-04-08 10:06:18 +07:00
Fedor Katurov
82bd1345a1 added .env files 2021-04-08 10:04:30 +07:00
Fedor Katurov
ef08fadeb1 added ci-cd files 2021-04-08 09:44:13 +07:00
Fedor Katurov
b60c97283d added tick city icon 2020-10-29 15:15:52 +07:00
Fedor Katurov
25d6ecaa17 added night city icon 2020-10-29 14:57:12 +07:00
Fedor Katurov
9017e63fac added night city icon 2020-10-29 14:41:57 +07:00
Fedor Katurov
1c62320e39 added night city icon 2020-10-29 14:29:34 +07:00
Fedor Katurov
e9be5ad612 added coffee icon 2020-10-29 12:11:24 +07:00
Fedor Katurov
1e5d10098d added coffee icon 2020-10-29 12:01:54 +07:00
Fedor Katurov
3d65cab965 added coffee icon 2020-10-29 11:56:44 +07:00
Fedor Katurov
7d4046ddc5 added lenin's sculpture 2020-10-29 11:10:42 +07:00
muerwre
08449b10c3
Merge pull request #29 from muerwre/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-10-29 10:10:49 +07:00
dependabot[bot]
608ec84a53
Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-16 08:33:26 +00:00
Fedor Katurov
fa6f481906 test jenkins 2020-03-20 21:49:53 +07:00
Fedor Katurov
6c39d20b98 test jenkins 2020-03-20 21:48:59 +07:00
Fedor Katurov
036dae321f test jenkins 2020-03-20 21:48:10 +07:00
Fedor Katurov
b207cdd55c test jenkins 2020-03-20 21:41:56 +07:00
Fedor Katurov
ef7dd1de24 test jenkins 2020-03-20 21:29:15 +07:00
Fedor Katurov
40c4e433bc test jenkins 2020-03-20 21:21:41 +07:00
Fedor Katurov
80ae6cb375 test jenkins 2020-03-20 21:17:53 +07:00
Fedor Katurov
a2f338c377 test jenkins 2020-03-20 21:16:34 +07:00
Fedor Katurov
bb5310107b test jenkins 2020-03-20 21:15:42 +07:00
Fedor Katurov
4d828baf5c test jenkins 2020-03-20 21:14:57 +07:00
Fedor Katurov
ed796167ea test jenkins 2020-03-20 21:12:24 +07:00
Fedor Katurov
4b7bff0f30 test jenkins 2020-03-20 21:11:19 +07:00
Fedor Katurov
4cffb64bc5 test jenkins 2020-03-20 21:10:36 +07:00
Fedor Katurov
8a80569cca test jenkins 2020-03-20 21:08:22 +07:00
Fedor Katurov
9ad26ddd56 test jenkins 2020-03-20 21:07:13 +07:00
Fedor Katurov
79e1418265 test jenkins 2020-03-20 21:06:38 +07:00
Fedor Katurov
d5460ea89f test jenkins 2020-03-20 21:05:09 +07:00
Fedor Katurov
fd01a0db9d test jenkins 2020-03-20 21:03:36 +07:00
Fedor Katurov
0b7cf74266 test jenkins 2020-03-20 21:02:26 +07:00
Fedor Katurov
e33a003894 test jenkins 2020-03-20 20:59:31 +07:00
Fedor Katurov
3a23bca44e test jenkins 2020-03-20 20:53:29 +07:00
Fedor Katurov
2a8734e440 test jenkins 2020-03-20 20:48:03 +07:00
Fedor Katurov
67fa815359 test jenkins 2020-03-20 20:47:06 +07:00
Fedor Katurov
74b8e7a4c8 test jenkins 2020-03-20 20:44:24 +07:00
Fedor Katurov
cc884bcf64 test jenkins 2020-03-20 20:43:42 +07:00
Fedor Katurov
865decd7cb test jenkins 2020-03-20 20:27:10 +07:00
Fedor Katurov
750c39102d test jenkins 2020-03-20 20:27:03 +07:00
Fedor Katurov
124bffedfe test jenkins 2020-03-20 20:24:51 +07:00
Fedor Katurov
5946f1a618 testing jenkins 2020-03-20 20:24:04 +07:00
Fedor Katurov
3a8d0021cd test jenkins 2020-03-20 20:17:40 +07:00
Fedor Katurov
4adacd0698 test jenkins 2020-03-20 20:16:58 +07:00
Fedor Katurov
03c85c5112 test jenkins 2020-03-20 20:15:55 +07:00
Fedor Katurov
74f7d2171b test jenkins 2020-03-20 20:13:11 +07:00
Fedor Katurov
cf2391e791 check jenkins 2020-03-20 20:09:50 +07:00
Fedor Katurov
8dfd28b346 test jenkins 2020-03-20 20:05:26 +07:00
Fedor Katurov
ae3f6d13da test jenkins 2020-03-20 20:04:53 +07:00
Fedor Katurov
d1d324a792 checking jenkins 2020-03-20 20:03:26 +07:00
Fedor Katurov
caa11c1eca check jenkins 2020-03-20 20:01:47 +07:00
Fedor Katurov
b9a499d0f0 testing jenkins 2020-03-20 19:52:09 +07:00
Fedor Katurov
7fa24d2fe6 testing jenkins 2020-03-20 19:49:21 +07:00
Fedor Katurov
b2ba661d56 testing jenkins 2020-03-20 19:47:08 +07:00
Fedor Katurov
12fa07f243 added jenkins file 2020-03-20 17:37:43 +07:00
Fedor Katurov
75f76e473b removed backend part 2020-03-20 17:11:36 +07:00
Fedor Katurov
97fe205e5f fixed text rendering 2020-02-14 10:58:09 +07:00
Fedor Katurov
3ca211b9bb fixed adaptive zoom 2020-02-11 15:38:53 +07:00
Fedor Katurov
a1c55befa2 adaptive sticker scale 2020-02-11 15:35:13 +07:00
Fedor Katurov
ef5cd0cdef scaling stickers 2020-02-11 15:10:43 +07:00
Fedor Katurov
09ce310bad changed default provider 2020-02-11 12:16:36 +07:00
Fedor Katurov
008a42bd9b fixed min max again 2020-02-11 11:36:41 +07:00
Fedor Katurov
a19555b429 fixed min-max problem 2020-02-11 11:32:22 +07:00
Fedor Katurov
1d45e65434 replacing gpx tracks 2020-02-11 11:04:57 +07:00
Fedor Katurov
acca2aba14 fixed gpx importing 2020-02-11 10:24:53 +07:00
Fedor Katurov
b03c5ce0de fixed transition for stickers 2020-02-10 17:33:26 +07:00
Fedor Katurov
5f7c036149 fixed again 2020-01-27 16:40:02 +07:00
Fedor Katurov
067021b18a undo bar for mobile devices 2020-01-27 16:18:50 +07:00
Fedor Katurov
b2357cf522 disabled append features for tracks 2020-01-27 15:51:29 +07:00
Fedor Katurov
53bb6a8443 start marker 2020-01-27 15:49:43 +07:00
Fedor Katurov
5ceda71589 gpx tracks not printable now 2020-01-27 11:53:21 +07:00
Fedor Katurov
f41078b769 printing gpx tracks 2020-01-24 18:08:34 +07:00
Fedor Katurov
5604d8716b faster track redraw 2020-01-24 17:48:07 +07:00
Fedor Katurov
c4aede5833 changing gpx color 2020-01-24 17:37:49 +07:00
Fedor Katurov
871f9bb8a2 fixed gpx dialog paddings 2020-01-24 17:27:31 +07:00
Fedor Katurov
460440ea24 fixed dialog width for micro screens 2020-01-24 17:18:46 +07:00
Fedor Katurov
3ffba24994 better gpx route error handling 2020-01-24 17:00:23 +07:00
Fedor Katurov
e995b46641 added gpx dialog 2020-01-24 16:53:47 +07:00
Fedor Katurov
947ec69e60 hiding buttons on short route 2020-01-22 17:01:51 +07:00
Fedor Katurov
02e570bc9c fix 2020-01-22 16:01:43 +07:00
Fedor Katurov
dd1c9a1d1c fixed panels behaviour 2020-01-22 15:53:15 +07:00
Fedor Katurov
ad676d5fde forwards and backwards drawing 2020-01-22 15:16:34 +07:00
Fedor Katurov
bbd7d6a89a better mouse hinting and ability to drop all markers on polyline 2020-01-22 14:04:23 +07:00
Fedor Katurov
16308996e1 c to clear 2020-01-22 13:07:17 +07:00
Fedor Katurov
7c931f4f6d clearing route on new position 2020-01-22 13:03:52 +07:00
Fedor Katurov
9a08ccd95b hide undo on mobiles 2020-01-22 12:33:09 +07:00
Fedor Katurov
37003b9e85 fixed mobile breakpoint 2020-01-22 12:30:09 +07:00
Fedor Katurov
ba7a8f9d66 cleanup 2020-01-22 12:26:26 +07:00
Fedor Katurov
8e0a4c6df6 fixed sticker mode change 2020-01-22 12:15:40 +07:00
Fedor Katurov
60b8fd51a2 better sticker change handling 2020-01-22 12:02:16 +07:00
Fedor Katurov
a1f9704c0b keyboard hints for undo and redo 2020-01-22 11:54:59 +07:00
Fedor Katurov
4915744c84 undo and redo functionality 2020-01-22 11:53:19 +07:00
Fedor Katurov
8df7d7d27d inactive status for polyline tools 2020-01-22 09:49:37 +07:00
Fedor Katurov
3de4c085da polyline tools 2020-01-22 09:42:48 +07:00
Fedor Katurov
2d749166cf route reverse 2020-01-21 16:04:36 +07:00
Fedor Katurov
8cac89cdd1 fixed dialogs 2020-01-21 15:08:01 +07:00
Fedor Katurov
d0f419c18b setMode --> changeMode 2020-01-21 15:01:17 +07:00
Fedor Katurov
5e55434772 disabled nominatim for now 2020-01-21 14:47:16 +07:00
Fedor Katurov
3c0b6cfa22 Merge branch 'master' into develop 2020-01-21 14:42:44 +07:00
Fedor Katurov
b3adf4d556 fixed range 2020-01-20 17:55:46 +07:00
Fedor Katurov
0fd656e5fa nominatim 2020-01-20 17:52:21 +07:00
Fedor Katurov
3a988d23df nominatim (dialog still doesn't work) 2020-01-20 16:55:40 +07:00
Fedor Katurov
b20a3445d1 nominatim search (without working dialog) 2020-01-20 16:42:46 +07:00
Fedor Katurov
c3e136cebb support for partial height dialogs 2020-01-20 13:27:28 +07:00
Fedor Katurov
30197c7f15 current location button 2020-01-20 12:44:12 +07:00
Fedor Katurov
5ef427cb45 zooming on marker click 2020-01-20 12:29:49 +07:00
Fedor Katurov
a574b7393d current user location 2020-01-20 12:20:54 +07:00
Fedor Katurov
5e3aa587c7 fixed min marker width 2020-01-17 18:02:58 +07:00
Fedor Katurov
ea8936f283 fixed km markers radiuses 2020-01-17 18:01:33 +07:00
Fedor Katurov
cb02338c67 removed console.log 2020-01-17 17:56:44 +07:00
Fedor Katurov
d4307ae336 good markers for router 2020-01-17 17:55:36 +07:00
Fedor Katurov
50c2ac70e3 fixed marker 2020-01-17 17:26:13 +07:00
Fedor Katurov
d752039de9 new marker for router 2020-01-17 17:25:47 +07:00
Fedor Katurov
24641a33f7 router now has additional end marker 2020-01-17 17:20:17 +07:00
Fedor Katurov
5db6d85e35 displaying end marker 2020-01-17 17:13:57 +07:00
Fedor Katurov
72c6b99f58 fixed dialog wrong appearance at disabled editor 2020-01-17 15:51:35 +07:00
Fedor Katurov
80120eb37b fixed touch hinter error 2020-01-16 18:05:08 +07:00
Fedor Katurov
71f2f7fba5 fixed arrow angle for km marks 2020-01-16 17:25:14 +07:00
Fedor Katurov
d8d448bcf7 updated leaflet version and tree-shaked deps 2020-01-16 17:08:55 +07:00
Fedor Katurov
973f934614 fixed arrows 2020-01-16 16:59:09 +07:00
Fedor Katurov
bdcd7fd814 cleanup 2020-01-16 16:32:37 +07:00
Fedor Katurov
4ac0bbebe2 even better performance by combining km marks and arrows 2020-01-16 16:31:47 +07:00
Fedor Katurov
f4cd6bd44a combined arrows with km marks for better performace 2020-01-16 16:26:56 +07:00
Fedor Katurov
58427e7017 optimized arrows a little bit 2020-01-16 16:19:08 +07:00
Fedor Katurov
de2b747a20 Arrows layer 2020-01-16 12:03:02 +07:00
Fedor Katurov
69d1d749cf refactored rerendering speed 2020-01-16 11:49:24 +07:00
Fedor Katurov
b6bf317649 map dialog optimizations 2020-01-16 11:04:43 +07:00
Fedor Katurov
23b8f5dea6 fixed gpx downloading 2020-01-16 10:33:48 +07:00
Fedor Katurov
18cbeed06e fixed distance on render 2020-01-16 10:28:00 +07:00
Fedor Katurov
586e09f2b5 changed panel radius 2020-01-16 10:10:42 +07:00
Fedor Katurov
2dd14f4229 fixed km marks step 2020-01-14 12:55:41 +07:00
Fedor Katurov
7e537d87ac fixed layer moving 2020-01-14 12:54:06 +07:00
Fedor Katurov
a06326fb1c better change handling on map change 2020-01-14 12:38:11 +07:00
Fedor Katurov
d8b51e0b1a optimized marks calculation speed 2020-01-14 12:29:05 +07:00
Fedor Katurov
0ea9710497 cleaned up stickers 2020-01-14 12:09:06 +07:00
Fedor Katurov
1307ff407e refactored stickers 2020-01-14 11:55:41 +07:00
Fedor Katurov
27884b1201 redeploy 2020-01-14 11:48:17 +07:00
Fedor Katurov
8c9a7fa339 fixed create reducer 2020-01-14 11:47:43 +07:00
Fedor Katurov
ce128306cc redeploy 2020-01-14 11:30:52 +07:00
Fedor Katurov
bf4ecc68c8 fixed insecure route list on user login 2020-01-14 11:26:12 +07:00
Fedor Katurov
0710ecdf01 faster sticker rotation and angle update fix 2020-01-14 11:15:46 +07:00
Fedor Katurov
56fd8ec4cd fixed cancel dialog 2020-01-14 11:08:26 +07:00
Fedor Katurov
1a00b98ba3 setting changed on actual map change 2020-01-14 10:47:25 +07:00
Fedor Katurov
34f98fb08b faster distance calc 2020-01-14 10:40:33 +07:00
Fedor Katurov
2e9b332012 removed unused console.log 2020-01-13 18:01:22 +07:00
Fedor Katurov
0b0b8b9cc0 added km marks 2020-01-13 18:00:06 +07:00
Fedor Katurov
bc34cf3876 added map fitting 2020-01-13 17:43:38 +07:00
Fedor Katurov
0314edd550 fixed sticker text issues 2020-01-10 14:45:17 +07:00
Fedor Katurov
afc9654200 fixed dialog witdths 2020-01-10 14:32:12 +07:00
Fedor Katurov
65885acb75 fixed repeating map requests 2020-01-10 12:30:32 +07:00
Fedor Katurov
9760002fad fixed map setting on load 2020-01-10 12:26:33 +07:00
Fedor Katurov
8adf64acb9 cleanup 2020-01-10 09:21:37 +07:00
Fedor Katurov
cb07aa9fb0 fixed marker hiding 2020-01-09 17:17:01 +07:00
Fedor Katurov
bdbe28b854 fixed dragging polyline collision with router 2020-01-09 17:09:56 +07:00
Fedor Katurov
2be073078f fixed router 2020-01-09 16:55:41 +07:00
Fedor Katurov
42dbfb0681 fixed setting distance 2020-01-09 14:03:13 +07:00
Fedor Katurov
c166eee586 drawing poly 2020-01-09 12:58:11 +07:00
Fedor Katurov
7bdf07cae5 fixed map clicks on sticker drag 2020-01-09 12:44:25 +07:00
Fedor Katurov
67eeaa7293 fixed trash dialog 2020-01-09 11:47:01 +07:00
Fedor Katurov
87670770b0 moved editor to separate reducer 2020-01-09 10:59:26 +07:00
Fedor Katurov
e950d98b73 updated aliases 2020-01-08 14:38:20 +07:00
Fedor Katurov
af8d270460 cleaned out user reducer 2020-01-08 14:25:46 +07:00
Fedor Katurov
103097edbd fixed imports 2020-01-08 12:51:19 +07:00
Fedor Katurov
23c9e42bd5 fixed logo components 2020-01-08 12:15:00 +07:00
Fedor Katurov
0f31144567 fixed provider dialog 2020-01-08 12:11:06 +07:00
Fedor Katurov
0c321f2bb3 removed all modules 2020-01-08 12:07:36 +07:00
Fedor Katurov
aa8fd14517 renamed some map actions from user 2020-01-08 11:44:27 +07:00
Fedor Katurov
de5726929f removed unnecessary action creators 2019-12-30 21:16:04 +07:00
Fedor Katurov
b75c028ce1 separated map and user reducers 2019-12-30 21:01:01 +07:00
Fedor Katurov
9f8cb1a875 cleanup 2019-12-30 17:59:02 +07:00
Fedor Katurov
848fa9fd02 cleanup 2019-12-30 17:56:19 +07:00
Fedor Katurov
6db2809de5 adding stickers on click 2019-12-30 17:35:37 +07:00
Fedor Katurov
e0048d1fc3 cleanup 2019-12-30 16:51:45 +07:00
Fedor Katurov
9c3c8cf46d passing editing status to map 2019-12-30 16:41:37 +07:00
Fedor Katurov
58eefd5670 stickers stops dragging 2019-12-30 15:07:59 +07:00
Fedor Katurov
fca52df9f5 added editable route 2019-12-30 13:16:35 +07:00
Fedor Katurov
5664291c92 map reducer and user reducer refactor 2019-12-30 11:10:28 +07:00
Fedor Katurov
813fded927 fixed reducer placement for user 2019-12-30 11:05:04 +07:00
Fedor Katurov
f814fe6c42 fixed undetected console log 2019-12-30 11:01:47 +07:00
Fedor Katurov
570efa661d changed readme 2019-12-14 23:42:15 +07:00
Fedor Katurov
d8705e3fb2 cleanup 2019-12-14 23:00:51 +07:00
Fedor Katurov
01a8a44114 removed non-working hosts from cache 2019-12-14 19:49:13 +07:00
Fedor Katurov
ef92e4f38c fixed errors 2019-12-14 19:34:26 +07:00
Fedor Katurov
6880d155ed removed providers with cached ones 2019-12-14 19:32:00 +07:00
Fedor Katurov
6ad3d9681a replaced some providers with cached one 2019-12-14 18:57:47 +07:00
Fedor Katurov
40c589063a fixed broken logo 2019-12-14 18:37:06 +07:00
Fedor Katurov
bbd7f801b4 added carto to cache 2019-12-14 18:34:51 +07:00
Fedor Katurov
710d04a77b added test cache server 2019-12-14 18:30:47 +07:00
Fedor Katurov
ba446169a9 test deploy 2019-12-14 18:26:56 +07:00
Fedor Katurov
d55f403ff2 removed serve tasks 2019-12-13 13:26:19 +07:00
Fedor Katurov
5b857c1724 removed backend configs 2019-12-13 13:24:15 +07:00
Fedor Katurov
dd6f163793 removed backend part 2019-12-13 13:23:54 +07:00
Fedor Katurov
7bea7b9ed1 fixed idents 2019-12-13 12:30:22 +07:00
Fedor Katurov
288c25e32c changed todos 2019-12-13 12:13:41 +07:00
Fedor Katurov
2144af9899 modified to match golang backend 2019-12-12 17:22:57 +07:00
Fedor Katurov
62cb8d8e18 fixed loading route and maps 2019-12-12 13:52:30 +07:00
Fedor Katurov
1f774a8299 fixing dl problem with regexp 2019-10-08 14:19:32 +07:00
Fedor Katurov
f664eb53e7 fixed filename saving error 2019-10-08 14:12:45 +07:00
Fedor Katurov
3ace7b157a removed consolelog 2019-10-08 14:09:28 +07:00
Fedor Katurov
8e860f327a removed flow 2019-09-17 15:21:45 +07:00
Integral Team
577398dc17 removed radius around dialog head 2019-09-11 18:01:22 +07:00
Integral Team
83b7e8dc98 added numeric stickers 2019-09-11 17:53:03 +07:00
Integral Team
29dde732bd fixed panel radiuses 2019-09-11 17:34:55 +07:00
Integral Team
dc66889a42 removed touchend 2019-09-11 17:26:24 +07:00
Integral Team
8b86b2c91c disabled transform transition for mobile devices 2019-09-11 17:23:48 +07:00
Integral Team
17efbba1ff added navigator to globals 2019-09-11 17:17:43 +07:00
Integral Team
350a090ac3 added service worker 2019-09-11 17:16:31 +07:00
Integral Team
944be7f4b1 changed trash icon 2019-09-11 17:13:53 +07:00
Integral Team
13c829e6cc removed sticker transform animation 2019-09-11 17:08:05 +07:00
Integral Team
a8ddf1e7fa removed conlog 2019-09-11 17:03:46 +07:00
Integral Team
3a74299ece added pwa cacher 2019-09-11 16:53:47 +07:00
Integral Team
8ba5d4d238 display fullscreen 2019-09-11 16:33:56 +07:00
Integral Team
9b4a6c45ee changed manifest details 2019-09-11 16:25:00 +07:00
Integral Team
cdbbaa0450 tried to add manifest 2019-09-11 16:24:10 +07:00
Integral Team
1a5dfd29a0 hide range slider on insufficient ranges 2019-09-11 16:07:37 +07:00
Integral Team
6f40f29d84 ceil to floor for map min and max 2019-09-11 16:05:26 +07:00
Integral Team
4416fa5d64 fixed bound fitting 2019-09-11 16:00:58 +07:00
Integral Team
acedb845af disabled stickers clustering 2019-09-11 15:39:59 +07:00
Integral Team
f62fce715d purple close button 2019-09-11 15:37:37 +07:00
Integral Team
45fe192564 fixed tabs and buttons on mobile 2019-09-11 15:37:01 +07:00
Integral Team
284c840a93 test deploy 2019-09-11 15:28:36 +07:00
Integral Team
edf819ab1a test deploy 2019-09-11 15:25:40 +07:00
Integral Team
92d50d4932 test deploy 2019-09-11 15:23:19 +07:00
Integral Team
d6fabfbb6f added blink to progress bar 2019-09-11 15:18:11 +07:00
Integral Team
0bfb1dee90 fixed loading error handling 2019-09-11 15:16:44 +07:00
Integral Team
dd2861fe5c fixed sticker desc touch handler 2019-09-11 12:57:55 +07:00
Integral Team
646c38c0ee updated todo 2019-09-11 12:52:55 +07:00
Integral Team
c1e3c6f383 not showing sticker dialog after adding sticker 2019-09-11 12:51:00 +07:00
Integral Team
017d2c75b6 antoher icon for map catalogue 2019-09-11 12:00:36 +07:00
Integral Team
18da8c9238 hide cursor tooltip on mobile devices 2019-09-11 11:49:09 +07:00
Integral Team
531a8ece25 deploy 2019-09-11 11:40:13 +07:00
Integral Team
a837614e4c deploy 2019-09-11 11:25:01 +07:00
Integral Team
952ba32c1d deploy 2019-09-11 11:24:21 +07:00
Integral Team
9c75f3e7c9 deploy 2019-09-11 11:23:41 +07:00
Integral Team
e75fb2fb86 deploy 2019-09-11 11:21:08 +07:00
Integral Team
99d287333c removed deploy test comment 2019-09-11 11:17:07 +07:00
Integral Team
7b7377f7ce deploy 2019-09-11 11:11:52 +07:00
Integral Team
1e57cf01ae deploy 2019-09-11 11:03:21 +07:00
Integral Team
3ed2e28161 removed styled from deps 2019-09-11 10:58:06 +07:00
Integral Team
5065894894 deploy 2019-09-11 10:55:20 +07:00
Integral Team
dffe378c0d deploy 2019-09-11 10:49:11 +07:00
Integral Team
793c6440ca deploy 2019-09-11 10:44:24 +07:00
Integral Team
30f10a7da9 test deploy 2019-09-11 10:42:57 +07:00
Integral Team
15486043d3 deploy test 2019-09-11 10:41:33 +07:00
Integral Team
0d5f8f273f moved back old packagejson dep 2019-09-11 10:27:46 +07:00
Integral Team
76affc56b5 fixed mobile spinning on zoom and vulnerabilities 2019-09-11 10:18:51 +07:00
muerwre
dac68e7445 cleanup 2019-07-24 14:32:16 +07:00
muerwre
a8df7c7c9e checking cache control 2019-07-24 13:31:08 +07:00
muerwre
745d4f89c7 checking cache control 2019-07-24 13:29:49 +07:00
muerwre
2afa5c856d checking cache control 2019-07-24 13:27:55 +07:00
muerwre
293376ce03 resized logo 2019-07-24 13:16:58 +07:00
muerwre
3713fa6909 fixed logo 2019-07-24 13:09:50 +07:00
muerwre
da67e10cd2 added prokatimsya logo 2019-07-24 13:07:32 +07:00
muerwre
fcea89b347 added bug to todo 2019-05-24 17:59:38 +07:00
muerwre
a42f839d1f added satellite map 2019-05-17 16:18:02 +07:00
muerwre
9edef64f97 fixed flex shrink 2019-03-29 17:37:38 +07:00
muerwre
8a23277311 changed row corner 2019-03-29 17:33:56 +07:00
muerwre
c1dfa24f1b better stars display 2019-03-29 17:31:27 +07:00
muerwre
d79eed6d70 show stars to everyone at mine tab 2019-03-29 17:29:10 +07:00
muerwre
644a15b782 did nothing 2019-03-29 17:26:37 +07:00
muerwre
6714721adf stars at route-row-view 2019-03-29 17:22:34 +07:00
muerwre
8dc3969906 description color white 2019-03-29 13:08:16 +07:00
muerwre
4d9456610f fixed backend filtering 2019-03-29 12:24:09 +07:00
muerwre
c8b233bcb7 changed title dialog styles 2019-03-29 12:11:27 +07:00
muerwre
27eee16e77
Merge pull request #14 from muerwre/feature/title-dialog
set min height for dialog
2019-03-29 12:07:42 +07:00
muerwre
2cf83e88ed set min height for dialog 2019-03-29 12:06:42 +07:00
muerwre
b9cdaed8c9
Merge pull request #13 from muerwre/feature/title-dialog
Feature/title dialog
2019-03-29 11:35:13 +07:00
muerwre
8f60a5efd6 complete setting and editing description 2019-03-29 11:31:04 +07:00
muerwre
c040e33a8a frontend is_starred and description support 2019-03-29 10:54:20 +07:00
muerwre
a4b620471a conditional dialog rendering 2019-03-29 10:26:26 +07:00
muerwre
41f40a783f handling empty text 2019-03-29 10:24:29 +07:00
muerwre
3a874583bc sliding up title dialog 2019-03-28 14:12:00 +07:00
muerwre
ea688c363b short cleanup 2019-03-28 12:09:09 +07:00
muerwre
f88bedd600 Cleared up InteractivePoly 2019-03-27 17:05:35 +07:00
muerwre
aca177aa6d removed role console.log 2019-03-21 18:27:56 +07:00
muerwre
fd7404e565 starred premoderation 2019-03-21 18:27:12 +07:00
muerwre
d2beab0976 fixed css-loader hashing 2019-03-21 18:19:40 +07:00
muerwre
56f5c66c79 fixed css-loader 2019-03-21 18:17:22 +07:00
muerwre
baa41f707d starred content 2019-03-21 17:58:38 +07:00
muerwre
a920217959 clearing search string on search tab change 2019-03-21 16:21:16 +07:00
muerwre
53f717651e Merge remote-tracking branch 'origin/master' 2019-03-21 16:16:24 +07:00
muerwre
198fc3579e fixed overwriting of deleted route 2019-03-21 16:16:17 +07:00
216 changed files with 55125 additions and 34825 deletions

View file

@ -1,13 +0,0 @@
{
"presets": ["env", "react","stage-2"],
"plugins": [
"ramda",
"lodash",
"react-hot-loader/babel",
"lodash",
["transform-runtime", {
"polyfill": false,
"regenerator": true
}]
]
}

36
.drone.yml Normal file
View file

@ -0,0 +1,36 @@
kind: pipeline
name: build
type: docker
platform:
os: linux
arch: amd64
steps:
- name: build-master
image: plugins/docker
when:
branch:
- master
environment:
REACT_APP_PUBLIC_PATH: https://map.vault48.org/
REACT_APP_API_ADDR: https://backend-map.vault48.org
REACT_APP_OSRM_URL: https://osrm.vault48.org/route/v1
REACT_APP_OSRM_PROFILE: bike
settings:
dockerfile: docker/www/Dockerfile
build_args_from_env:
- REACT_APP_PUBLIC_PATH
- REACT_APP_API_ADDR
- REACT_APP_OSRM_URL
- REACT_APP_OSRM_PROFILE
tag:
- ${DRONE_BRANCH}
username:
from_secret: global_docker_login
password:
from_secret: global_docker_password
registry:
from_secret: global_docker_registry
repo:
from_secret: docker_repo

4
.env.example Normal file
View file

@ -0,0 +1,4 @@
REACT_APP_PUBLIC_PATH = https://map.vault48.org/
REACT_APP_API_ADDR = https://backend.map.vault48.org
REACT_APP_OSRM_URL = https://vault48.org:5001/route/v1
REACT_APP_OSRM_PROFILE = bike

View file

@ -1,71 +0,0 @@
{
"extends": "airbnb",
"parser": "babel-eslint",
"plugins": [
"react",
"jsx-a11y",
"import"
],
"rules": {
"quotes": 1,
"comma-dangle": 0,
"no-restricted-syntax": 1,
"new-cap": 1,
"no-continue": 1,
"no-underscore-dangle": 0,
"global-require": 1,
"react/no-multi-comp": 1,
"react/jsx-filename-extension": 0,
"import/no-unresolved": 1,
"import/prefer-default-export": 0,
"import/extensions": 1,
"no-return-assign": 1,
"max-len": 1,
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/interactive-supports-focus": 0,
"arrow-parens": 0,
"jsx-a11y/no-autofocus": 0,
"react/jsx-closing-tag-location": 0,
"prefer-promise-reject-errors": 0,
"jsx-a11y/mouse-events-have-key-events": 0,
"camelcase": 0,
"no-trailing-spaces": 0,
},
"globals": {
"document": false,
"window": false,
"HTMLInputElement": false,
"HTMLDivElement": false,
"Headers": false,
"FormData": false,
"WebSocket": true,
"Element": true,
"localStorage": true,
},
"env": {},
"settings": {
"import/resolver": {
"webpack": {
"webpack.config.js": {
"resolve": {
"alias": {
"$components": "src/components",
"$containers": "src/containers",
"$constants": "src/constants",
"$sprites": "src/sprites",
"$config": "config",
"$styles": "src/styles",
"$redux": "src/redux",
"$utils": "src/utils",
"$modules": "src/modules"
},
"extensions": [".js", ".jsx", ".scss"],
"modules": ["config", "src", "node_modules"]
}
}
}
}
}
}

View file

@ -1,23 +0,0 @@
[options]
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='^$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
[include]
public
../node_modules/
[libs]
flow-libs/

6
.gitignore vendored
View file

@ -14,9 +14,9 @@ yarn-error.log
/osrm/pbf
/osrm/pbf/*
/config/frontend.js
/config/backend.js
# Bundle
*.js.map
stats.json
.env
build

5
.prettierrc Normal file
View file

@ -0,0 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5"
}

View file

@ -35,10 +35,3 @@ Configs are placed in ```./config/frontend.js```
For development launch ```npm start``` and visit ```http://localhost:8000/```
For production launch ```npm build```, the output will be placed at ```./dist``` folder, you should configure your http server to serve index html from that folder.
### Backend
Take a look at ```./config/backend.js```. By default your api server will be spawned at ```http://localhost:3000/```
For development launch ```npm serve-dev```, it will launch dev server, reloading on every code update
For production launch ```npm serve``` and make it running in background by any desirable way, such as ```forever```

View file

@ -1,59 +0,0 @@
/*
https://habr.com/company/ruvds/blog/321104/
*/
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const lessMiddleware = require('less-middleware');
const logger = require('morgan');
const bodyParser = require('body-parser');
const routeRouter = require('./routes/route');
const authRouter = require('./routes/auth');
const db = require('./config/db');
const app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(lessMiddleware(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, PATCH');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use(bodyParser.json());
app.use(express.json());
// app.use('/', indexRouter);
// app.use('/users', usersRouter);
app.use('/auth', authRouter);
app.use('/route', routeRouter);
// catch 404 and forward to error handler
app.use((req, res, next) => {
next(createError(404));
});
// error handler
app.use((err, req, res, next) => {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;

View file

@ -1,34 +0,0 @@
#!/usr/bin/env node
const { CONFIG } = require('../../config/backend');
const app = require('../app');
const fs = require('fs');
const http = require('http');
const https = require('https');
if (CONFIG.HTTP.ENABLED) {
const httpPort = CONFIG.HTTP.PORT;
// app.set('port', httpPort);
const httpServer = http.createServer(app);
httpServer.listen(httpPort);
httpServer.on('error', console.log);
httpServer.on('listening', console.log);
}
if (CONFIG.HTTPS.ENABLED) {
const sslPort = CONFIG.HTTPS.PORT;
// app.set('port', sslPort);
const key = fs.readFileSync(CONFIG.HTTPS.PRIVATE_KEY, 'utf8');
const cert = fs.readFileSync(CONFIG.HTTPS.CERTIFICATE, 'utf8');
const ca = fs.readFileSync(CONFIG.HTTPS.CA, 'utf8');
const sslServer = https.createServer({ key, cert, ca }, app);
sslServer.listen(sslPort);
sslServer.on('error', console.log);
sslServer.on('listening', console.log);
}

View file

@ -1,18 +0,0 @@
const { CONFIG } = require('../../config/backend');
const {
DB: {
USER, PASSWORD, HOSTNAME, PORT, DATABASE
}
} = CONFIG;
const mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
mongoose.connect(`mongodb://${USER}:${PASSWORD}@${HOSTNAME}:${PORT}/${DATABASE}`, { });
const database = mongoose.connection;
database.on('error', (err) => { console.error(`Database Connection Error: ${err}`); process.exit(2); });
database.on('connected', () => { console.info('Succesfully connected to MongoDB Database'); });
console.log(`DB: mongodb://${USER}:${PASSWORD}@${HOSTNAME}:${PORT}/${DATABASE}`);

View file

@ -1,11 +0,0 @@
// export const OAUTH_FAILED_TITLE = 'Ошибка авторизации';
module.exports.STRINGS = {
OAUTH: {
ERROR_TITLE: 'Ошибка авторизации',
ERROR_HEADING: 'Ошибка',
ERROR_TEXT: 'Авторизация не удалась, попробуйте еще раз',
ERROR_CLOSE_BUTTON: 'ЗАКРЫТЬ',
SUCCESS_TITLE: 'Успешно!',
},
};

View file

@ -1,24 +0,0 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const RouteSchema = new Schema(
{
_id: { type: String, required: true },
title: { type: String, default: '' },
// address: { type: String, required: true },
version: { type: Number, default: 2 },
route: { type: Array, default: [] },
stickers: { type: Array, default: [] },
owner: { type: Schema.Types.ObjectId, ref: 'User' },
distance: { type: Number, default: 0 },
is_public: { type: Boolean, default: false },
is_deleted: { type: Boolean, default: false },
created_at: { type: Date, default: Date.now() },
updated_at: { type: Date, default: Date.now() },
logo: { type: String, default: 'DEFAULT' },
provider: { type: String, default: 'DEFAULT' },
},
);
module.exports.RouteSchema = RouteSchema;

View file

@ -1,27 +0,0 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
// Schemas
const UserSchema = new Schema(
{
_id: { type: String, required: true },
role: {
type: String,
required: true,
enum: ['admin', 'guest', 'user', 'vk'],
},
token: { type: String, required: true },
created_at: { type: Date, required: true, default: Date.now },
first_name: { type: String },
last_name: { type: String },
photo: { type: String },
version: { type: Number, default: 2 },
routes: [{ type: Schema.Types.ObjectId, ref: 'Route' }]
},
{
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
}
);
module.exports.UserSchema = UserSchema;

View file

@ -1,8 +0,0 @@
const mongoose = require('mongoose');
const { UserSchema } = require('./User');
const { RouteSchema } = require('./Route');
module.exports.User = mongoose.model('User', UserSchema);
module.exports.Route = mongoose.model('Route', RouteSchema);

View file

@ -1 +0,0 @@
body{background:#6d9dc8;font:14px "Lucida Grande",Helvetica,Arial,sans-serif;color:white;margin:0;font-weight:300}div#message{padding:100px;display:flex;align-items:center;justify-content:center;flex-direction:column;height:100vh;width:100vw;margin:0;position:absolute;box-sizing:border-box;text-align:left}div.bg{background:white;color:#666666;padding:20px 40px;border-radius:4px}h1{margin-bottom:10px;text-transform:uppercase}button{height:32px;padding:0 24px;color:white;border:none;box-sizing:border-box;margin-top:40px;border-radius:3px;background:#6d9dc8;font-weight:bold;float:right;cursor:pointer}

View file

@ -1,47 +0,0 @@
body {
background: #6d9dc8;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
color: white;
margin: 0;
font-weight: 300;
}
div#message {
padding: 100px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100vh;
width: 100vw;
margin: 0;
position: absolute;
box-sizing: border-box;
text-align: left;
}
div.bg {
background: white;
color: #666666;
padding: 20px 40px;
border-radius: 4px;
}
h1 {
margin-bottom: 10px;
text-transform: uppercase;
}
button {
height: 32px;
padding: 0 24px;
color: white;
border: none;
box-sizing: border-box;
margin-top: 40px;
border-radius: 3px;
background: #6d9dc8;
font-weight: bold;
float: right;
cursor: pointer;
}

View file

@ -1 +0,0 @@
body{padding:50px;font:14px "Lucida Grande",Helvetica,Arial,sans-serif}a{color:#00B7FF}

View file

@ -1,8 +0,0 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

View file

@ -1,16 +0,0 @@
const express = require('express');
const guest = require('./auth/guest');
const list = require('./auth/list');
const check = require('./auth/check');
const vk = require('./auth/social/vk');
const iframe_vk = require('./auth/iframe/vk');
const router = express.Router();
router.get('/', check);
router.get('/list', list);
router.get('/guest', guest);
router.get('/social/vk', vk);
router.get('/iframe/vk', iframe_vk);
module.exports = router;

View file

@ -1,32 +0,0 @@
const { User } = require('../../models');
const { generateGuest, generateRandomUrl } = require('./guest');
module.exports = async (req, res) => {
const { id, token } = req.query;
const user = await User.findOne({ _id: id, token });
// .populate({
// path: 'routes',
// select: '_id title distance owner updated_at',
// options: {
// limit: 200,
// sort: { updated_at: -1 },
// }
// })
const random_url = await generateRandomUrl();
if (user) {
return res.send({
success: true, ...user.toObject(), id: user._id, random_url
});
}
const guest = await generateGuest();
const created = await User.create(guest).then(result => result.toObject());
return res.send({
success: false, error: 'user not found', error_code: 1231, ...created, id: created._id, random_url
});
};

View file

@ -1,52 +0,0 @@
const { genRandomSequence } = require('../../utils/gen');
const { User } = require('../../models');
const generateGuestToken = () => {
const _id = `guest:${genRandomSequence(16)}`;
return User.find({ _id }).then(user => {
if (user.length) return generateGuestToken();
return _id;
});
};
const generateUser = (_id, role = 'guest') => {
const token = `seq:${genRandomSequence(32)}`;
return { _id, token, role };
};
const saveUser = user => {
const model = new User({ ...user });
return model.save()
.then(() => model.toObject())
.catch(() => ({
...user,
success: false,
error: 'Error saving user model',
error_code: 1232,
}));
};
const generateRandomUrl = () => Promise.resolve(genRandomSequence(16));
const generateGuest = async () => {
const user = await generateGuestToken()
.then(generateUser);
const random_url = await generateRandomUrl();
return { ...user, random_url, first_name: '', last_name: '', photo: '' };
};
module.exports = async (req, res) => {
const user = await generateGuest().then(saveUser);
res.send({ success: true, ...user });
};
module.exports.generateUser = generateUser;
module.exports.generateGuest = generateGuest;
module.exports.generateGuestToken = generateGuestToken;
module.exports.generateRandomUrl = generateRandomUrl;

View file

@ -1,22 +0,0 @@
const { User } = require('../../../models');
const { CONFIG } = require('../../../../config/backend');
const md5 = require('js-md5');
const { generateRandomUrl } = require('../guest');
module.exports = async (req, res) => {
if (!CONFIG.SOCIAL.VK_IFRAME.ENABLED) return res.send({ success: false, error: 'Unsupported Method' });
const { query: { viewer_id, auth_key } } = req;
const checksum = md5(`${CONFIG.SOCIAL.VK_IFRAME.APP_ID}_${viewer_id}_${CONFIG.SOCIAL.VK_IFRAME.SECRET}`);
if (checksum !== auth_key) return res.send({ success: false, error: 'No such user (1)' });
const user = await User.findOne({ _id: `vk:${viewer_id}` }).populate('routes');
if (!user) return res.send({ success: false, error: 'No such user (2)' });
const random_url = await generateRandomUrl();
return res.send({ success: true, user: { ...user.toObject(), id: user._id, random_url } });
};

View file

@ -1,9 +0,0 @@
const { User } = require('../../models');
module.exports = (req, res) => User.find((err, articles) => {
if (!err) return res.send(articles);
res.statusCode = 500;
return res.send({ error: 'Server error' });
});

View file

@ -1,82 +0,0 @@
const { User } = require('../../../models');
const axios = require('axios');
const { generateUser } = require('../guest');
const { STRINGS } = require('../../../config/strings');
const { CONFIG } = require('../../../../config/backend');
const fetchUserData = async (req, res) => {
const { query: { code } } = req;
const host = req.get('host');
const proto = req.connection.encrypted ? 'https' : 'http';
const { data: { access_token, user_id } } = await axios.get(
'https://oauth.vk.com/access_token',
{
params: {
client_id: CONFIG.SOCIAL.VK.APP_ID,
client_secret: CONFIG.SOCIAL.VK.SECRET,
code,
redirect_uri: `${proto}://${host}/auth/social/vk`,
}
}
).catch(() => {
return res.render('social/vk_error', {
title: STRINGS.OAUTH.ERROR_TITLE,
heading: STRINGS.OAUTH.ERROR_HEADING,
reason: STRINGS.OAUTH.ERROR_TEXT,
button: STRINGS.OAUTH.ERROR_CLOSE_BUTTON,
});
});
const { data } = await axios.get(
'https://api.vk.com/method/users.get',
{
params: {
user_id,
fields: 'photo',
v: '5.67',
access_token,
}
}
).catch(() => {
return res.render('social/vk_error', {
title: STRINGS.OAUTH.ERROR_TITLE,
heading: STRINGS.OAUTH.ERROR_HEADING,
reason: STRINGS.OAUTH.ERROR_TEXT,
button: STRINGS.OAUTH.ERROR_CLOSE_BUTTON,
});
});
return data;
};
module.exports = async (req, res) => {
const { response } = await fetchUserData(req, res);
const {
first_name = '', last_name = '', photo = '', id = ''
} = response[0];
const newUser = await generateUser(`vk:${id}`, 'vk');
const name = `${first_name} ${last_name}`;
const user = {
...newUser, first_name, last_name, photo, name,
};
const auth = await User.findOne({ _id: user._id }).populate('routes');
if (auth) {
await auth.set({
first_name, last_name, name, photo
}).save();
res.render('social/success', { title: STRINGS.OAUTH.SUCCESS_TITLE, ...user, token: auth.token });
} else {
const created = await User.create(user, (err, result) => {
return result.toObject();
});
res.render('social/success', { title: STRINGS.OAUTH.SUCCESS_TITLE, ...user, ...created });
}
};

View file

@ -1,9 +0,0 @@
const express = require('express');
const router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;

View file

@ -1,16 +0,0 @@
const express = require('express');
const post = require('./route/post');
const get = require('./route/get');
const list = require('./route/list');
const drop = require('./route/drop');
const patch = require('./route/patch');
const router = express.Router();
router.post('/', post);
router.get('/', get);
router.patch('/', patch);
router.delete('/', drop);
router.get('/list', list);
module.exports = router;

View file

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

View file

@ -1,26 +0,0 @@
const { generateRandomUrl } = require('../auth/guest');
const { Route } = require('../../models');
module.exports = async (req, res) => {
const { query: { name } } = req;
if (!name) return res.send({ success: false, mode: 'not_found_1' });
const exists = await Route.findOne({ _id: name, is_deleted: false }).populate('owner', '_id');
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,
...data,
address: exists._id,
owner: {
...data.owner,
id: data.owner._id,
},
random_url,
});
};

View file

@ -1,87 +0,0 @@
const { Route, User } = require('../../models');
module.exports = async (req, res) => {
const {
query: {
id, token, title, distance, author, step = 20, shift = 0,
}
} = req;
const user = await User.findOne({ _id: id, token });
let criteria = { is_deleted: false };
if (title) {
criteria = {
...criteria,
$or: [
{ title: new RegExp(title.trim(), 'ig') },
{ _id: new RegExp(title.trim(), 'ig') },
],
};
}
if (!author || !user || (user._id !== author)) {
criteria = {
...criteria,
is_public: true,
};
}
let list = await Route.find(
{
...criteria,
},
'_id title distance owner updated_at is_public is_deleted',
{
limit: 9000,
sort: { updated_at: -1 },
}
).populate('owner', '_id');
list = list.filter(item => !author || (item.owner && item.owner._id === author));
let limits = list.reduce(({ min, max }, { distance: dist }) => ({
min: Math.ceil(Math.min(dist, min) / 25) * 25,
max: Math.ceil(Math.max(dist, max) / 25) * 25,
}), { min: 999999, max: 0 });
const minDist = parseInt(distance[0], 10);
const maxDist = parseInt(distance[1], 10) === 200 ? 99999 : parseInt(distance[1], 10);
// const maxDist = parseInt(distance[1], 10) > parseInt(distance[0], 10)
// ? parseInt(distance[1], 10)
// : 10000;
if (distance && distance.length === 2) {
list = list.filter(item => (
item.distance >= minDist &&
item.distance <= maxDist
));
}
const limit = list.length;
if (step) {
list = list.slice(parseInt(shift, 10), (parseInt(shift, 10) + parseInt(step, 10)));
}
if (list.length === 0) {
limits = { min: 0, max: 0 };
} else if (limits.max === 0) {
limits = { min: 0, max: 0 };
} else if (limits.min === limits.max) {
limits = { min: limits.max - 25, max: limits.max };
} else if (limits.max > 200) {
limits = { min: limits.min, max: 200 };
}
res.send({
success: true,
list,
limit,
step: parseInt(step, 10),
shift: parseInt(shift, 10),
...limits,
});
};

View file

@ -1,24 +0,0 @@
const { User, Route } = require('../../models');
const { parseString } = require('../../utils/parse');
module.exports = async (req, res) => {
const { body, body: { id, token, address } } = req;
const owner = await User.findOne({ _id: id, token }).populate('routes');
if (!owner) return res.send({ success: false, reason: 'unauthorized' });
const title = parseString(body.title, 64);
const is_public = !!body.is_public;
const exists = await Route.findOne({ _id: address, is_deleted: false }).populate('owner', '_id');
if (!exists) return res.send({ success: false, mode: 'not_exists' });
if (exists && exists.owner._id !== id) return res.send({ success: false, mode: 'not_yours' });
exists.set({ title, is_public }).save();
return res.send({ success: true, ...exists });
};

View file

@ -1,50 +0,0 @@
const { User, Route } = require('../../models');
const { parseRoute, parseStickers, parseString, parseNumber, parseAddress } = require('../../utils/parse');
module.exports = async (req, res) => {
const { body, body: { id, token, force } } = req;
const owner = await User.findOne({ _id: id, token }).populate('routes');
if (!owner) return res.send({ success: false, reason: 'unauthorized', id, token });
const title = parseString(body.title, 64);
const address = parseAddress(body.address, 32)
const route = parseRoute(body.route);
const stickers = parseStickers(body.stickers);
const logo = parseString(body.logo, 16);
const provider = parseString(body.provider, 16) || 'DEFAULT';
const distance = parseNumber(body.distance, 0, 1000);
const is_public = !!body.is_public;
if ((!route || route.length <= 0) && (!stickers || stickers.length <= 0)) {
return res.send({ success: false, mode: 'empty' });
}
const exists = await Route.findOne({ _id: address, is_deleted: false }).populate('owner', '_id');
if (exists && exists.owner._id !== id) return res.send({ success: false, mode: 'exists' });
if (exists && !force) return res.send({ success: false, mode: 'overwriting' });
if (exists) {
exists.set({
title, route, stickers, logo, distance, updated_at: Date.now(), provider, is_public,
}).save();
return res.send({
success: true, title, address, route, stickers, mode: 'overwrited', is_public,
});
}
const created = await Route.create({
_id: address, title, route, stickers, owner, logo, distance, provider, is_public,
});
await owner.routes.push(created);
await owner.save();
return res.send({
success: true, title, address, route, stickers, provider, is_public,
});
};

View file

@ -1,9 +0,0 @@
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;

View file

@ -1,182 +0,0 @@
#!/usr/bin/env node
const mysql = require('mysql');
const { REPLACEMENT, MAPS } = require('./stickersReplacement');
const db = require('../config/db');
let total_dist = 0;
const { User, Route } = require('../models');
const con = mysql.createConnection({
host: 'vault48.org',
user: 'macos_exporter',
password: 'password',
});
const tryJSON = data => {
try {
return JSON.parse(data);
} catch (e) {
return {};
}
};
function deg2rad(deg) {
return (deg * Math.PI) / 180;
}
const findDistance = (t1, n1, t2, n2) => {
// convert coordinates to radians
const lat1 = deg2rad(t1);
const lon1 = deg2rad(n1);
const lat2 = deg2rad(t2);
const lon2 = deg2rad(n2);
// find the differences between the coordinates
const dlat = lat2 - lat1;
const dlon = lon2 - lon1;
// here's the heavy lifting
const a = (Math.sin(dlat / 2) ** 2) +
(Math.cos(lat1) * Math.cos(lat2) * (Math.sin(dlon / 2) ** 2));
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // great circle distance in radians
// const dm = c * 3961; // great circle distance in miles
const dk = c * 6373; // great circle distance in km
// round the results down to the nearest 1/1000
// const mi = round(dm);
return (Math.round(dk * 1000) / 1000);
};
const calcPolyDistance = route => {
let summ = 0;
for (let i = 1; i < route.length; i += 1) {
summ += findDistance(route[i - 1].lat, route[i - 1].lng, route[i].lat, route[i].lng);
}
total_dist += summ;
return parseFloat(Number(summ).toFixed(2));
};
const stickerAngleParser = angle => parseFloat(((Number(angle) % Math.PI) + Math.PI).toFixed(2));
const stickerStyleParser = style => (REPLACEMENT[style]
? { ...REPLACEMENT[style] }
: { set: 'base', sticker: 'chicken' });
const stickersParser = stickers => (
stickers.map(({
ang, latlng, style, text
}) => ({
angle: stickerAngleParser(ang), // todo: change it!
latlng,
text,
...stickerStyleParser(style),
}))
);
const pointParser = points => (
points.map(({ latlngs, text }) => ({
text,
latlng: latlngs[0],
set: 'base',
sticker: 'empty',
angle: 2.2,
}))
);
const mapStyleParser = style => (MAPS[style]
? MAPS[style]
: 'DEFAULT'
);
const run = async () => {
await con.connect(err => { if (err) throw err; });
await con.query('use neu_map');
const users = await new Promise(resolve => con.query('SELECT * from tokens where role = "vk"', (err, rows) => {
resolve(rows.map(({
login, token, created, role, data
}) => {
const info = tryJSON(data);
const created_at = new Date(created * 1000);
return {
_id: login,
token,
created_at,
updated_at: created_at,
name: info.name,
first_name: (info.name && info.name.split(' ')[0]) || '',
last_name: (info.name && info.name.split(' ')[1]) || '',
role,
photo: info.photo,
version: 1,
};
}));
}));
const routes = await new Promise(resolve => con.query('SELECT routes.*, tokens.login as login from routes LEFT JOIN tokens ON tokens.id = routes.id WHERE tokens.login IS NOT NULL AND tokens.role="vk"', (err, rows) => {
if (err) console.log('error', err);
resolve(rows.map(({
data, created, name, login
}) => {
const {
map_style, route, stickers, points, logo
} = tryJSON(data);
const created_at = new Date(created * 1000);
return {
_id: name,
owner: { _id: login },
created_at,
updated_at: created_at,
provider: mapStyleParser(map_style),
route: route.map(({ lat, lng }) => ({ lat: Number(lat), lng: Number(lng) })),
stickers: [
...stickersParser(stickers),
...pointParser(points)
],
logo: logo === 'default' ? 'nvs' : logo,
title: '',
version: 1,
distance: calcPolyDistance(route),
is_public: true,
};
}));
}));
console.log('ended, got users:', users.length);
console.log('ended, got routes:', routes.length);
const deletedUsers = await User.deleteMany({
// version: 1
});
const deletedRoutes = await Route.deleteMany({
// version: 1
});
console.log("dropped users", deletedUsers);
console.log("dropped routes", deletedRoutes);
console.log('inserting users');
const addedUsers = (await Promise.all(users.map(user => User.create(user))))
.reduce((obj, user) => ({ ...obj, [user._id]: user }), {});
console.log('inserting routes');
const addedRoutes = await (await Promise.all(routes.map(route => Route.create({
...route,
owner: addedUsers[route.owner._id],
}))));
await Promise.all(addedRoutes.map(async (route) => {
await addedUsers[route.owner._id].routes.push(route);
}));
await Promise.all(addedRoutes.map(route => new Promise(resolve => (
addedUsers[route.owner._id].save(() => resolve())
))));
console.log('ok');
};
run();

View file

@ -1,44 +0,0 @@
module.exports.REPLACEMENT = {
0: { set: 'base', sticker: 'men' },
1: { set: 'base', sticker: 'square' },
2: { set: 'base', sticker: 'bridge' },
3: { set: 'base', sticker: 'ikea' },
4: { set: 'base', sticker: 'bugr' },
5: { set: 'base', sticker: 'monum' },
6: { set: 'base', sticker: 'opera' },
7: { set: 'base', sticker: 'forest' },
8: { set: 'base', sticker: 'empty' },
9: { set: 'base', sticker: 'empty' },
10: { set: 'base', sticker: 'road' },
11: { set: 'base', sticker: 'chicken' },
12: { set: 'base', sticker: 'camp' },
13: { set: 'base', sticker: 'fastfood' },
14: { set: 'base', sticker: 'beer' },
15: { set: 'base', sticker: 'pancake' },
16: { set: 'base', sticker: 'rocks' },
17: { set: 'real', sticker: 'swamp' },
18: { set: 'real', sticker: 'skull' },
19: { set: 'base', sticker: 'crap' },
20: { set: 'pin', sticker: 'start' },
21: { set: 'pin', sticker: 'p1' },
22: { set: 'pin', sticker: 'p2' },
23: { set: 'pin', sticker: 'p3' },
24: { set: 'pin', sticker: 'p4' },
25: { set: 'pin', sticker: 'p5' },
26: { set: 'pin', sticker: 'p6' },
27: { set: 'pin', sticker: 'finish' },
29: { set: 'pin', sticker: 'danger' },
30: { set: 'pin', sticker: 'question' },
};
module.exports.MAPS = {
'watercolor': 'BLANK',
'darq': 'BLANK',
'default': 'DGIS',
'osm': 'DEFAULT',
'hot': 'HOT',
'blank': 'BLANK',
'sat': 'DEFAULT',
'ymap': 'DEFAULT',
'ysat': 'DEFAULT',
};

View file

@ -1,10 +0,0 @@
module.exports.genRandomSequence = (length = 16) => {
let sequence = '';
const symbols = 'ABCDEFGHIJKLMOPQRSTUVXYZabcdefghijgmlopqrstuvxyz01234567890'
for (let i = 0; i < length; i += 1) {
sequence += symbols[parseInt(Math.random() * (symbols.length - 1), 10)];
}
return sequence;
};

View file

@ -1,30 +0,0 @@
module.exports.parseRoute = route => route.filter(el => (
Object.keys(el).length === 2
&& el.lat
&& parseInt(el.lat, 10) > 0
&& parseInt(el.lat, 10) < 1000
&& el.lng
&& parseInt(el.lng, 10) > 0
&& parseInt(el.lng, 10) < 1000
));
module.exports.parseStickers = stickers => stickers.filter(el => (
Object.keys(el).length === 5
&& el.latlng
&& Object.keys(el.latlng).length === 2
&& el.latlng.lat
&& parseInt(el.latlng.lat, 10) > 0
&& parseInt(el.latlng.lat, 10) < 1000
&& el.latlng.lng
&& parseInt(el.latlng.lng, 10) > 0
&& parseInt(el.latlng.lng, 10) < 1000
));
// .map(el => ((el.text && String(el.text).substr(0, 100)) || ''));
const parseString = (value, size) => (value && String(value).substr(0, size)) || '';
module.exports.parseNumber = (value, min, max) => (value && Number(value) && Math.min(max, Math.max(min, value))) || 0;
module.exports.parseString = parseString;
module.exports.parseAddress = (value, size) => (
parseString(value, size).replace(/[^A-Za-z\-_0-9]/ig, '_').replace(/_{2,}/ig, '_')
);

View file

@ -1,6 +0,0 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}

View file

@ -1,5 +0,0 @@
extends layout
block content
h1= title
p Welcome to #{title}

View file

@ -1,7 +0,0 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content

View file

@ -1,17 +0,0 @@
doctype html
html
body
script.
window.opener.postMessage({
type: 'oauth_login',
user: {
id: '#{_id}',
token: '#{token}',
name: '#{name}',
photo: '#{photo}',
role: '#{role}',
}
}, '*');
window.close();

View file

@ -1,13 +0,0 @@
doctype html
html
head
title Ошибка авторизации
link(rel='stylesheet', href='/stylesheets/social/vk.css')
body
block content
div#message
div.bg
h1=heading
div=reason
button(onclick='window.close()')=button

View file

@ -1,27 +0,0 @@
module.exports.CONFIG = {
HTTPS: {
ENABLED: false,
PORT: 3000,
PRIVATE_KEY: '/etc/letsencrypt/live/HOSTNAME.org/privkey.pem',
CERTIFICATE: '/etc/letsencrypt/live/HOSTNAME.org/cert.pem',
CA: '/etc/letsencrypt/live/hostname/chain.pem',
},
HTTP: {
ENABLED: false,
PORT: 3000,
},
DB: { // mongo db config
USER: 'user',
PASSWORD: 'password',
HOSTNAME: 'HOSTNAME.org',
PORT: 27017,
DATABASE: 'map',
},
SOCIAL: {
VK: {
ENABLED: false,
SECRET: 'YOUR_VK_SECRET', // secret token
APP_ID: 0, // numeric
}
}
};

View file

@ -1,25 +0,0 @@
import { DEFAULT_PROVIDER, PROVIDERS } from '$constants/providers';
import { LatLngLiteral } from 'leaflet';
const API_ADDR = 'https://HOSTNAME.org:3000';
const OSRM_URL = 'https://HOSTNAME.org:5001/route/v1';
const OSRM_PROFILE = 'bike';
const OSRM_TEST_URL = ([south_west, north_east]: [LatLngLiteral, LatLngLiteral]) => (
`${OSRM_URL}/${OSRM_PROFILE}/${Object.values(south_west).join(',')};${Object.values(north_east).join(',')}`
);
export const CLIENT = {
OSRM_URL,
API_ADDR,
OSRM_TEST_URL,
OSRM_PROFILE,
STROKE_WIDTH: 5,
};
export const COLORS = {
PATH_COLOR: ['#ff7700', '#ff3344'],
};
export const PROVIDER = PROVIDERS[DEFAULT_PROVIDER];
export const MOBILE_BREAKPOINT = 768;

48
craco.config.js Normal file
View file

@ -0,0 +1,48 @@
const CracoAlias = require('craco-alias');
// const fastRefreshCracoPlugin = require('craco-fast-refresh');
module.exports = {
webpack: {
alias: {
'~': `src`,
},
output: {
publicPath: '/',
},
},
eslint: {
enable: false,
mode: 'file',
},
jest: {
setupTestFrameworkScriptFile: '<rootDir>/src/setupTests.js',
configure: {
moduleNameMapper: {
'^~/(.*)$': '<rootDir>/src/$1',
'^.+\\.scss$': 'identity-obj-proxy',
},
snapshotSerializers: ['enzyme-to-json/serializer'],
moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'jsx', 'node'],
verbose: true,
roots: ['<rootDir>/src'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
'^.+\\.ts?$': 'babel-jest',
'^.+\\.js?$': 'ts-jest',
'^.+\\.jsx?$': 'babel-jest',
},
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'node',
},
},
plugins: [
// { plugin: fastRefreshCracoPlugin },
{
plugin: CracoAlias,
options: {
source: 'tsconfig',
tsConfigPath: 'tsconfig.paths.json',
},
},
],
};

11
docker-compose.yml Normal file
View file

@ -0,0 +1,11 @@
version: '3'
services:
www:
restart: always
build:
context: .
dockerfile: docker/www/Dockerfile
ports:
- ${EXPOSE}:80
volumes:
- /etc/localtime:/etc/localtime:ro

28
docker/www/Dockerfile Normal file
View file

@ -0,0 +1,28 @@
# stage1 as builder
FROM node:erbium-alpine as builder
COPY package.json yarn.lock ./
RUN yarn
COPY . .
ARG REACT_APP_PUBLIC_PATH
ARG REACT_APP_API_ADDR
ARG REACT_APP_OSRM_URL
ARG REACT_APP_OSRM_PROFILE
ENV REACT_APP_PUBLIC_PATH $REACT_APP_PUBLIC_PATH
ENV REACT_APP_API_ADDR $REACT_APP_API_ADDR
ENV REACT_APP_OSRM_URL $REACT_APP_OSRM_URL
ENV REACT_APP_OSRM_PROFILE $REACT_APP_OSRM_PROFILE
RUN yarn build
FROM nginx:alpine
COPY docker/www/nginx.conf /etc/nginx/nginx.conf
RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /build /usr/share/nginx/html
EXPOSE ${EXPOSE} 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]

46
docker/www/nginx.conf Normal file
View file

@ -0,0 +1,46 @@
worker_processes 4;
events { worker_connections 1024; }
http {
server {
listen 80;
root /usr/share/nginx/html;
include /etc/nginx/mime.types;
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/xml application/javascript;
## All static files will be served directly.
location ~* ^.+\.(?:css|cur|js|jpe?g|gif|htc|ico|png|xml|otf|ttf|eot|woff|woff2|svg)$ {
access_log off;
expires 30d;
add_header Cache-Control public;
gzip_static on;
## No need to bleed constant updates. Send the all shebang in one
## fell swoop.
tcp_nodelay off;
## Set the OS file cache.
open_file_cache max=3000 inactive=120s;
open_file_cache_valid 45s;
open_file_cache_min_uses 2;
open_file_cache_errors off;
}
location / {
gzip_static on;
try_files $uri @index;
}
location @index {
add_header Cache-Control "no-store, no-cache, must-revalidate";
expires -1;
try_files /index.html =404;
}
}
}

35201
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,120 +5,70 @@
"description": "Beta Map",
"main": "./src/js/index.js",
"scripts": {
"start": "NODE_ENV=development webpack-dev-server --mode development --hot --open --inline --progress",
"serve": "node ./backend/bin/www",
"serve-dev": "nodemon ./backend/bin/www",
"build": "NODE_ENV=production webpack --env production --config=webpack.config.js --progress -p",
"profile": "webpack --json > stats.json"
"start": "craco start",
"build": "craco build",
"test": "craco test"
},
"author": "Grigory Chervoplyas",
"license": "ISC",
"keywords": [],
"devDependencies": {
"@babel/cli": "^7.0.0-rc.1",
"@babel/preset-env": "^7.0.0-rc.1",
"awesome-typescript-loader": "^5.2.1",
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-ramda": "^2.0.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-import-resolver-babel-module": "^4.0.0",
"eslint-import-resolver-webpack": "^0.9.0",
"eslint-loader": "^2.0.0",
"eslint-plugin-babel": "^5.0.0",
"eslint-plugin-flowtype": "^2.46.2",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.7.0",
"file-loader": "^1.1.11",
"flow-bin": "^0.73.0",
"html-webpack-plugin": "^3.2.0",
"identity-obj-proxy": "^3.0.0",
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^0.5.0",
"style-loader": "^0.21.0",
"ts-node": "^8.0.1",
"typescript": "^3.2.4",
"uglifyjs-webpack-plugin": "^1.3.0",
"webpack": "^4.6.0",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.1.14"
},
"dependencies": {
"@types/classnames": "^2.2.7",
"@types/leaflet": "^1.4.3",
"@types/node": "^11.9.0",
"@types/ramda": "^0.26.39",
"@types/react": "16.8.1",
"axios": "^0.18.0",
"babel-runtime": "^6.26.0",
"bluebird": "^3.5.3",
"body-parser": "^1.18.3",
"@types/react-dom": "^17.0.3",
"craco": "^0.0.3",
"craco-alias": "^2.2.0"
},
"dependencies": {
"axios": "^0.21.1",
"classnames": "^2.2.6",
"clean-webpack-plugin": "^0.1.9",
"cookie-parser": "~1.4.3",
"craco-fast-refresh": "^1.0.5",
"croppr": "^2.3.1",
"debug": "~2.6.9",
"express": "~4.16.0",
"eslint-loader": "^4.0.2",
"file-saver": "^2.0.0",
"history": "^4.7.2",
"http-errors": "~1.6.2",
"js-md5": "^0.7.3",
"leaflet": "^1.4.0",
"gpx-parser-builder": "^1.0.2",
"leaflet": "1.6.0",
"leaflet-editable": "^1.1.0",
"leaflet-editable-polyline": "muerwre/leaflet-editable-polyline#master",
"leaflet-geometryutil": "^0.9.0",
"leaflet-routing-machine": "muerwre/leaflet-routing-machine#no-osrm-text",
"leaflet-routing-machine": "^3.2.12",
"leaflet.markercluster": "^1.4.1",
"less": "^3.8.1",
"less-middleware": "~2.2.1",
"lodash": "^4.17.10",
"mongoose": "^5.3.14",
"morgan": "~1.9.0",
"mysql": "^2.16.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"node-sass": "^5.0.0",
"pt-sans-cyrillic": "0.0.4",
"pug": "2.0.0-beta11",
"raleway-cyrillic": "^4.0.2",
"rc-slider": "8.5.0",
"react": "16.8.1",
"react-dom": "16.8.1",
"react-hot-loader": "^4.1.1",
"ramda": "^0.26.1",
"rc-slider": "^9.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-infinite-scroller": "^1.2.2",
"react-rangeslider": "^2.2.0",
"react-redux": "^5.0.7",
"react-router": "^4.3.1",
"react-scripts": "^4.0.3",
"react-scrollbar": "^0.5.4",
"reactrangeslider": "^3.0.6",
"redux": "^4.0.1",
"redux-persist": "^5.10.0",
"redux-saga": "^0.16.2",
"reduxsauce": "^1.0.0",
"scrypt": "^6.0.3",
"styled-components": "^3.2.6",
"styled-theming": "^2.2.0",
"throttle-debounce": "^2.1.0",
"redux-saga": "^1.0.0",
"tt-react-custom-scrollbars": "^4.2.1-tt2",
"typeface-pt-sans": "0.0.54",
"typesafe-actions": "^3.0.0",
"webpack-git-hash": "^1.0.2"
"typescript": "^4.2.4",
"uuid": "^3.4.0"
},
"flow-coverage-report": {
"includeGlob": [
"src/**/*.js",
"src/**/*.jsx"
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"type": [
"text",
"html",
"json"
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"eslintConfig": {
"extends": [
"react-app"
]
}
}

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

5
public/images/arrow.svg Normal file
View file

@ -0,0 +1,5 @@
<svg width="48" height="48" xmlns="http://www.w3.org/2000/svg">
<g id="path-arrow" transform="scale(2) translate(5 -2)">
<path d="m 2.625,3.375 h 7.5 L 10.28125,1.609375 13.5,4.25 10.484375,6.921875 10.171875,5.15625 2.625,5.125 Z" fill="#ff3344" fillRule="evenodd" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 297 B

425
public/images/icon.svg Normal file
View file

@ -0,0 +1,425 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32">
<defs>
<g id="icon-router">
<rect x="0" y="0" width="32" height="32" fill="black" stroke="none" />
<g transform="translate(0 0)">
<path d="M12 5c-2.105-.003-3.587 1.238-4.3 2.354a6.454 6.454 0 0 0-.888 2.226l-.02.123v12.137h3v-11.814c.01-.045.098-.531.434-1.057.352-.549.722-.97 1.77-.969 1.073.001 1.547.446 1.945.983.367.493.47.894.49.966v12.45s-.003 1.326.624 2.723c.626 1.396 2.196 3.019 4.627 3.068 2.45.05 4.063-1.603 4.689-3.022.626-1.418.621-2.77.621-2.77v-10.949h-3v10.913s-.032.84-.365 1.595c-.334.756-.61 1.259-1.885 1.233-1.295-.027-1.617-.557-1.95-1.297a4.623 4.623 0 0 1-.36-1.526v-12.873l-.045-.177s-.256-1.071-1.035-2.121c-.78-1.05-2.273-2.193-4.352-2.196z" stroke="none" fill="white" />
<circle cx="8.5" cy="24" r="2" fill="none" stroke-width="2" stroke="white" />
<circle cx="23.5" cy="9" r="2" fill="none" stroke-width="2" stroke="white" />
</g>
</g>
<g id="icon-sticker">
<rect x="0" y="0" width="32" height="32" fill="none" stroke="none" />
<g transform="translate(-680 -285)">
<path d="M690.764 306.811l5.71 7.913 5.014-7.694z" stroke="none" fill="white" />
<path transform="matrix(.88332 0 0 .88132 81.246 35.954)" d="M702.328 307.792l-6.02-.026-6.02.026-2.986-5.226-3.033-5.2 3.033-5.2 2.987-5.227 6.02.026 6.02-.026 2.986 5.226 3.033 5.2-3.033 5.2z" fill="none" stroke="white" />
</g>
</g>
<g id="icon-poly">
<rect x="0" y="0" width="32" height="32" fill="black" stroke="none" />
<g>
<path d="m6 14l9 -7l-2 16l14 -10l-3 12" fill="none" stroke-linecap="butt" stroke-linejoin="round" stroke="white" />
<circle cx="5" cy="15" r="2" fill="none" stroke-width="2" stroke="white" />
<circle cx="16" cy="5" r="2" fill="none" stroke-width="2" stroke="white" />
<circle cx="12" cy="25" r="2" fill="none" stroke-width="2" stroke="white" />
<circle cx="28" cy="11" r="2" fill="none" stroke-width="2" stroke="white" />
<circle cx="24" cy="27" r="2" fill="none" stroke-width="2" stroke="white" />
</g>
</g>
<g id="icon-shooter">
<rect x="0" y="0" width="32" height="32" fill="black" stroke="none" />
<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" fill="white" stroke="none" />
</g>
</g>
<g id="icon-trash">
<g transform="matrix(4.6 0 0 4.6 -4 -4)" stroke="none">
<path fill="white" d="M2.783 3.626h2.923v2.923H2.783z" />
<path fill="white" d="M2.479 2.597h3.508v.748H2.479z" />
<path fill="white" d="M3.438 1.919h1.473v.865H3.438z" />
<path fill="black" d="M3.859 2.25h.631v.386h-.631z" />
<path fill="black" d="M3.134 3.906h.468v2.315h-.468z" />
<path fill="black" d="M-4.537 3.906h.444v2.315h-.444z" transform="scale(-1 1)" />
<path fill="black" d="M4.958 3.906h.444v2.315h-.444z" />
</g>
</g>
<g id="icon-save" stroke="none">
<path fill="black" d="M0 0h32v32H0z" />
<path fill="white" d="M6.844 8.459V24h18.312V11.375H14.031V8.459z" />
<rect fill="black" width="2.74" height="5.038" x="14.63" y="14.411" stroke-width="3.603" />
<path fill="black" d="M16.866 19.73l-4.315-.06-4.314.06 2.209-3.707 2.105-3.766 2.106 3.766z" transform="matrix(.45903 -.40628 .79506 .23456 -3.467 21.088)" />
</g>
<svg xmlns="http://www.w3.org/2000/svg" id="svg8" width="600" height="32">
<g id="g4853" transform="translate(-29.985 30.509)">
<rect id="rect4832" width="4.035" height="1.75" x="16.495" y="-28.417" fill="#fff" stroke-opacity=".941" stroke-width=".115" rx="0" ry="0" />
<path id="rect4834" fill="#fff" stroke-opacity=".941" stroke-width=".247" d="M15.535-28.417h5.616v.933h-5.616z" />
<path id="rect4836" fill="#fff" stroke-opacity=".941" stroke-width=".293" d="M16.495-29.706v1.29h.671v-.761h2.66v.76h.67v-1.29z" />
<path id="path4841" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width=".753" d="M16.886-27.49c.092.03-.101 1.171.111 2.499.213 1.328-.477 1.846-.477 1.846" />
<path id="path4843" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width=".753" d="M18.496-27.49c.093.03-.101 1.171.112 2.499.212 1.328-.478 1.846-.478 1.846" />
<path id="path4845" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width=".753" d="M20.107-27.49c.092.03-.102 1.171.111 2.499s-.477 1.846-.477 1.846" />
</g>
<g id="g4909" transform="translate(-.054 .888)">
<path id="rect4855" fill="#fff" stroke-opacity=".941" stroke-width=".811" d="M16.272-27.384h4.266l-.397 4.3h-3.538z" />
<path id="rect4857" fill="#fff" stroke-opacity=".941" stroke-width=".43" d="M15.809-28.939h5.192v.992h-5.192z" />
<path id="rect4879" fill="#fff" stroke-opacity=".941" stroke-width=".502" d="M17.246-29.945v1.123h.586v-.66h1.005v.66h.587v-1.123z" />
<g id="g4900">
<path id="path4885" fill-rule="evenodd" stroke-width=".265" d="M16.885-26.893l.187 3.508h.538l-.14-3.508z" />
<path id="path4887" fill-rule="evenodd" stroke-width=".265" d="M19.983-26.893l-.187 3.508h-.538l.14-3.508z" />
<path id="rect4889" stroke-opacity=".941" stroke-width=".821" d="M18.165-26.881h.538v3.473h-.538z" />
</g>
</g>
<g id="icon-save" stroke-opacity=".941" transform="translate(-64)">
<path id="rect4766" stroke-width=".265" d="M0 0h32v32H0z" />
<path id="rect4986" fill="#fff" stroke-width="2.491" d="M6.844 8.459V24h18.312V11.375H14.031V8.459z" />
<rect id="rect4991" width="2.74" height="5.038" x="14.63" y="14.411" stroke-width="3.603" rx="0" ry="0" />
<path id="path4993" stroke-width="3" d="M16.866 19.73l-4.315-.06-4.314.06 2.209-3.707 2.105-3.766 2.106 3.766z" transform="matrix(.45903 -.40628 .79506 .23456 -3.467 21.088)" />
</g>
<g id="g4982" stroke-opacity=".941" transform="translate(-32)">
<path id="rect4964" stroke-width=".265" d="M0 0h32v32H0z" />
<g id="g4980" transform="matrix(3.77953 0 0 3.77953 .002 -.002)">
<path id="rect4966" fill="#fff" stroke-width=".794" d="M2.783 3.626h2.923v2.923H2.783z" />
<path id="rect4968" fill="#fff" stroke-width=".794" d="M2.479 2.597h3.508v.748H2.479z" />
<path id="rect4970" fill="#fff" stroke-width=".794" d="M3.438 1.919h1.473v.865H3.438z" />
<path id="rect4972" stroke-width=".684" d="M3.859 2.25h.631v.386h-.631z" />
<path id="rect4974" stroke-width="1.442" d="M3.134 3.906h.468v2.315h-.468z" />
<path id="rect4976" stroke-width="1.405" d="M-4.537 3.906h.444v2.315h-.444z" transform="scale(-1 1)" />
<path id="rect4978" stroke-width="1.405" d="M4.958 3.906h.444v2.315h-.444z" />
</g>
</g>
<g id="g5188" transform="translate(-96)">
<path id="rect5180" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" />
<g id="g5322" fill="#fff" transform="translate(7.066 7.065) scale(.04494)">
<path id="path5267" stroke-width="445.017" d="M313.1 147.875c-6.057 0-11.962.654-17.661 1.871l-15.805-53.435 37.842-2.565a9.275 9.275 0 0 1 9.602 6.932 9.246 9.246 0 0 1-1.45 7.728 9.243 9.243 0 0 1-6.865 3.834l-9.126.652a6.5 6.5 0 0 0-6.02 6.947c.256 3.581 3.361 6.289 6.947 6.02l9.126-.652a22.21 22.21 0 0 0 16.493-9.21 22.21 22.21 0 0 0 3.484-18.566c-2.677-10.375-12.376-17.364-23.069-16.654l-45.936 3.114a6.5 6.5 0 0 0-5.793 8.329l6.405 21.656H145.477l-6.316-12H155.5a6.5 6.5 0 1 0 0-13h-47a6.5 6.5 0 1 0 0 13h16.086l9.542 18.349-18.836 33.485a84.026 84.026 0 0 0-30.792-5.834c-46.593 0-84.5 37.906-84.5 84.5s37.907 84.5 84.5 84.5c44.404 0 80.892-34.436 84.225-78h31.776a6.5 6.5 0 0 0 5.511-3.055l68.779-110.047 8.185 27.672c-31.758 12.162-54.376 42.945-54.376 78.93 0 46.594 37.907 84.5 84.5 84.5s84.5-37.906 84.5-84.5-37.907-84.501-84.5-84.501zm-228.6 156c-39.425 0-71.5-32.075-71.5-71.5s32.075-71.5 71.5-71.5c8.549 0 16.75 1.513 24.355 4.276l-31.482 55.968c-3.726 2.365-6.206 6.516-6.206 11.256 0 7.363 5.969 13.333 13.333 13.333 5.002 0 9.354-2.759 11.636-6.833h59.556c-3.297 36.388-33.959 65-71.192 65zm11.636-78a13.384 13.384 0 0 0-4.025-4.439l28.528-50.717c19.37 11.397 32.922 31.647 35.052 55.156zm72.589 0c-2.17-28.365-18.393-52.845-41.715-66.482l14.327-25.471 48.396 91.953zm32.258-6.538l-48.665-92.462h106.454zM313.1 303.875c-39.425 0-71.5-32.075-71.5-71.5 0-30.093 18.697-55.885 45.077-66.418l16.89 57.105a13.284 13.284 0 0 0-3.8 9.313c0 7.363 5.969 13.333 13.333 13.333s13.333-5.97 13.333-13.333c0-6.354-4.449-11.661-10.399-12.999l-16.895-57.123a71.624 71.624 0 0 1 13.962-1.378c39.425 0 71.5 32.075 71.5 71.5s-32.076 71.5-71.501 71.5z" />
</g>
</g>
<g id="icon-cycle">
<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-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)">
<path fill="none" stroke="#fff" stroke-width="2" d="M23.423 9.284c-6.673-4.127-12.713-4.175-19.269 0 .174 4.944-.354 13.294 9.635 17.855 10.323-4.9 9.545-13.305 9.634-17.855z" />
<path fill="#fff" stroke="none" d="M13.435 5.925v21.037s10.695-3.27 9.988-17.678c0 0-3.447-2.828-9.988-3.359z" />
<path fill="none" stroke-width="2" stroke="#fff" d="M5.773 15.118L15.7 6.19" />
<path fill="none" stroke-width="2" stroke="#fff" d="M6.276 20.333l7.778-7.778" />
<path fill="none" stroke-width="2" stroke="#fff" d="M8.574 23.691c.442-.53 5.612-5.612 5.612-5.612" />
<path fill="none" stroke-width="2" stroke="#fff" d="M11.932 25.636l2.343-2.342" />
</g>
</g>
<g id="icon-locate">
<path id="rect5819" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<circle id="path5839" cx="16" cy="16" r="5.127" fill="none" stroke="#fff" stroke-width="2.5" />
<path id="path5841" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="2.5" d="M16.087 6.854v4.242" />
<path id="path5843" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="2.5" d="M16.087 20.686v4.243" />
<path id="path5845" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="2.5" d="M20.948 16.134h4.243" />
<path id="path5847" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="2.5" d="M6.63 16.134h4.242" />
</g>
<g id="icon-shot-2" stroke="none" transform="scale(1.1) translate(-2 -1)">
<path id="rect5819" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<rect id="rect6617" width="21" height="13.89" x="6.25" y="9.833" fill="#fff" rx="2" ry="2" />
<path id="rect6619" fill="#fff" stroke-width="1.979" d="M13.812 7.009h6.282c.45 0 .749.528 1.147 1.039l3.096 3.974c.398.51-3.793 6.989-4.243 6.989h-6.282c-.45 0-4.889-6.747-4.475-7.24l3.19-3.802c.415-.493.836-.96 1.285-.96z" />
<circle id="path6621" cx="16.627" cy="16.311" r="6.158" fill="black" />
<circle id="circle6628" cx="16.627" cy="16.311" r="3.533" fill="#fff" />
</g>
<g id="icon-trash-2" stroke="none">
<g transform="translate(0 -3) scale(3.82445) scale(1.1) rotate(45 4 4)">
<rect id="rect4966" width="3.125" height="2.893" x="2.671" y="3.751" fill="#fff" rx=".36" ry=".403" />
<rect id="rect4968" width="3.663" height=".87" x="2.401" y="2.423" fill="#fff" rx=".435" ry=".435" />
<rect id="rect4970" width="2.924" height="1.306" x="2.771" y="1.698" fill="#fff" rx=".653" ry=".653" />
<rect id="rect4972" width="1.933" height=".523" x="3.266" y="2.114" fill="black" rx=".261" ry=".261" />
<g id="g4918" stroke-width=".523" transform="matrix(1.3497 0 0 1.3497 -1.613 -1.674)">
<rect id="rect4912" width="2.059" height=".425" x="5.622" y=".289" rx=".237" ry=".21" transform="rotate(45)" fill="black" />
<rect id="rect4914" width="2.047" height=".389" x="-.491" y="-6.803" rx=".237" ry=".195" transform="rotate(135)" fill="black" />
</g>
</g>
</g>
<g id="icon-logo-2" transform="scale(1.1) translate(-1 -2)">
<path id="rect5611" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" stroke="none" />
<g id="g5792" fill="none" stroke="#fff" stroke-width="2.37" transform="matrix(.84382 0 0 .84382 4.366 1.93)">
<path id="path5774" fill-rule="evenodd" d="M22.399 10.08c-5.964-3.689-11.363-3.732-17.222 0 .155 4.418-.316 11.882 8.61 15.957 9.228-4.38 8.532-11.89 8.612-15.957z" />
</g>
<g id="g8284" fill="#fff" stroke-width="3.052" transform="matrix(.40236 0 0 .40236 10.45 8.978)">
<path d="M22.399 10.08c-5.964-3.689-11.363-3.732-17.222 0 .155 4.418-.316 11.882 8.61 15.957 9.228-4.38 8.532-11.89 8.612-15.957z" stroke="none" />
</g>
</g>
<g id="icon-sticker-2" transform="scale(1.1) translate(-3 -2)" stroke="none">
<g id="g4867" transform="matrix(.94996 0 0 .94996 -.16 .083)">
<path id="path4859" fill="#fff" fill-rule="evenodd" stroke-width=".767" d="M13.47 22.847l3.045 5.275 3.12-5.405z" />
<circle id="path4863" cx="16.552" cy="14.98" r="8.712" fill="#fff" stroke-width="1.851" />
<circle id="circle4869" cx="16.552" cy="14.98" r="5" fill="black" />
</g>
</g>
<g id="icon-route-2" transform="scale(1.1) translate(32 -2)" stroke="none">
<g fill="white">
<circle id="path5642" cx="-24.357" cy="23.576" r="2.507" fill="#fff" stroke-width="4.363" />
<path id="path5644" fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="2.621" d="M-24.289 21.588V10.61c0-4.22 6.62-4.194 6.62 0V21.42c0 4.011 6.828 4.027 6.828 0v-10.98" />
<circle id="circle5646" cx="-10.93" cy="8.462" r="2.507" fill="#fff" stroke-width="4.363" />
</g>
</g>
<g id="icon-poly-2" transform="scale(1.1) translate(-3 -2)" stroke="none">
<g fill="white">
<path id="path6375" fill="none" fill-rule="evenodd" stroke="#fff" stroke-linejoin="round" stroke-width="2.387" d="M7.924 12.991l8.569-4.989-6.55 14.587 13.762-7.974-3.958 9.12" />
<circle id="path6363" cx="14.711" cy="-4.523" r="2.387" fill="#fff" stroke-width="5.304" transform="rotate(75)" />
<circle id="circle6367" cx="12.17" cy="-13.883" r="2.387" fill="#fff" stroke-width="5.304" transform="rotate(75)" />
<circle id="circle6371" cx="20.342" cy="-19.194" r="2.387" fill="#fff" stroke-width="5.304" transform="rotate(75)" />
<circle id="circle6373" cx="24.138" cy="-3.701" r="2.387" fill="#fff" stroke-width="5.304" transform="rotate(75)" />
<circle id="circle6774" cx="27.918" cy="-13.025" r="2.387" fill="#fff" stroke-width="5.304" transform="rotate(75)" />
</g>
</g>
<g id="icon-save-2" transform="scale(1.1) translate(-1 -2)" stroke="none">
<g fill="white">
<path id="rect4766" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path id="rect4986" fill="#fff" stroke-opacity=".941" stroke-width="2.477" d="M6.894 8.502v15.455h18.212V11.402H14.042v-2.9z" />
<path id="path7854" fill="none" fill-rule="evenodd" stroke="#000" stroke-width="2.735" d="M10.923 16.586l4.19 4.19 6.606-6.607" transform="scale(0.9) translate(2 2)" />
</g>
</g>
<g id="icon-trash-3">
<path id="rect4766" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" stroke="none" />
<path xmlns="http://www.w3.org/2000/svg" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z" fill="white" transform="translate(4 4)" stroke-width="1" />
</g>
<g id="icon-trash-4">
<path id="rect4766" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" stroke="none" />
<path xmlns="http://www.w3.org/2000/svg" d="M21 9l-9-7-2.59 2.02 7.87 7.87zm0 5.07l-1.63-1.27-.67.52 1.43 1.43zM3.41.86L2 2.27l4.22 4.22L3 9l9 7 2.1-1.63 1.42 1.42-3.53 2.75-7.37-5.73L3 14.07l9 7 4.95-3.85L20.73 21l1.41-1.41z" fill="white" transform="translate(4 4)" stroke-width="0" />
</g>
<g id="icon-logo-3" transform="scale(1.1) translate(-1 -2)">
<path id="rect5611" stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" stroke="none" />
<g id="g5792" fill="none" stroke="#fff" stroke-width="3" transform="matrix(.84382 0 0 .84382 5 2.53)">
<path id="path5774" fill-rule="evenodd" d="M22.399 10.08c-5.964-3.689-11.363-3.732-17.222 0 .155 4.418-.316 11.882 8.61 15.957 9.228-4.38 8.532-11.89 8.612-15.957z" />
</g>
</g>
<g id="icon-shot-3" stroke="none" transform="">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path xmlns="http://www.w3.org/2000/svg" d="M18 20H4V6h9V4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-9h-2v9zm-7.79-3.17l-1.96-2.36L5.5 18h11l-3.54-4.71zM20 4V1h-2v3h-3c.01.01 0 2 0 2h3v2.99c.01.01 2 0 2 0V6h3V4h-3z" fill="white" transform="translate(4 4)" stroke-width="0" />
</g>
<g id="icon-reg-1" stroke="none" transform="scale(1.1) translate(-2 -1)">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path xmlns="http://www.w3.org/2000/svg" d="M11 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0-6c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zM5 18c.2-.63 2.57-1.68 4.96-1.94l2.04-2c-.39-.04-.68-.06-1-.06-2.67 0-8 1.34-8 4v2h9l-2-2H5zm15.6-5.5l-5.13 5.17-2.07-2.08L12 17l3.47 3.5L22 13.91z" fill="white" transform="translate(5 3)" stroke-width="1" />
</g>
<g id="icon-cancel-1" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path xmlns="http://www.w3.org/2000/svg" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" fill="white" stroke="white" transform="translate(4 4)" stroke-width="1" />
</g>
<g id="icon-check-1" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path xmlns="http://www.w3.org/2000/svg" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="white" transform="translate(4 4)" stroke-width="1" stroke="white" />
</g>
<g id="icon-pin-1" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path xmlns="http://www.w3.org/2000/svg" d="M13 16.12c3.47-.41 6.17-3.36 6.17-6.95 0-3.87-3.13-7-7-7s-7 3.13-7 7c0 3.47 2.52 6.34 5.83 6.89V20H5v2h14v-2h-6v-3.88z" fill="white" transform="translate(4 4)" stroke-width="0" stroke="white" />
</g>
<g id="icon-check-2" stroke="none" transform="scale(1.1) translate(-2 -1)">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path xmlns="http://www.w3.org/2000/svg" d="M11 5v5.59H7.5l4.5 4.5 4.5-4.5H13V5h-2zm-5 9c0 3.31 2.69 6 6 6s6-2.69 6-6h-2c0 2.21-1.79 4-4 4s-4-1.79-4-4H6z" fill="white" transform="translate(4 4)" stroke-width="0" stroke="white" />
</g>
<g id="icon-map-1" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<path xmlns="http://www.w3.org/2000/svg" d="M20.5 3l-.16.03L15 5.1 9 3 3.36 4.9c-.21.07-.36.25-.36.48V20.5c0 .28.22.5.5.5l.16-.03L9 18.9l6 2.1 5.64-1.9c.21-.07.36-.25.36-.48V3.5c0-.28-.22-.5-.5-.5zM10 5.47l4 1.4v11.66l-4-1.4V5.47zm-5 .99l3-1.01v11.7l-3 1.16V6.46zm14 11.08l-3 1.01V6.86l3-1.16v11.84z" fill="white" transform="translate(4 4)" stroke-width="1" />
</g>
<g id="icon-sticker-3" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<g>
<path fill="#fff" fill-rule="evenodd" stroke-width=".767" d="M12 24l4 6 3.5-6.405z" transform="rotate(45 16 16)" />
<circle cx="16" cy="15" r="9.5" fill="#fff" stroke-width="1.851" />
<circle cx="16" cy="15" r="6.5" fill="black" />
</g>
</g>
<g id="icon-poly-3" transform="scale(1.1) translate(-3 -2)" stroke="none">
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black" />
<g fill="white" transform="translate(2 0.5)">
<path stroke-width="2.3" fill="none" fill-rule="evenodd" stroke="#fff" stroke-linejoin="round" d="M7.924 12.991l8.569-4.989-6.55 14.587 13.762-7.974-3.958 9.12" />
<circle cx="7.711" cy="13" r="1" fill="black" stroke-width="2.5" stroke="white" />
<circle cx="16.5" cy="8" r="1" fill="black" stroke-width="2.5" stroke="white" />
<circle cx="10" cy="22" r="1" fill="black" stroke-width="2.5" stroke="white" />
<circle cx="19.7" cy="23.701" r="1" fill="black" stroke-width="2.5" stroke="white" />
<circle cx="24" cy="14.5" r="1" fill="black" stroke-width="2.5" stroke="white" />
</g>
</g>
<g id="icon-poly-4" stroke="none">
<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="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="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="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>
<g id="icon-shot-4" stroke="none">
<path stroke="none" fill="black" />
<path d="M14.12 4l1.83 2H20v12H4V6h4.05l1.83-2h4.24M15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2zm-3 7c1.65 0 3 1.35 3 3s-1.35 3-3 3-3-1.35-3-3 1.35-3 3-3m0-2c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5z" fill="white" stroke="white" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-trash-5" stroke="none">
<path stroke="none" fill="black" />
<path d="M14.12 10.47L12 12.59l-2.13-2.12-1.41 1.41L10.59 14l-2.12 2.12 1.41 1.41L12 15.41l2.12 2.12 1.41-1.41L13.41 14l2.12-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4zM6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-trash-6" stroke="none">
<path stroke="none" fill="black" />
<g transform="scale(1.25) translate(1 1)">
<path xmlns="http://www.w3.org/2000/svg" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9zm7.5-5l-1-1h-5l-1 1H5v2h14V4z" />
</g>
</g>
<g id="icon-copy-1" stroke="none">
<path stroke="none" fill="black" />
<path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-sync-1" stroke="none">
<path stroke="none" fill="black" />
<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-block-1" stroke="none">
<path stroke="none" fill="black" />
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-edit-1" stroke="none">
<path stroke="none" fill="black" />
<path d="M14.06 9.02l.92.92L5.92 19H5v-.92l9.06-9.06M17.66 3c-.25 0-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.2-.2-.45-.29-.71-.29zm-3.6 3.19L3 17.25V21h3.75L17.81 9.94l-3.75-3.75z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-eye-1" stroke="none">
<path stroke="none" fill="black" />
<path d="M12 6c3.79 0 7.17 2.13 8.82 5.5-.59 1.22-1.42 2.27-2.41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l1.65 1.65C10.66 6.09 11.32 6 12 6zm-1.07 1.14L13 9.21c.57.25 1.03.71 1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07C16.5 9.01 14.48 7 12 7c-.37 0-.72.05-1.07.14zM2.01 3.87l2.68 2.68C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.98-.29 4.32-.82l3.42 3.42 1.41-1.41L3.42 2.45 2.01 3.87zm7.5 7.5l2.61 2.61c-.04.01-.08.02-.12.02-1.38 0-2.5-1.12-2.5-2.5 0-.05.01-.08.01-.13zm-3.4-3.4l1.75 1.75c-.23.55-.36 1.15-.36 1.78 0 2.48 2.02 4.5 4.5 4.5.63 0 1.23-.13 1.77-.36l.98.98c-.88.24-1.8.38-2.75.38-3.79 0-7.17-2.13-8.82-5.5.7-1.43 1.72-2.61 2.93-3.53z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-gpx-1" stroke="none">
<path stroke="none" fill="black" />
<path d="M8 23h8v-2H8v2zm8-21.99L8 1c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM16 15H8V5h8v10z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-arrow-up-1" stroke="none">
<path stroke="none" fill="black" />
<circle cx="16" cy="16" fill="white" r="4" />
</g>
<g id="icon-more-vert" stroke="none">
<path stroke="none" fill="black" />
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-star-blank" stroke="none">
<path stroke="none" fill="black" />
<path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-star-fill" stroke="none">
<path stroke="none" fill="black" />
<path d="m12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27z" fill="white" stroke="none" stroke-width="0" transform="translate(4 4)" />
</g>
<g id="icon-cluster-1" stroke="none">
<rect x="0" y="0" width="32" height="32" fill="black" stroke="none" />
<circle cx="10" cy="21" fill="white" r="4" />
<circle cx="22" cy="21" fill="white" r="4" />
<circle cx="16" cy="11" fill="white" r="4" />
</g>
<g id="icon-chevron-down" stroke="none">
<g transform="scale(2) translate(-4 -3)">
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
</g>
</g>
<g id="icon-sad-1" stroke="none">
<path stroke="none" fill="black" />
<g transform="translate(4 4)">
<circle cx="15.5" cy="9.5" r="1.5" fill="white" stroke="none" />
<circle cx="8.5" cy="9.5" r="1.5" fill="white" stroke="none" />
<path d="M12 14c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5zm-.01-12C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" fill="white" stroke="none" stroke-width="0" />
</g>
</g>
<g id="icon-search" stroke="none">
<path stroke="none" fill="black" />
<g transform="translate(4 4)">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</g>
</g>
<g id="icon-reverse" stroke="none">
<path stroke="none" fill="black" />
<g transform="translate(6 4)">
<path d="M9.832 2.934c-.967 0-1.683-.018-1.683-.018L7.021 0l-3.1 6.51 5.843 2.368-.777-3.032s.351-.051.845-.052a6.974 6.974 0 016.973 6.972 6.973 6.973 0 01-.922 3.457l2.07 2.07a9.833 9.833 0 001.711-5.527c0-5.43-4.402-9.831-9.832-9.832zm-8.094 4.27A9.833 9.833 0 000 12.766c0 5.43 4.402 9.832 9.832 9.832 1.108 0 1.398-.153 1.398-.153l.902 2.4 3.734-5.378-6.43-1.959.914 2.2s.046.03-.518.031a6.974 6.974 0 01-6.973-6.973 6.974 6.974 0 01.948-3.494z" fill="#fff" />
</g>
</g>
<g id="icon-drop-start" stroke="none">
<g transform="translate(6 12)" fill="white">
<path d="M23.528 6.662H11.979L9.953 4.98l2.026-1.887h11.549zM7.572 9.755l2.183-2.183-2.696-2.695 2.696-2.695L7.572 0 4.877 2.695 2.182 0 0 2.182l2.695 2.695L0 7.572l2.182 2.183 2.695-2.696z" fill="#fff" />
</g>
</g>
<g id="icon-drop-end" stroke="none">
<g transform="translate(6 12)" fill="white">
<path d="M0 3.093h11.548l2.026 1.681-2.026 1.888H0zM15.955 0l-2.182 2.182 2.695 2.695-2.695 2.695 2.182 2.183 2.695-2.696 2.696 2.696 2.182-2.183-2.696-2.695 2.696-2.695L21.346 0 18.65 2.695z" fill="#fff" />
</g>
</g>
<g id="icon-undo" stroke="none">
<g transform="translate(8 8)" fill="white">
<path d="M5.912 1.278c-.967 0-2.84.42-2.84.42L2.257 0 0 4.855 5.094 6.19l-.777-1.938s1.101-.113 1.595-.115a6.974 6.974 0 016.972 6.973 6.97 6.97 0 01-.921 3.457l2.07 2.07a9.833 9.833 0 001.71-5.527c0-5.43-4.401-9.832-9.831-9.832z" fill="#fff" />
</g>
</g>
<g id="icon-redo" stroke="none">
<g transform="translate(8 8)" fill="white">
<path d="M9.832 1.278c.967 0 2.84.42 2.84.42L13.486 0l2.257 4.855L10.65 6.19l.777-1.938s-1.101-.113-1.595-.115a6.974 6.974 0 00-6.973 6.973 6.97 6.97 0 00.922 3.457l-2.07 2.07A9.833 9.833 0 010 11.11c0-5.43 4.402-9.832 9.832-9.832z" fill="#fff" />
</g>
</g>
<g id="icon-draw-forward" stroke="none">
<g transform="translate(4 11)" fill="white">
<path d="M19.38 0l5.374 4.547-5.374 4.655V0z" />
<path d="M0 2.872h19.91v3.569H0z" />
</g>
</g>
<g id="icon-draw-backward" stroke="none">
<g transform="translate(4 11)" fill="white">
<path d="M5.374 9.202L0 4.655 5.374 0v9.202z" />
<path d="M24.754 6.33H4.844V2.762h19.91z" />
</g>
</g>
<g id="icon-to-poly" stroke="none">
<path d="M0 0h32v32H0z" fill="black" />
<path d="M7.924 12.991l8.569-4.989-6.55 14.587 13.762-7.974-3.958 9.12" fill="none" stroke="#fff" stroke-width="2.387" stroke-linejoin="round"/>
<circle transform="rotate(75)" r="2.387" cy="-4.523" cx="14.711" fill="#fff"/>
<circle transform="rotate(75)" r="2.387" cy="-13.883" cx="12.17" fill="#fff"/>
<circle transform="rotate(75)" r="2.387" cy="-19.194" cx="20.342" fill="#fff"/>
<circle transform="rotate(75)" cx="24.138" cy="-3.701" r="5.48" fill="black" />
<circle transform="rotate(75)" cx="27.918" cy="-13.025" r="2.387" fill="#fff"/>
<path d="M8.224 18.144v3.132H2.357v3.891h5.867v3.012l5.86-5.077z"/>
</g>
<g id="icon-start">
<path d="M15.646 2.959a12.688 12.688 0 00-8 2.85v1.837h8zm0 4.687v8h8v-8zm8 0h1.838a12.688 12.688 0 00-1.838-1.835zm0 8v8h1.836a12.688 12.688 0 002.852-8zm0 8h-8v4.688a12.688 12.688 0 008-2.85zm-8 0v-8h-8v8zm-8 0H5.81a12.688 12.688 0 001.837 1.836zm0-8v-8H5.811a12.688 12.688 0 00-2.852 8z" fill="#fff"/>
<path d="M15.646 0A15.646 15.646 0 000 15.646a15.646 15.646 0 0015.646 15.647 15.646 15.646 0 0015.647-15.647A15.646 15.646 0 0015.646 0zm0 3.24a12.407 12.407 0 0112.408 12.406 12.407 12.407 0 01-12.408 12.408A12.407 12.407 0 013.24 15.646 12.407 12.407 0 0115.646 3.24z" fill="#fff"/>
</g>
</svg>
</defs>
<use xlink:href="#icon-trash-6" />
</svg>

After

Width:  |  Height:  |  Size: 36 KiB

BIN
public/images/logos/jw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
public/images/logos/lgo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 612 KiB

154
public/index.html Normal file
View file

@ -0,0 +1,154 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=0.95">
<meta name="theme-color" content="#5A3D6D">
<link href="https://fonts.googleapis.com/css?family=Rubik:300,400,500,700&amp;subset=cyrillic" rel="stylesheet" />
<title>Редактор маршрутов</title>
<!-- analytics, lol -->
<script async src="https://anal.vault48.org/script.js" data-website-id="9f2c8b59-93ff-4bcc-9ab6-11541f68f198"></script>
<link rel="shortcut icon" href="/favicon.png" type="image/png">
<meta property="og:image" content="/misc/vk_preview.png" />
<meta content="/misc/vk_preview.png">
<style>
#loader {
position: fixed;
background: #333333;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 10;
transition: opacity 1s;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-family: sans-serif;
font-size: 16px;
flex-direction: column;
font-weight: 500;
-webkit-font-smoothing: antialiased;
}
@keyframes blink {
0% { opacity: 0.9; }
100% { opacity: 1; }
}
#loader-current {
text-transform: uppercase;
font-size: 20px;
text-align: center;
font-weight: 600;
display: none;
}
#loader-container {
max-width: 320px;
height: 100px;
width: 100%;
position: relative;
}
#loader-progress {
width: 100%;
height: 24px;
background: rgba(0, 0, 0, 0.2);
box-shadow: inset rgba(0,0,0,0.1) 0 0 0 1px;
border-radius: 12px;
box-sizing: border-box;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
}
#loader-bar {
/*background: linear-gradient(90deg, #845b9e, #54faff);*/
background: linear-gradient(130deg,#46bff3, #7833ff);
/* background: #7c5f9e; */
width: 10%;
height: 100%;
border-radius: 8px;
transition: width 500ms;
animation: blink 0.5s infinite alternate linear;
min-width: 16px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
#loader-bar.is_failed {
background: linear-gradient(130deg, #f3572b, #a51519);
}
#loader-bar.is_failed::after {
content: 'перезагрузка';
font-size: 12px;
position: relative;
top: 1px;
opacity: 0.5;
}
body {
overflow: hidden;
padding: 0;
margin: 0;
}
canvas#renderer {
position: fixed;
left: 0;
top: 0;
}
#loader-error {
color: white;
/* background-color: white; */
border-radius: 12px;
padding: 10px;
margin-top: 20px;
opacity: 0;
font-size: 13px;
text-align: center;
box-shadow: inset white 0 0 0 1px;
text-transform: uppercase;
position: absolute;
bottom: 100%;
left: 0;
width: 100%;
box-sizing: border-box;
transform: translate(0, -16px);
}
#loader-error h4 {
margin: 0 0 5px 0;
}
</style>
</head>
<body>
<canvas id="renderer"></canvas>
<section id="canvas" style="position: absolute; width: 100%; height: 100%;"></section>
<section id="loader">
<div id="loader-container">
<div id="loader-current">ЗАГРУЗКА</div>
<div id="loader-error">
<h4>Хранилище недоступно</h4>
<div>Мы работаем над решением проблемы</div>
</div>
<div id="loader-progress">
<div id="loader-bar"></div>
</div>
</div>
</section>
<section id="index"></section>
</body>

View file

@ -1,40 +0,0 @@
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import { IModes, MODES } from '$constants/modes';
import { IStickerPack, STICKERS } from '$constants/stickers';
import { StickerIcon } from '$components/StickerIcon';
interface Props {
mode: keyof IModes,
sticker: string,
set: keyof IStickerPack,
}
export class Cursor extends React.PureComponent<Props, {}> {
componentDidMount() {
window.addEventListener('mousemove', this.moveCursor);
}
moveCursor = e => {
if (!e.clientX || !e.clientY) return;
const { clientX, clientY } = e;
this.cursor.style.transform = `translate3d(${clientX}px, ${clientY}px, 0)`;
};
cursor: HTMLElement = null;
render() {
const { 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>
);
}
};

View file

@ -1,4 +1,4 @@
import * as React from 'react';
import React from 'react';
export const Fills = () => (
<svg>
@ -43,6 +43,6 @@ export const Fills = () => (
<path d="m 2.625,3.375 h 7.5 L 10.28125,1.609375 13.5,4.25 10.484375,6.921875 10.171875,5.15625 2.625,5.125 Z" fill="#ff3344" fillRule="evenodd" />
</g>
</defs>
<image xlinkHref={require('$sprites/stickers/stickers-base.svg')} width={0} height={0} />
<image xlinkHref="/images/stickers-base.svg" width={0} height={0} />
</svg>
);

View file

@ -1,4 +1,4 @@
import * as React from 'react';
import React from 'react';
import { Scrollbars } from 'tt-react-custom-scrollbars';
export const Scroll = props => (

View file

@ -1,42 +1,48 @@
import * as React from 'react';
import React from 'react';
import classnames from 'classnames';
interface Props {
value: string;
onChange: (text: string) => void;
onBlur: () => void;
}
type State = {
text: String;
}
export class StickerDesc extends React.PureComponent<Props, State> {
class StickerDesc extends React.PureComponent<Props, State> {
state = {
text: this.props.value,
};
setText = e => {
this.setState({ text: e.target.value });
this.props.onChange(e.target.value);
};
blockMouse = e => {
e.preventDefault();
e.stopPropagation();
if (!this.input) {
return
}
this.input.focus();
};
input: HTMLTextAreaElement;
// todo: pass here locker for moving markers from Sticker.js
input: HTMLTextAreaElement | null = null;
render() {
const { text } = this.state;
const { value: text } = this.props;
return (
<div
className={classnames('sticker-desc', { is_empty: !text.trim() })}
onMouseDown={this.blockMouse}
onMouseUp={this.blockMouse}
onDragStart={this.blockMouse}
onMouseDownCapture={this.blockMouse}
onMouseUpCapture={this.blockMouse}
onDragStartCapture={this.blockMouse}
onTouchStartCapture={this.blockMouse}
>
<div className="sticker-desc-sizer">
<span
@ -50,9 +56,12 @@ export class StickerDesc extends React.PureComponent<Props, State> {
onMouseDown={this.blockMouse}
onDragStart={this.blockMouse}
ref={el => { this.input = el; }}
onBlur={this.props.onBlur}
/>
</div>
</div>
)
}
}
export { StickerDesc };

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import { STICKERS } from '$constants/stickers';
import React from 'react';
import { STICKERS } from '~/constants/stickers';
type Props = {
set: string,

View file

@ -1,5 +1,5 @@
// @flow
import * as React from 'react';
import React from 'react';
import classnames from 'classnames';
type Props = {

View file

@ -1,78 +1,36 @@
import * as React from 'react';
import { marker } from 'leaflet';
import { DomMarker } from '$utils/DomMarker';
import { Icon } from '$components/panels/Icon';
import { editor } from '$modules/Editor';
import React, { FC, useCallback } from 'react';
import { IState } from '~/redux/store';
import { selectUserLocation } from '~/redux/user/selectors';
import { connect } from 'react-redux';
import { Tooltip } from './panels/Tooltip';
import { MainMap } from '~/constants/map';
interface Props {
const mapStateToProps = (state: IState) => ({
location: selectUserLocation(state),
});
}
type Props = ReturnType<typeof mapStateToProps> & {};
export class UserLocation extends React.Component<Props, {}> {
constructor(props) {
super(props);
const UserLocationUnconnected: FC<Props> = ({ location }) => {
const onClick = useCallback(() => {
if (!location) return;
const element = document.createElement('div');
this.icon = new DomMarker({ element, className: 'location-marker' });
MainMap.setView(location, 17);
}, [MainMap, location]);
this.map = editor.map.map;
this.location = [];
}
return (
<div className="status-bar location-bar pointer tooltip-container" onClick={onClick}>
<Tooltip position="top">Где&nbsp;я?</Tooltip>
icon;
mark = null;
map;
location;
<svg width="20" height="20" viewBox="0 0 20 20" style={{ opacity: location ? 1 : 0.5 }}>
<g transform="translate(7 2)">
<circle r="1.846" cy="1.846" cx="5.088" />
<path d="M3.004 4.326h4l2-3 1 1-3 4v10h-1l-1-7-1 7h-1v-10s-3.125-4-3-4l1-1z" />
<ellipse ry="1" rx="4" cy="16.326" cx="5.004" opacity=".262" fill="black" />
</g>
</svg>
</div>
);
};
componentDidMount() {
this.getUserLocation(this.updateLocationMark);
}
getUserLocation = callback => {
// todo: TO SAGAS
if (!window.navigator || !window.navigator.geolocation) return;
window.navigator.geolocation.getCurrentPosition(position => {
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude) return;
const { latitude, longitude } = position.coords;
callback(latitude, longitude);
});
};
centerMapOnLocation = () => {
if (this.location && this.location.length === 2) {
this.panMapTo(this.location[0], this.location[1]);
} else {
this.getUserLocation(this.panMapTo);
}
this.getUserLocation(this.updateLocationMark);
};
panMapTo = (latitude, longitude) => {
if (!latitude || !longitude) return;
this.map.panTo([latitude, longitude]);
};
updateLocationMark = (latitude, longitude) => {
if (!latitude || !longitude) return;
if (this.mark) this.map.removeLayer(this.mark);
this.location = [latitude, longitude];
this.mark = marker(this.location, { icon: this.icon }).addTo(this.map);
};
render() {
return (
<div className="status-bar square pointer pointer">
<div onClick={this.centerMapOnLocation}>
<Icon icon="icon-locate" size={30} />
</div>
</div>
);
}
}
export const UserLocation = connect(mapStateToProps)(UserLocationUnconnected);

View file

@ -1,74 +1,62 @@
// @flow
import * as React from 'react';
import { Scroll } from '$components/Scroll';
import { APP_INFO } from '$constants/app_info';
import React, { Fragment } from 'react';
import { Scroll } from '~/components/Scroll';
import { APP_INFO } from '~/constants/app_info';
export const AppInfoDialog = () => (
<div className="dialog-content">
<div className="dialog-head">
<div className="dialog-head-title">
Orchid Map
</div>
<div className="small gray">
версия{' '}
{(APP_INFO.VERSION || 1)}.
{(APP_INFO.CHANGELOG[APP_INFO.VERSION].length || 0)}.
{(APP_INFO.CHANGELOG[APP_INFO.VERSION][0].length - 1 || 0)}
</div>
<hr />
<div className="small app-info-list">
<div>
<div>Исходный код:</div>
<a href="//github.com/muerwre/orchidMap" target="_blank">github.com/muerwre/orchidMap</a>
<Fragment>
<div style={{ flex: 1 }} />
<div className="dialog-content">
<div className="dialog-head">
<div className="dialog-head-title">Orchid Map</div>
<div className="small gray">
версия {APP_INFO.VERSION || 1}.{APP_INFO.CHANGELOG[APP_INFO.VERSION].length || 0}.
{APP_INFO.CHANGELOG[APP_INFO.VERSION][0].length - 1 || 0}
</div>
<div>
<div>Frontend:</div>
<a href="//reactjs.org/" target="_blank">ReactJS</a>,{' '}
<a href="//leafletjs.com" target="_blank">Leaflet</a>,{' '}
<a href="//www.liedman.net/leaflet-routing-machine/" target="_blank">Leaflet Routing Machine</a>{' '}
</div>
<div>
<div>Backend:</div>
<a href="//project-osrm.org/" target="_blank">OSRM</a>,{' '}
<a href="//nodejs.org/" target="_blank">NodeJS</a>,{' '}
<a href="//expressjs.com/" target="_blank">ExpressJS</a>,{' '}
<a href="//mongodb.com/" target="_blank">MongoDB</a>
<hr />
<div className="small app-info-list">
<div>
<div>Исходный код:</div>
<a href="//github.com/muerwre/orchid-front" target="_blank">
github.com/muerwre/orchid-front
</a>
<br />
<a href="//github.com/muerwre/orchid-backend" target="_blank">
github.com/muerwre/orchid-backend
</a>
</div>
<div>
<div>Frontend:</div>
<a href="//reactjs.org/" target="_blank">
ReactJS
</a>
,{' '}
<a href="//leafletjs.com" target="_blank">
Leaflet
</a>
,{' '}
<a href="//www.liedman.net/leaflet-routing-machine/" target="_blank">
Leaflet Routing Machine
</a>{' '}
</div>
<div>
<div>Backend:</div>
<a href="//project-osrm.org/" target="_blank">
OSRM
</a>
,{' '}
<a href="//golang.org/" target="_blank">
Golang
</a>
,{' '}
<a href="//nginx.org/" target="_blank">
Nginx
</a>
,{' '}
</div>
</div>
</div>
</div>
<Scroll className="dialog-shader">
<div>
<div className="app-info-changelog">
<h2>История изменений</h2>
{
[...Object.keys(APP_INFO.CHANGELOG)].reverse().map((version, i) => (
<div className="app-info-changelog-item" key={version}>
<div className="app-info-number">{version}.</div>
<div className="app-info-version">
{
APP_INFO.CHANGELOG[version].map((release, y) => (
<div className="app-info-release" key={release}>
<div className="app-info-number">{APP_INFO.CHANGELOG[version].length - y}.</div>
<div className="app-info-build">
{
APP_INFO.CHANGELOG[version][y].map((build, z) => (
<div className="app-info-change" key={build}>
<div className="app-info-number">{(z)}.</div>
<span>{APP_INFO.CHANGELOG[version][y][z]}</span>
</div>
))
}
</div>
</div>
))
}
</div>
</div>
))
}
</div>
</div>
</Scroll>
</div>
</Fragment>
);

View file

@ -1,29 +1,30 @@
import * as React from 'react';
import React from 'react';
import { MODES } from '$constants/modes';
import { Icon } from '$components/panels/Icon';
import { setMode, stopEditing } from "$redux/user/actions";
import { MODES } from '~/constants/modes';
import { Icon } from '~/components/panels/Icon';
import { editorChangeMode, editorStopEditing } from '~/redux/editor/actions';
import { connect } from 'react-redux';
type Props = {
stopEditing: typeof stopEditing,
setMode: typeof setMode,
width: number,
const mapStateToProps = () => ({});
const mapDispatchToProps = {
editorChangeMode,
editorStopEditing,
};
export class CancelDialog extends React.Component<Props, void> {
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & { };
class CancelDialogUnconnected extends React.Component<Props, void> {
cancel = () => {
this.props.stopEditing();
this.props.editorStopEditing();
};
proceed = () => {
this.props.setMode(MODES.NONE);
this.props.editorChangeMode(MODES.NONE);
};
render() {
const { width } = this.props;
return (
<div className="control-dialog" style={{ width }}>
<div className="control-dialog control-dialog__medium">
<div className="helper cancel-helper">
<div className="helper__text danger">
<Icon icon="icon-cancel-1" />
@ -46,3 +47,5 @@ export class CancelDialog extends React.Component<Props, void> {
);
}
}
export const CancelDialog = connect(mapStateToProps, mapDispatchToProps)(CancelDialogUnconnected)

View file

@ -0,0 +1,16 @@
import React, { FC } from 'react';
import { Icon } from '~/components/panels/Icon';
interface IProps {}
const DialogLoader: FC<IProps> = ({}) => {
return (
<div className="dialog-maplist-loader">
<div className="dialog-maplist-icon spin">
<Icon icon="icon-sync-1" />
</div>
</div>
);
};
export { DialogLoader };

View file

@ -0,0 +1,177 @@
import React, { FC, useCallback, ChangeEvent } from 'react';
import { connect } from 'react-redux';
import { IState } from '~/redux/store';
import { selectEditorGpx } from '~/redux/editor/selectors';
import { GpxDialogRow } from '~/components/gpx/GpxDialogRow';
import { GpxConfirm } from '~/components/gpx/GpxConfirm';
import { MainMap } from '~/constants/map';
import { latLngBounds } from 'leaflet';
import { Switch } from '../Switch';
import { selectMapRoute, selectMapTitle, selectMapAddress } from '~/redux/map/selectors';
import classNames from 'classnames';
import uuid from 'uuid';
import { getUrlData } from '~/utils/history';
import { getRandomColor } from '~/utils/dom';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import * as MAP_ACTIONS from '~/redux/map/actions';
import { simplify } from '~/utils/simplify';
const mapStateToProps = (state: IState) => ({
gpx: selectEditorGpx(state),
route: selectMapRoute(state),
title: selectMapTitle(state),
address: selectMapAddress(state),
});
const mapDispatchToProps = {
editorDropGpx: EDITOR_ACTIONS.editorDropGpx,
editorUploadGpx: EDITOR_ACTIONS.editorUploadGpx,
editorSetGpx: EDITOR_ACTIONS.editorSetGpx,
editorGetGPXTrack: EDITOR_ACTIONS.editorGetGPXTrack,
mapSetRoute: MAP_ACTIONS.mapSetRoute,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const GpxDialogUnconnected: FC<Props> = ({
title,
address,
gpx,
route,
editorGetGPXTrack,
editorSetGpx,
editorUploadGpx,
mapSetRoute,
}) => {
const toggleGpx = useCallback(() => {
editorSetGpx({ enabled: !gpx.enabled });
}, [gpx, editorSetGpx]);
const onGpxUpload = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
if (!event.target || !event.target.files || event.target.files.length === 0) {
return;
}
editorUploadGpx(event.target.files[0]);
},
[editorUploadGpx]
);
const onFocusRoute = useCallback(
index => {
if (!gpx.list[index] || !gpx.list[index].latlngs) return;
const bounds = latLngBounds(gpx.list[index].latlngs);
MainMap.fitBounds(bounds);
},
[gpx, MainMap]
);
const onRouteDrop = useCallback(
index => {
editorSetGpx({ list: gpx.list.filter((el, i) => i !== index) });
},
[gpx, editorSetGpx]
);
const onRouteColor = useCallback(
index => {
if (!gpx.enabled) return;
editorSetGpx({
list: gpx.list.map((el, i) => (i !== index ? el : { ...el, color: getRandomColor() })),
});
},
[gpx, editorSetGpx]
);
const onRouteToggle = useCallback(
index => {
if (!gpx.enabled) return;
editorSetGpx({
list: gpx.list.map((el, i) => (i !== index ? el : { ...el, enabled: !el.enabled })),
});
},
[gpx, editorSetGpx]
);
const addCurrent = useCallback(() => {
if (!route.length) return;
const { path } = getUrlData();
editorSetGpx({
list: [
...gpx.list,
{
latlngs: route,
enabled: false,
name: title || address || path,
id: uuid(),
color: getRandomColor(),
},
],
});
}, [route, gpx, editorSetGpx]);
const onRouteReplace = useCallback(
(i: number) => {
mapSetRoute(simplify(gpx.list[i].latlngs));
editorSetGpx({
list: gpx.list.map((el, index) => (i !== index ? el : { ...el, enabled: false })),
});
},
[gpx, mapSetRoute, editorSetGpx]
);
return (
<div className="control-dialog control-dialog__left control-dialog__small">
<div className="gpx-title">
<div className="flex_1 big white upper">Треки</div>
<Switch active={gpx.enabled} onPress={toggleGpx} />
</div>
{gpx.list.map((item, index) => (
<GpxDialogRow
item={item}
key={item.id}
index={index}
enabled={gpx.enabled}
onRouteDrop={onRouteDrop}
onFocusRoute={onFocusRoute}
onRouteToggle={onRouteToggle}
onRouteColor={onRouteColor}
onRouteReplace={onRouteReplace}
/>
))}
<div className="gpx-buttons">
<button className="button outline">
<input type="file" onChange={onGpxUpload} />
Загрузить GPX
</button>
<div
className={classNames('button outline', { disabled: !route.length })}
onClick={addCurrent}
>
Добавить текущий
</div>
<div
className={classNames('button success', { disabled: !route.length })}
onClick={editorGetGPXTrack}
>
Скачать текущий
</div>
</div>
</div>
);
};
const GpxDialog = connect(mapStateToProps, mapDispatchToProps)(GpxDialogUnconnected);
export { GpxDialog };

View file

@ -1,15 +1,22 @@
import * as React from 'react';
import { LOGOS } from '$constants/logos';
import { Icon } from '$components/panels/Icon';
import React from 'react';
import { LOGOS } from '~/constants/logos';
import { Icon } from '~/components/panels/Icon';
import classnames from 'classnames';
import { setLogo as setLogoAction } from "$redux/user/actions";
import { IRootState } from "$redux/user/reducer";
import * as MAP_ACTIONS from "~/redux/map/actions"
import { selectMapLogo } from '~/redux/map/selectors';
import { connect } from 'react-redux';
interface Props extends IRootState {
setLogo: typeof setLogoAction,
}
const mapStateToProps = state => ({
logo: selectMapLogo(state),
});
export const LogoDialog = ({ logo, setLogo }: Props) => (
const mapDispatchToProps = {
mapSetLogo: MAP_ACTIONS.mapSetLogo,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const LogoDialogUnconnected = ({ logo, mapSetLogo }: Props) => (
<div className="control-dialog top">
<div className="helper logo-helper">
<div className="helper-back">
@ -19,7 +26,7 @@ export const LogoDialog = ({ logo, setLogo }: Props) => (
Object.keys(LOGOS).map(item => (
<div
className={classnames('helper-menu-item', { active: (item === logo) })}
onMouseDown={() => setLogo(item)}
onMouseDown={() => mapSetLogo(item)}
key={item}
>
{LOGOS[item][0]}
@ -29,3 +36,7 @@ export const LogoDialog = ({ logo, setLogo }: Props) => (
</div>
</div>
);
const LogoDialog = connect(mapStateToProps, mapDispatchToProps)(LogoDialogUnconnected);
export { LogoDialog };

View file

@ -1,102 +1,123 @@
import * as React from 'react';
import { bindActionCreators } from 'redux';
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { RouteRowWrapper } from '$components/maps/RouteRowWrapper';
import { Scroll } from '$components/Scroll';
import { RouteRowWrapper } from '~/components/maps/RouteRowWrapper';
import { Scroll } from '~/components/Scroll';
import {
searchSetDistance,
searchSetTitle,
searchSetTab,
setDialogActive,
mapsLoadMore,
dropRoute,
modifyRoute,
} from '$redux/user/actions';
import { isMobile } from '$utils/window';
toggleRouteStarred,
} from '~/redux/user/actions';
import { editorSetDialogActive } from '~/redux/editor/actions';
import { isMobile } from '~/utils/window';
import classnames from 'classnames';
// import { Range } from 'rc-slider';
import * as Range from 'rc-slider/lib/Range';
import { TABS } from '$constants/dialogs';
import { Icon } from '$components/panels/Icon';
import { pushPath } from '$utils/history';
import { IRootState, IRouteListItem } from '$redux/user/reducer';
import { TABS, TABS_TITLES } from '~/constants/dialogs';
import { Icon } from '~/components/panels/Icon';
import { pushPath } from '~/utils/history';
import { IRouteListItem } from '~/redux/user';
import { ROLES } from '~/constants/auth';
import { IState } from '~/redux/store';
import { MapListDialogHead } from '~/components/search/MapListDialogHead';
import { DialogLoader } from '~/components/dialogs/DialogLoader';
export interface IMapListDialogProps extends IRootState {
marks: { [x: number]: string },
routes_sorted: Array<IRouteListItem>,
routes: IRootState['routes'],
ready: IRootState['ready'],
const mapStateToProps = ({
editor: { editing },
user: {
routes,
user: { role },
},
}: IState) => {
return {
role,
routes,
editing,
ready: routes.filter.max < 9999,
};
};
mapsLoadMore: typeof mapsLoadMore,
searchSetDistance: typeof searchSetDistance,
searchSetTitle: typeof searchSetTitle,
searchSetTab: typeof searchSetTab,
setDialogActive: typeof setDialogActive,
dropRoute: typeof dropRoute,
modifyRoute: typeof modifyRoute,
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'];
is_editing: boolean;
is_dropping: boolean;
}
export interface IMapListDialogState {
menu_target: IRouteListItem['_id'],
editor_target: IRouteListItem['_id'],
is_editing: boolean,
is_dropping: boolean,
}
class Component extends React.Component<IMapListDialogProps, IMapListDialogState> {
class MapListDialogUnconnected extends PureComponent<Props, State> {
state = {
menu_target: null,
editor_target: null,
menu_target: '',
editor_target: '',
is_editing: false,
is_dropping: false,
};
startEditing = (editor_target: IRouteListItem['_id']): void => this.setState({
editor_target,
menu_target: null,
is_editing: true,
is_dropping: false,
});
startEditing = (editor_target: IRouteListItem['address']): void =>
this.setState({
editor_target,
menu_target: '',
is_editing: true,
is_dropping: false,
});
showMenu = (menu_target: IRouteListItem['_id']): void => this.setState({
menu_target,
});
showMenu = (menu_target: IRouteListItem['address']): void =>
this.setState({
menu_target,
});
hideMenu = (): void => this.setState({
menu_target: null,
});
hideMenu = (): void =>
this.setState({
menu_target: '',
});
showDropCard = (editor_target: IRouteListItem['_id']): void => this.setState({
editor_target,
menu_target: null,
is_editing: false,
is_dropping: true,
});
showDropCard = (editor_target: IRouteListItem['address']): void =>
this.setState({
editor_target,
menu_target: '',
is_editing: false,
is_dropping: true,
});
stopEditing = (): void => {
this.setState({ editor_target: null });
this.setState({ editor_target: '' });
};
setTitle = ({ target: { value } }: { target: { value: string }}): void => {
setTitle = ({ target: { value } }: { target: { value: string } }): void => {
this.props.searchSetTitle(value);
};
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: { target: { scrollHeight: number, scrollTop: number, clientHeight: number }}): void => {
const { target: { scrollHeight, scrollTop, clientHeight }} = e;
onScroll = (e: {
target: { scrollHeight: number; scrollTop: number; clientHeight: number };
}): void => {
const {
target: { scrollHeight, scrollTop, clientHeight },
} = e;
const delta = scrollHeight - scrollTop - clientHeight;
if (
@ -108,122 +129,105 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
}
};
dropRoute = (_id: string): void => {
this.props.dropRoute(_id);
dropRoute = (address: string): void => {
this.props.dropRoute(address);
};
modifyRoute = ({ _id, title, is_public }: { _id: string, title: string, is_public: boolean }): void => {
this.props.modifyRoute(_id, { title, is_public });
modifyRoute = ({
address,
title,
is_public,
}: {
address: string;
title: string;
is_public: boolean;
}): void => {
this.props.modifyRoute(address, { title, is_public });
this.stopEditing();
};
toggleStarred = (id: string) => this.props.toggleRouteStarred(id);
render() {
const {
ready,
role,
routes: {
list,
loading,
filter: {
min,
max,
title,
distance,
tab,
}
filter: { min, max, title, distance, tab },
},
marks,
}: IMapListDialogProps = this.props;
}: // marks,
Props = this.props;
const { editor_target, menu_target, is_editing, is_dropping } = this.state;
return (
<div className="dialog-content">
{ list.length === 0 && loading &&
<div className="dialog-maplist-loader">
<div className="dialog-maplist-icon spin">
<Icon icon="icon-sync-1" />
</div>
</div>
}
{ ready && !loading && list.length === 0 &&
<div className="dialog-content full">
{list.length === 0 && loading && (
<DialogLoader />
)}
{ready && !loading && list.length === 0 && (
<div className="dialog-maplist-loader">
<div className="dialog-maplist-icon">
<Icon icon="icon-sad-1" />
</div>
ТУТ ПУСТО <br />
И ОДИНОКО
ТУТ ПУСТО <br />И ОДИНОКО
</div>
}
)}
<div className="dialog-tabs">
{
Object.keys(TABS).map(item => (
<div
className={classnames('dialog-tab', { active: tab === item })}
onClick={() => this.props.searchSetTab(item)}
key={item}
>
{TABS[item]}
</div>
))
}
</div>
<div className="dialog-head">
<div>
<input
type="text"
placeholder="Поиск по названию"
value={title}
onChange={this.setTitle}
/>
<br />
{
ready
?
<Range
min={min}
max={max}
marks={marks}
step={25}
onChange={this.props.searchSetDistance}
defaultValue={[0, 10000]}
value={distance}
pushable={25}
disabled={min >= max}
/>
: <div className="range-placeholder" />
}
</div>
{Object.values(TABS).map(
item =>
(role === ROLES.admin || item !== TABS.PENDING) && (
<div
className={classnames('dialog-tab', { active: tab === item })}
onClick={() => this.props.searchSetTab(item)}
key={item}
>
{TABS_TITLES[item]}
</div>
)
)}
</div>
<Scroll
className="dialog-shader"
onScroll={this.onScroll}
>
<MapListDialogHead
min={min}
max={max}
distance={distance}
onDistanceChange={this.props.searchSetDistance}
ready={ready}
search={title}
onSearchChange={this.setTitle}
/>
<Scroll className="dialog-shader" onScroll={this.onScroll}>
<div className="dialog-maplist">
{
list.map(route => (
<RouteRowWrapper
title={route.title}
distance={route.distance}
_id={route._id}
is_public={route.is_public}
tab={tab}
is_editing_mode={is_dropping ? 'drop' : 'edit'}
is_editing_target={editor_target === route._id}
is_menu_target={menu_target === route._id}
openRoute={this.openRoute}
startEditing={this.startEditing}
stopEditing={this.stopEditing}
showMenu={this.showMenu}
hideMenu={this.hideMenu}
showDropCard={this.showDropCard}
dropRoute={this.dropRoute}
modifyRoute={this.modifyRoute}
key={route._id}
/>
))
}
{list.map(route => (
<RouteRowWrapper
title={route.title}
distance={route.distance}
address={route.address}
is_public={route.is_public}
is_published={route.is_published}
tab={tab}
is_editing_mode={is_dropping ? 'drop' : 'edit'}
is_editing_target={editor_target === route.address}
is_menu_target={menu_target === route.address}
openRoute={this.openRoute}
startEditing={this.startEditing}
stopEditing={this.stopEditing}
showMenu={this.showMenu}
hideMenu={this.hideMenu}
showDropCard={this.showDropCard}
dropRoute={this.dropRoute}
modifyRoute={this.modifyRoute}
toggleStarred={this.toggleStarred}
key={route.address}
is_admin={role === ROLES.admin}
/>
))}
</div>
</Scroll>
@ -233,33 +237,6 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
}
}
const mapStateToProps = ({ user: { editing, routes } }) => {
if (routes.filter.max >= 9999) {
return {
routes, editing, marks: {}, ready: false,
};
}
return ({
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 MapListDialog = connect(mapStateToProps, mapDispatchToProps)(MapListDialogUnconnected);
const mapDispatchToProps = dispatch => bindActionCreators({
searchSetDistance,
searchSetTitle,
searchSetTab,
setDialogActive,
mapsLoadMore,
dropRoute,
modifyRoute,
}, dispatch);
export const MapListDialog = connect(mapStateToProps, mapDispatchToProps)(Component);
export { MapListDialog };

View file

@ -0,0 +1,46 @@
import React, { FC, Fragment, useCallback } from 'react';
import { connect } from 'react-redux';
import { IState } from '~/redux/store';
import { selectEditorNominatim } from '~/redux/editor/selectors';
import { DialogLoader } from './DialogLoader';
import { NominatimListItem } from '~/components/nominatim/NominatimListItem';
import { MainMap } from '~/constants/map';
import { Scroll } from '../Scroll';
const mapStateToProps = (state: IState) => ({
nominatim: selectEditorNominatim(state),
});
type Props = ReturnType<typeof mapStateToProps> & {};
const NominatimDialogUnconnected: FC<Props> = ({ nominatim: { loading, list } }) => {
const onItemClick = useCallback(
(index: number) => {
if (!list[index]) return;
MainMap.setView(list[index].latlng, 17);
},
[MainMap, list]
);
return (
<Fragment>
<Scroll>
<div className="dialog-flex-scroll">
<div style={{ flex: 1 }} />
<div className="dialog-content nominatim-dialog-content">
{loading && <DialogLoader />}
{list.map((item, i) => (
<NominatimListItem item={item} key={item.id} />
))}
</div>
</div>
</Scroll>
</Fragment>
);
};
const NominatimDialog = connect(mapStateToProps)(NominatimDialogUnconnected);
export { NominatimDialog };

View file

@ -0,0 +1,37 @@
import React, { FC, useCallback, useState } from 'react';
import { Icon } from '../panels/Icon';
interface IProps {
active: boolean;
onSearch: (search: string) => void;
}
const NominatimSearchPanel: FC<IProps> = ({ active, onSearch }) => {
const [search, setSearch] = useState('Колывань');
const setValue = useCallback(({ target: { value } }) => setSearch(value), [setSearch]);
const onSubmit = useCallback(event => {
event.preventDefault();
if (search.length < 3) return;
onSearch(search);
}, [search, onSearch]);
return (
<form className="panel nominatim-panel active" onSubmit={onSubmit}>
<div className="control-bar">
<div className="nominatim-search-input">
<input type="text" placeholder="Поиск на карте" value={search} onChange={setValue} />
</div>
<button>
<Icon icon="icon-search" />
</button>
</div>
</form>
);
};
export { NominatimSearchPanel };

View file

@ -0,0 +1,111 @@
import React, { FC, useCallback } from 'react';
import { Icon } from '~/components/panels/Icon';
import { connect } from 'react-redux';
import * as MAP_ACTIONS from '~/redux/map/actions';
import { IState } from '~/redux/store';
import { selectMapRoute } from '~/redux/map/selectors';
import classNames from 'classnames';
import { selectEditorDirection } from '~/redux/editor/selectors';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import { DRAWING_DIRECTIONS } from '~/redux/editor/constants';
const mapStateToProps = (state: IState) => ({
route: selectMapRoute(state),
direction: selectEditorDirection(state),
});
const mapDispatchToProps = {
mapSetRoute: MAP_ACTIONS.mapSetRoute,
editorSetDirection: EDITOR_ACTIONS.editorSetDirection,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const PolylineDialogUnconnected: FC<Props> = ({
route,
direction,
editorSetDirection,
mapSetRoute,
}) => {
const reverseRoute = useCallback(() => {
if (route.length < 2) return;
mapSetRoute([...route].reverse());
}, [mapSetRoute, route]);
const curRouteStart = useCallback(() => {
if (route.length < 1) return;
mapSetRoute(route.slice(1, route.length));
}, [mapSetRoute, route]);
const curRouteEnd = useCallback(() => {
if (route.length < 1) return;
mapSetRoute(route.slice(0, route.length - 1));
}, [mapSetRoute, route]);
const continueBackward = useCallback(() => {
editorSetDirection(DRAWING_DIRECTIONS.BACKWARDS);
}, [editorSetDirection]);
const continueForward = useCallback(() => {
editorSetDirection(DRAWING_DIRECTIONS.FORWARDS);
}, [editorSetDirection]);
return (
<div className="control-dialog control-dialog__medium">
<div className="helper">
<div className="helper__text">
<button
className={classNames('helper__icon_button', { inactive: route.length < 2 })}
onClick={reverseRoute}
>
<Icon icon="icon-reverse" />
</button>
<button
className={classNames('helper__icon_button', { inactive: route.length < 1 })}
onClick={curRouteStart}
>
<Icon icon="icon-drop-start" />
</button>
<button
className={classNames('helper__icon_button', { inactive: route.length < 1 })}
onClick={curRouteEnd}
>
<Icon icon="icon-drop-end" />
</button>
<button
className={classNames('helper__icon_button', {
inactive: route.length < 2,
active: direction === DRAWING_DIRECTIONS.BACKWARDS,
})}
onClick={continueBackward}
>
<Icon icon="icon-draw-backward" />
</button>
<button
className={classNames('helper__icon_button', {
inactive: route.length < 2,
active: direction === DRAWING_DIRECTIONS.FORWARDS,
})}
onClick={continueForward}
>
<Icon icon="icon-draw-forward" />
</button>
<div className="flex_1" />
<div className="big white upper">Ручной режим</div>
</div>
</div>
</div>
);
};
const PolylineDialog = connect(mapStateToProps, mapDispatchToProps)(PolylineDialogUnconnected);
export { PolylineDialog };

View file

@ -1,15 +1,22 @@
import * as React from 'react';
import { PROVIDERS, replaceProviderUrl } from '$constants/providers';
import { Icon } from '$components/panels/Icon';
import React from 'react';
import { PROVIDERS, replaceProviderUrl } from '~/constants/providers';
import { Icon } from '~/components/panels/Icon';
import classnames from 'classnames';
import { changeProvider as changeProviderAction } from "$redux/user/actions";
import { IRootState } from "$redux/user/reducer";
import * as MAP_ACTIONS from "~/redux/map/actions";
import { selectMapProvider } from '~/redux/map/selectors';
import { connect } from 'react-redux';
interface Props extends IRootState {
changeProvider: typeof changeProviderAction,
}
const mapStateToProps = state => ({
provider: selectMapProvider(state),
});
export const ProviderDialog = ({ provider, changeProvider }: Props) => (
const mapDispatchToProps = {
mapSetProvider: MAP_ACTIONS.mapSetProvider,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const ProviderDialogUnconnected = ({ provider, mapSetProvider }: Props) => (
<div className="control-dialog top right control-dialog-provider">
<div className="helper provider-helper">
{
@ -19,8 +26,8 @@ export const ProviderDialog = ({ provider, changeProvider }: Props) => (
style={{
backgroundImage: `url(${replaceProviderUrl(item, { x: 5980, y: 2589, zoom: 13 })})`,
}}
onMouseDown={() => changeProvider(item)}
key={PROVIDERS[item].name}
onMouseDown={() => mapSetProvider(item)}
key={PROVIDERS[item]?.name}
>
{
provider === item &&
@ -34,3 +41,7 @@ export const ProviderDialog = ({ provider, changeProvider }: Props) => (
</div>
</div>
);
const ProviderDialog = connect(mapStateToProps, mapDispatchToProps)(ProviderDialogUnconnected)
export { ProviderDialog }

View file

@ -1,34 +1,30 @@
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import {
routerCancel as routerCancelAction,
routerSubmit as routerSubmitAction,
} from "$redux/user/actions";
import classnames from "classnames";
import React, { FC } from 'react';
import { Icon } from '~/components/panels/Icon';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import classnames from 'classnames';
import { connect } from 'react-redux';
import { selectEditor, selectEditorRouter, selectEditorDirection } from '~/redux/editor/selectors';
import pick from 'ramda/es/pick';
import { IState } from '~/redux/store';
type Props = {
routerPoints: number,
width: number,
is_routing: boolean,
routerCancel: typeof routerCancelAction,
routerSubmit: typeof routerSubmitAction,
}
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">
<Icon icon="icon-pin-1" />
<div className="big white upper">
Укажите первую точку на карте
</div>
<div className="big white upper">Укажите первую точку на карте</div>
</div>
</div>
<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="flex_1" />
<div className="helper__buttons">
<div className="button router-helper__button" onClick={editorRouterCancel}>
Отмена
</div>
</div>
@ -36,7 +32,11 @@ 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">
@ -45,9 +45,10 @@ const firstPoint = ({ routerCancel }: { routerCancel: typeof routerCancelAction
</div>
</div>
<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="flex_1" />
<div className="helper__buttons">
<div className="button router-helper__button" onClick={editorRouterCancel}>
Отмена
</div>
</div>
@ -56,10 +57,11 @@ 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">
@ -69,12 +71,13 @@ const draggablePoints = ({
</div>
</div>
<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="flex_1" />
<div className="helper__buttons button-group">
<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>
@ -82,14 +85,31 @@ const draggablePoints = ({
</React.Fragment>
);
export const RouterDialog = ({
routerPoints, routerCancel, routerSubmit, width, is_routing,
}: Props) => (
<div className="control-dialog" style={{ width }}>
<div className={classnames('save-loader', { active: is_routing })} />
const mapStateToProps = (state: IState) => ({
router: selectEditorRouter(state),
});
{!routerPoints && noPoints({ routerCancel })}
{routerPoints === 1 && firstPoint({ routerCancel })}
{routerPoints >= 2 && draggablePoints({ routerCancel, routerSubmit })}
const mapDispatchToProps = {
editorRouterCancel: EDITOR_ACTIONS.editorRouterCancel,
editorRouterSubmit: EDITOR_ACTIONS.editorRouterSubmit,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const RouterDialogUnconnected: FC<Props> = ({
router: { waypoints },
editorRouterCancel,
editorRouterSubmit,
}) => (
<div className="control-dialog control-dialog__medium">
<div className={classnames('save-loader')} />
{!waypoints.length && noPoints({ editorRouterCancel })}
{waypoints.length === 1 && firstPoint({ editorRouterCancel })}
{waypoints.length >= 2 && draggablePoints({ editorRouterCancel, editorRouterSubmit })}
</div>
);
const RouterDialog = connect(mapStateToProps, mapDispatchToProps)(RouterDialogUnconnected);
export { RouterDialog };

View file

@ -1,40 +1,46 @@
import * as React from 'react';
import { copyToClipboard, getUrlData } from '$utils/history';
import { toTranslit } from '$utils/format';
import { TIPS } from '$constants/tips';
import { MODES } from '$constants/modes';
import { Icon } from '$components/panels/Icon';
import { Switch } from '$components/Switch';
import React from 'react';
import { copyToClipboard, getUrlData } from '~/utils/history';
import { toTranslit, parseDesc } from '~/utils/format';
import { TIPS } from '~/constants/tips';
import { Icon } from '~/components/panels/Icon';
import { Switch } from '~/components/Switch';
import classnames from 'classnames';
import { IRootState } from "$redux/user/reducer";
import { sendSaveRequest, setMode } from "$redux/user/actions";
import { connect } from 'react-redux';
import { selectMap } from '~/redux/map/selectors';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import { selectEditorSave } from '~/redux/editor/selectors';
import { MODES } from '~/constants/modes';
interface Props extends IRootState {
width: number,
setMode: typeof setMode,
sendSaveRequest: typeof sendSaveRequest,
save_error: string,
const mapStateToProps = state => ({
map: selectMap(state),
save: selectEditorSave(state),
});
save_loading: boolean,
save_finished: boolean,
save_overwriting: boolean,
}
const mapDispatchToProps = {
editorCancelSave: EDITOR_ACTIONS.editorCancelSave,
editorChangeMode: EDITOR_ACTIONS.editorChangeMode,
editorSendSaveRequest: EDITOR_ACTIONS.editorSendSaveRequest,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & { };
interface State {
address: string,
title: string,
is_public: boolean,
address: string;
title: string;
is_public: boolean;
description: string;
}
export class SaveDialog extends React.Component<Props, State> {
constructor(props) {
class SaveDialogUnconnected extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
address: props.address || '',
title: props.title || '',
is_public: props.is_public || false,
address: props.map.address || '',
title: props.map.title || '',
is_public: props.map.is_public || false,
description: props.map.description || '',
};
}
@ -42,25 +48,38 @@ export class SaveDialog extends React.Component<Props, State> {
const { path } = getUrlData();
const { title, address } = this.state;
return toTranslit(address.trim()) || toTranslit(title.trim().toLowerCase()).substr(0, 32) || toTranslit(path.trim()).substr(0, 32);
return (
toTranslit(address.trim()) ||
toTranslit(title.trim().toLowerCase()).substr(0, 32) ||
toTranslit(path.trim()).substr(0, 32)
);
};
setTitle = ({ target: { value } }) => this.setState({ title: ((value && value.substr(0, 64)) || '') });
setTitle = ({ target: { value } }) =>
this.setState({ title: (value && value.substr(0, 64)) || '' });
setAddress = ({ target: { value } }) => this.setState({ address: (value && value.substr(0, 32) || '') });
setAddress = ({ target: { value } }) =>
this.setState({ address: (value && value.substr(0, 32)) || '' });
cancelSaving = () => this.props.setMode(MODES.NONE);
setDescription = ({ target: { value } }) =>
this.setState({ description: (value && value.substr(0, 256)) || '' });
sendSaveRequest = (e, force = false) => {
const { title, is_public } = this.state;
editorSendSaveRequest = (e, force = false) => {
const { title, is_public, description } = this.state;
const address = this.getAddress();
this.props.sendSaveRequest({
title, address, force, is_public
this.props.editorSendSaveRequest({
title,
address,
force,
is_public,
description,
});
};
forceSaveRequest = e => this.sendSaveRequest(e, true);
forceSaveRequest = e => this.editorSendSaveRequest(e, true);
cancelSaving = () => this.props.editorChangeMode(MODES.NONE);
onCopy = e => {
e.preventDefault();
@ -72,31 +91,45 @@ export class SaveDialog extends React.Component<Props, State> {
this.setState({ is_public: !this.state.is_public });
};
componentWillUnmount = () => {
this.props.editorCancelSave()
};
render() {
const { title, is_public } = this.state;
const { save_error, save_finished, save_overwriting, width, save_loading } = this.props;
const { title, is_public, description } = this.state;
const {
save: { error, finished, overwriting, loading },
} = this.props;
const { host, protocol } = getUrlData();
return (
<div className="control-dialog control-dialog-medium" style={{ width }}>
<div className="control-dialog control-dialog__medium">
<div className="helper save-helper">
<div className={classnames('save-loader', { active: save_loading })} />
<div className={classnames('save-loader', { active: loading })} />
<div className="save-title">
<div className="save-title-input">
<div className="save-title-label">Название</div>
<input type="text" value={title} onChange={this.setTitle} autoFocus readOnly={save_finished} />
<input
type="text"
value={title}
onChange={this.setTitle}
autoFocus
readOnly={finished}
/>
</div>
</div>
<div className="save-description">
<div className="save-address-input">
<label className="save-address-label">{protocol}//{host}/</label>
<label className="save-address-label">
{protocol}//{host}/
</label>
<input
type="text"
value={this.getAddress()}
onChange={this.setAddress}
readOnly={save_finished}
readOnly={finished}
onCopy={this.onCopy}
/>
<div className="save-address-copy" onClick={this.onCopy}>
@ -104,37 +137,49 @@ export class SaveDialog extends React.Component<Props, State> {
</div>
</div>
<div className="save-text">
{ save_error || TIPS.SAVE_INFO }
<div className="save-textarea">
<textarea
placeholder="Описание маршрута"
value={parseDesc(description)}
onChange={this.setDescription}
/>
</div>
<div className="save-text">{error || TIPS.SAVE_INFO}</div>
<div className="save-buttons">
<div className={classnames('save-buttons-text pointer', { gray: !is_public })} onClick={this.togglePublic}>
<div
className={classnames('save-buttons-text pointer', { gray: !is_public })}
onClick={this.togglePublic}
>
<Switch active={is_public} />
{
is_public
? ' В каталоге карт'
: ' Только по ссылке'
}
{is_public ? ' В каталоге карт' : ' Только по ссылке'}
</div>
<div>
{ !save_finished &&
<div className="button" onClick={this.cancelSaving}>Отмена</div>
}
{
!save_finished && !save_overwriting &&
<div className="button primary" onClick={this.sendSaveRequest}>Сохранить</div>
}
{
save_overwriting &&
<div className="button danger" onClick={this.forceSaveRequest}>Перезаписать</div>
}
{ save_finished &&
<div className="button" onClick={this.onCopy}>Скопировать</div>
}
{ save_finished &&
<div className="button success" onClick={this.cancelSaving}>Отлично!</div>
}
{!finished && (
<div className="button" onClick={this.cancelSaving}>
Отмена
</div>
)}
{!finished && !overwriting && (
<div className="button primary" onClick={this.editorSendSaveRequest}>
Сохранить
</div>
)}
{overwriting && (
<div className="button danger" onClick={this.forceSaveRequest}>
Перезаписать
</div>
)}
{finished && (
<div className="button" onClick={this.onCopy}>
Скопировать
</div>
)}
{finished && (
<div className="button success" onClick={this.cancelSaving}>
Отлично!
</div>
)}
</div>
</div>
</div>
@ -143,3 +188,7 @@ export class SaveDialog extends React.Component<Props, State> {
);
}
}
const SaveDialog = connect(mapStateToProps, mapDispatchToProps)(SaveDialogUnconnected);
export { SaveDialog };

View file

@ -1,13 +1,14 @@
import * as React from 'react';
import React from 'react';
import { connect } from 'react-redux';
import { selectEditorRenderer } from '~/redux/editor/selectors';
interface Props {
renderer: {
info: string,
progress: number,
}
}
const mapStateToProps = state => ({
renderer: selectEditorRenderer(state),
});
export const ShotPrefetchDialog = ({ renderer: { info, progress }}: Props) => (
type Props = ReturnType<typeof mapStateToProps> & {};
const ShotPrefetchDialogUnconnected = ({ renderer: { info, progress }}: Props) => (
<div className="control-dialog control-dialog-small left">
<div className="helper helper-prefetch">
<div className="dialog-prefetch-stage">{info}</div>
@ -19,3 +20,7 @@ export const ShotPrefetchDialog = ({ renderer: { info, progress }}: Props) => (
</div>
</div>
);
const ShotPrefetchDialog = connect(mapStateToProps)(ShotPrefetchDialogUnconnected);
export { ShotPrefetchDialog }

View file

@ -1,39 +1,44 @@
// @flow
import * as React from 'react';
import React from 'react';
import { STICKERS } from '$constants/stickers';
import { setActiveSticker as setActiveStickerAction } from "$redux/user/actions";
import { STICKERS } from '~/constants/stickers';
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) => (
<div className="control-dialog control-dialog-big" style={{ width }}>
type Props = ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps & {
width: number;
};
const StickersDialogUnconnected = ({ editorSetActiveSticker, width }: Props) => (
<div className="control-dialog control-dialog__medium" 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

@ -1,50 +1,144 @@
import * as React from 'react';
import { bindActionCreators } from "redux";
import React from 'react';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { getStyle } from '~/utils/dom';
import { nearestInt } from '~/utils/geom';
import { parseDesc } from '~/utils/format';
import { selectMap } from '~/redux/map/selectors';
import { selectEditor } from '~/redux/editor/selectors';
interface ITitleDialogProps {
editing: boolean,
title?: string,
}
const mapStateToProps = state => ({
editor: selectEditor(state),
map: selectMap(state),
});
interface ITitleDialogState {
type Props = ReturnType<typeof mapStateToProps> & {
minLines?: number;
maxLines?: number;
};
interface State {
raised: boolean;
height: number;
height_raised: number;
}
export class Component extends React.PureComponent<ITitleDialogProps, ITitleDialogState> {
export class TitleDialogUnconnected extends React.PureComponent<Props, State> {
state = {
raised: false,
height: 0,
height_raised: 0,
};
onHover = () => this.setState({ raised: true });
onLeave = () => this.setState({ raised: false });
componentDidMount() {
this.setMaxHeight();
}
componentDidUpdate() {
this.setMaxHeight();
}
setMaxHeight = () => {
if (!this.ref_sizer || !this.ref_title || !this.ref_text) return 0;
const { height: sizer_height } = this.ref_sizer.getBoundingClientRect();
const { height: title_height } = this.ref_title.getBoundingClientRect();
const { height: text_height } = this.ref_text.getBoundingClientRect();
if (text_height === 0) {
this.setState({ height: 0, height_raised: 0 });
return;
}
const title_margin = parseInt(getStyle(this.ref_title, 'margin-bottom'), 10) || 0;
const text_margins =
(parseInt(getStyle(this.ref_text, 'margin-top'), 10) || 0) +
parseInt(getStyle(this.ref_text, 'margin-bottom'), 10) || 0;
const text_line = parseInt(getStyle(this.ref_text, 'line-height'), 10) || 0;
const container_height = sizer_height - title_height - title_margin - text_margins;
const min_height = (this.props.minLines || 5) * text_line;
const max_height = (this.props.maxLines || 20) * text_line;
const height =
nearestInt(Math.min(container_height, Math.min(text_height, min_height)), text_line) +
text_margins;
const height_raised =
nearestInt(Math.min(container_height, Math.min(text_height, max_height)), text_line) +
text_margins;
this.setState({
height: height_raised - height < 2 * text_line ? height_raised : height,
height_raised,
});
};
render() {
const { editing, title } = this.props;
const {
editor: { editing },
map: { title, description },
} = this.props;
const { raised, height, height_raised } = this.state;
return (
<div className="title-dialog-wrapper">
<div className="title-dialog-sizer" ref={el => { this.sizer = el; }}>
<div className={classnames('title-dialog', { active: title && !editing })}>
<div className="title-dialog-pane title-dialog-name">
<div
className="title-dialog-sizer"
ref={el => {
this.ref_sizer = el;
}}
>
<div
className={classnames('title-dialog', { active: title && !editing })}
onMouseOver={this.onHover}
onMouseOut={this.onLeave}
>
<div
className="title-dialog-pane title-dialog-name"
ref={el => {
this.ref_title = el;
}}
>
<h2>{title}</h2>
</div>
<div className="title-dialog-pane title-dialog-text">
Давно выяснено, что при оценке дизайна и композиции читаемый текст мешает сосредоточиться. Lorem Ipsum используют потому, что тот обеспечивает более или менее стандартное заполнение шаблона, а также реальное распределение букв и пробелов в абзацах, которое не получается при простой дубликации "Здесь ваш текст.. Здесь ваш текст.. Здесь ваш текст.." Многие программы электронной вёрстки и редакторы HTML используют Lorem Ipsum в качестве текста по умолчанию, так что поиск по
<div
className={classnames('title-dialog-pane title-dialog-text', {
has_shade: height_raised > height,
})}
style={{
height: raised ? height_raised : height,
marginBottom: height === 0 ? 0 : 15,
}}
ref={el => {
this.ref_overflow = el;
}}
>
<div
ref={el => {
this.ref_text = el;
}}
>
{parseDesc(description)}
</div>
</div>
</div>
</div>
</div>
)
);
}
text;
sizer;
ref_sizer;
ref_title;
ref_text;
ref_overflow;
}
const mapStateToProps = ({ user: { editing, title } }) => ({ editing, title });
const mapDispatchToProps = dispatch => bindActionCreators({ }, dispatch);
const TitleDialog = connect(mapStateToProps)(TitleDialogUnconnected);
export const TitleDialog = connect(mapStateToProps, mapDispatchToProps)(Component);
export { TitleDialog };

View file

@ -1,43 +1,59 @@
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import React, { FC } from 'react';
import { connect } from 'react-redux';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
type Props = {
clearPoly: () => void,
clearStickers: () => void,
clearAll: () => void,
clearCancel: () => void,
const mapStateToProps = () => ({});
const mapDispatchToProps = {
editorClearPoly: EDITOR_ACTIONS.editorClearPoly,
editorClearStickers: EDITOR_ACTIONS.editorClearStickers,
editorClearAll: EDITOR_ACTIONS.editorClearAll,
editorClearCancel: EDITOR_ACTIONS.editorClearCancel,
};
width: number,
}
type Props = ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps & {
width: number;
};
export const TrashDialog = ({
clearPoly, clearStickers, clearAll, clearCancel, width,
}: Props) => (
<div className="control-dialog" style={{ width }}>
<div className="helper trash-helper">
const TrashDialogUnconnected: FC<Props> = ({
editorClearPoly,
editorClearStickers,
editorClearAll,
editorClearCancel,
width,
}) => (
<div className="control-dialog control-dialog__medium" style={{ width }}>
<div className="helper trash-helper desktop-only">
<div className="helper__text danger">
<Icon icon="icon-trash-4" />
<div className="big upper desktop-only">Удалить:</div>
<div className="big upper desktop-only">Все изменения будут удалены!</div>
</div>
</div>
<div className="helper trash-helper">
<div className="helper__buttons flex_1 trash-buttons">
<div className="button-group">
<div className="button router-helper__button" onClick={clearPoly}>
<div className="button router-helper__button" onClick={editorClearPoly}>
Маршрут
</div>
<div className="button router-helper__button" onClick={clearStickers}>
<div className="button router-helper__button" onClick={editorClearStickers}>
Стикеры
</div>
<div className="button router-helper__button" onClick={clearAll}>
<div className="button router-helper__button" onClick={editorClearAll}>
ВСЕ
</div>
</div>
<div className="flex_1" />
<div className="button primary router-helper__button" onClick={clearCancel}>
<div className="button primary router-helper__button" onClick={editorClearCancel}>
Отмена
</div>
</div>
</div>
</div>
);
const TrashDialog = connect(mapStateToProps, mapDispatchToProps)(TrashDialogUnconnected);
export { TrashDialog };

View file

@ -0,0 +1,21 @@
import React, { FC } from 'react';
interface IProps {}
const GpxConfirm: FC<IProps> = ({}) => {
return (
<div className="gpx-confirm">
<div className="gpx-confirm__text">Маршрут уже нанесен. Что делаем?</div>
<div className="gpx-confirm__buttons">
<div className="button success">Соединить</div>
<div className="button danger">Переписать</div>
<div className="button primary">Отмена</div>
</div>
</div>
);
};
export { GpxConfirm };

View file

@ -0,0 +1,60 @@
import React, { FC, memo } from 'react';
import { IGpxRoute } from '~/redux/editor';
import { Switch } from '../Switch';
import { Icon } from '../panels/Icon';
import classnames from 'classnames';
interface IProps {
item: IGpxRoute;
index: number;
enabled: boolean;
onFocusRoute: (i: number) => void;
onRouteDrop: (i: number) => void;
onRouteToggle: (i: number) => void;
onRouteColor: (i: number) => void;
onRouteReplace: (i: number) => void;
}
const GpxDialogRow: FC<IProps> = memo(
({
item,
index,
enabled,
onRouteToggle,
onFocusRoute,
onRouteDrop,
onRouteColor,
onRouteReplace,
}) => {
return (
<div className={classnames('gpx-row', { 'gpx-row_disabled': !enabled || !item.enabled })}>
<div
className="gpx-row__color"
style={{ backgroundColor: item.color }}
onClick={() => onRouteColor(index)}
/>
<div className="gpx-row__title" onClick={() => onFocusRoute(index)}>
{item.name}
</div>
<div className="gpx-row__buttons">
<div onClick={() => onRouteReplace(index)}>
<Icon icon="icon-to-poly" size={24} />
</div>
<div onClick={() => onRouteDrop(index)}>
<Icon icon="icon-trash-6" size={24} />
</div>
<div>
<Switch active={item.enabled} onPress={() => onRouteToggle(index)} />
</div>
</div>
</div>
);
}
);
export { GpxDialogRow };

View file

@ -1,21 +1,21 @@
import * as React from 'react';
import { LOGOS } from '$constants/logos';
import React from 'react';
import { LOGOS } from '~/constants/logos';
import { connect } from 'react-redux';
import { IRootState } from "$redux/user/reducer";
import { IRootState } from '~/redux/user';
import { selectMapLogo } from '~/redux/map/selectors';
interface Props extends IRootState {}
const mapStateToProps = state => ({ logo: selectMapLogo(state) });
type Props = ReturnType<typeof mapStateToProps>;
const Component = ({ logo }: Props) => (
const LogoPreviewUnconnected = React.memo(({ logo }: Props) => (
<div
className="logo-preview"
style={{
backgroundImage: logo
? `url(${LOGOS[logo][1]})`
: 'none'
backgroundImage: logo ? `url(${LOGOS && LOGOS[logo] && LOGOS[logo][1]})` : 'none',
}}
/>
);
));
const mapStateToProps = ({ user: { logo } }) => ({ logo });
const LogoPreview = connect(mapStateToProps)(LogoPreviewUnconnected);
export const LogoPreview = connect(mapStateToProps)(Component);
export { LogoPreview };

View file

@ -1,29 +1,25 @@
// @flow
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import { MapListDialog } from "$components/dialogs/MapListDialog";
import { Tooltip } from "$components/panels/Tooltip";
import { ReactElement } from "react";
import React, { FC, memo } from 'react';
import { MapListDialog } from '~/components/dialogs/MapListDialog';
import { ReactElement } from 'react';
interface Props {
_id: string,
stopEditing: typeof MapListDialog.stopEditing,
dropRoute: typeof MapListDialog.dropRoute,
address: string;
stopEditing: typeof MapListDialog.stopEditing;
dropRoute: typeof MapListDialog.dropRoute;
}
export const RouteRowDrop = ({
_id, stopEditing, dropRoute,
}: Props): ReactElement<Props, null> => (
<div
className="route-row-drop"
>
<div
className="route-row"
>
export const RouteRowDrop: FC<Props> = memo(({ address, stopEditing, dropRoute }) => (
<div className="route-row-drop">
<div className="route-row">
<div className="button-group">
<div className="button" onClick={dropRoute.bind(null, _id)}>Удалить</div>
<div className="button primary" onClick={stopEditing}>Отмена</div>
<div className="button" onClick={dropRoute.bind(null, address)}>
Удалить
</div>
<div className="button primary" onClick={stopEditing}>
Отмена
</div>
</div>
</div>
</div>
);
));

View file

@ -1,12 +1,12 @@
// @flow
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import { Switch } from '$components/Switch';
import { MapListDialog } from "$components/dialogs/MapListDialog";
import React from 'react';
import { Icon } from '~/components/panels/Icon';
import { Switch } from '~/components/Switch';
import { MapListDialog } from "~/components/dialogs/MapListDialog";
interface Props {
title: string;
_id: string;
address: string;
is_public: boolean,
modifyRoute: typeof MapListDialog.modifyRoute,
}
@ -29,10 +29,10 @@ export class RouteRowEditor extends React.Component<Props, State> {
stopEditing = () => {
const {
state: { title, is_public },
props: { _id }
props: { address }
} = this;
this.props.modifyRoute({ _id, title, is_public })
this.props.modifyRoute({ address, title, is_public })
};
setPublic = () => this.setState({ is_public: !this.state.is_public });

View file

@ -1,72 +1,100 @@
// @flow
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import { MapListDialog } from "$components/dialogs/MapListDialog";
import { Tooltip } from "$components/panels/Tooltip";
import React from "react";
import { Icon } from "~/components/panels/Icon";
import { MapListDialog } from "~/components/dialogs/MapListDialog";
import { Tooltip } from "~/components/panels/Tooltip";
import { ReactElement } from "react";
import classnames from 'classnames';
import classnames from "classnames";
import { toggleRouteStarred } from "~/redux/user/actions";
import { TABS } from "~/constants/dialogs";
interface Props {
_id: string,
tab: string,
title: string,
distance: number,
is_public: boolean,
tab: string;
openRoute: typeof MapListDialog.openRoute,
startEditing: typeof MapListDialog.startEditing,
stopEditing: typeof MapListDialog.stopEditing,
showMenu: typeof MapListDialog.showMenu,
hideMenu: typeof MapListDialog.hideMenu,
showDropCard: typeof MapListDialog.showDropCard,
address: string;
title: string;
distance: number;
is_public: boolean;
is_admin: boolean;
is_published: boolean;
openRoute: typeof MapListDialog.openRoute;
toggleStarred: typeof MapListDialog.toggleStarred;
startEditing: typeof MapListDialog.startEditing;
stopEditing: typeof MapListDialog.stopEditing;
showMenu: typeof MapListDialog.showMenu;
hideMenu: typeof MapListDialog.hideMenu;
showDropCard: typeof MapListDialog.showDropCard;
}
export const RouteRowView = ({
title, distance, _id, openRoute, tab, startEditing, showMenu, showDropCard, hideMenu,
}: Props): ReactElement<Props, null> => (
<div
className={classnames('route-row-view', { has_menu: (tab === 'mine') })}
>
<div
className="route-row"
onClick={() => openRoute(_id)}
>
title,
distance,
address,
openRoute,
tab,
startEditing,
showMenu,
showDropCard,
hideMenu,
is_admin,
is_published,
toggleStarred
}: Props): ReactElement<Props> => (
<div className={classnames("route-row-view", { has_menu: tab === "my" })}>
{(tab === TABS.PENDING || tab === TABS.STARRED) && is_admin && (
<div className="route-row-fav" onClick={toggleStarred.bind(null, address)}>
{is_published ? (
<Icon icon="icon-star-fill" size={24} />
) : (
<Icon icon="icon-star-blank" size={24} />
)}
</div>
)}
<div className="route-row" onClick={() => openRoute(address)}>
<div className="route-title">
<span>{(title || _id)}</span>
{(tab === "my" || !is_admin) && is_published && (
<div className="route-row-corner">
<Icon icon="icon-star-fill" size={18} />
</div>
)}
<span>{title || address}</span>
</div>
<div className="route-description">
<span>
<Icon icon="icon-link-1" />
{_id}
{address}
</span>
<span>
<Icon icon="icon-cycle-1" />
{(distance && `${distance} km`) || '0 km'}
{(distance && `${distance} km`) || "0 km"}
</span>
</div>
</div>
{
tab === 'mine' &&
<React.Fragment>
<div
className="route-row-edit-button pointer"
onMouseOver={showMenu.bind(null, _id)}
onMouseOut={hideMenu}
>
<Icon icon="icon-more-vert" />
<div className="route-row-edit-menu pointer">
<div onMouseDown={showDropCard.bind(null, _id)}>
<Tooltip>Удалить</Tooltip>
<Icon icon="icon-trash-3" size={32} />
</div>
<div onMouseDown={startEditing.bind(null, _id)} className="modify-button">
<Tooltip>Редактировать</Tooltip>
<Icon icon="icon-edit-1" size={32} />
</div>
{tab === "my" && (
<React.Fragment>
<div
className="route-row-edit-button pointer"
onMouseOver={showMenu.bind(null, address)}
onMouseOut={hideMenu}
>
<Icon icon="icon-more-vert" />
<div className="route-row-edit-menu pointer">
<div onMouseDown={showDropCard.bind(null, address)}>
<Tooltip>Удалить</Tooltip>
<Icon icon="icon-trash-3" size={32} />
</div>
<div
onMouseDown={startEditing.bind(null, address)}
className="modify-button"
>
<Tooltip>Редактировать</Tooltip>
<Icon icon="icon-edit-1" size={32} />
</div>
</div>
</React.Fragment>
}
</div>
</React.Fragment>
)}
</div>
);

View file

@ -1,76 +1,93 @@
import * as React from 'react';
import React, { FC, memo } from 'react';
import classnames from 'classnames';
import { MapListDialog } from "$components/dialogs/MapListDialog";
import { RouteRowView } from "$components/maps/RouteRowView";
import { RouteRowEditor } from "$components/maps/RouteRowEditor";
import { RouteRowDrop } from "$components/maps/RouteRowDrop";
import { ReactElement } from "react";
import { MapListDialog } from '~/components/dialogs/MapListDialog';
import { RouteRowView } from '~/components/maps/RouteRowView';
import { RouteRowEditor } from '~/components/maps/RouteRowEditor';
import { RouteRowDrop } from '~/components/maps/RouteRowDrop';
import { ReactElement } from 'react';
interface Props {
_id: string,
tab: string,
title: string,
distance: number,
is_public: boolean,
address: string;
tab: string;
title: string;
distance: number;
is_public: boolean;
is_published: boolean;
is_editing_target: boolean,
is_menu_target: boolean,
is_admin: boolean;
is_editing_target: boolean;
is_menu_target: boolean;
openRoute: typeof MapListDialog.openRoute,
startEditing: typeof MapListDialog.startEditing,
stopEditing: typeof MapListDialog.stopEditing,
showMenu: typeof MapListDialog.showMenu,
hideMenu: typeof MapListDialog.hideMenu,
showDropCard: typeof MapListDialog.showDropCard,
dropRoute: typeof MapListDialog.dropRoute,
modifyRoute: typeof MapListDialog.modifyRoute,
openRoute: typeof MapListDialog.openRoute;
startEditing: typeof MapListDialog.startEditing;
stopEditing: typeof MapListDialog.stopEditing;
showMenu: typeof MapListDialog.showMenu;
hideMenu: typeof MapListDialog.hideMenu;
showDropCard: typeof MapListDialog.showDropCard;
dropRoute: typeof MapListDialog.dropRoute;
modifyRoute: typeof MapListDialog.modifyRoute;
toggleStarred: typeof MapListDialog.toggleStarred;
is_editing_mode: 'edit' | 'drop',
is_editing_mode: 'edit' | 'drop';
}
export const RouteRowWrapper = ({
title, distance, _id, openRoute, tab, startEditing, showMenu,
showDropCard, is_public, is_editing_target, is_menu_target, is_editing_mode,
dropRoute, stopEditing, modifyRoute, hideMenu,
}: Props): ReactElement<Props, null> => (
<div
className={classnames('route-row-wrapper', {
is_menu_target,
is_editing_target,
})}
>
{
is_editing_target && is_editing_mode === 'edit' &&
export const RouteRowWrapper: FC<Props> = memo(
({
title,
distance,
address,
openRoute,
tab,
startEditing,
showMenu,
showDropCard,
is_public,
is_editing_target,
is_menu_target,
is_editing_mode,
dropRoute,
stopEditing,
modifyRoute,
hideMenu,
is_admin,
is_published,
toggleStarred,
}) => (
<div
className={classnames('route-row-wrapper', {
is_menu_target,
is_editing_target,
})}
>
{is_editing_target && is_editing_mode === 'edit' && (
<RouteRowEditor
title={title}
_id={_id}
address={address}
is_public={is_public}
modifyRoute={modifyRoute}
/>
}
{
is_editing_target && is_editing_mode === 'drop' &&
<RouteRowDrop
_id={_id}
dropRoute={dropRoute}
stopEditing={stopEditing}
/>
}
{
!is_editing_target &&
)}
{is_editing_target && is_editing_mode === 'drop' && (
<RouteRowDrop address={address} dropRoute={dropRoute} stopEditing={stopEditing} />
)}
{!is_editing_target && (
<RouteRowView
_id={_id}
address={address}
tab={tab}
title={title}
distance={distance}
is_public={is_public}
is_published={is_published}
openRoute={openRoute}
startEditing={startEditing}
stopEditing={stopEditing}
showMenu={showMenu}
hideMenu={hideMenu}
showDropCard={showDropCard}
is_admin={is_admin}
toggleStarred={toggleStarred}
/>
}
</div>
)}
</div>
)
);

View file

@ -0,0 +1,21 @@
import React, { FC, useCallback } from 'react';
import { INominatimResult } from '~/redux/types';
import { MainMap } from '~/constants/map';
interface IProps {
item: INominatimResult;
}
const NominatimListItem: FC<IProps> = ({ item }) => {
const onClick = useCallback(() => {
MainMap.panTo(item.latlng);
}, [MainMap]);
return (
<div onClick={onClick} className="nominatim-list-item">
<div className="title">{item.title}</div>
</div>
);
};
export { NominatimListItem };

View file

@ -1,25 +1,27 @@
// flow
import * as React from 'react';
import { toHours } from '$utils/format';
import { Icon } from '$components/panels/Icon';
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 * as Slider from 'rc-slider/lib/Slider';
import { bindActionCreators } from 'redux';
import { setSpeed } from '$redux/user/actions';
import { IRootState } from "$redux/user/reducer";
import { Tooltip } from "$components/panels/Tooltip";
import { isMobile } from "$utils/window";
import Slider from 'rc-slider/lib/Slider';
import { editorSetSpeed } from '~/redux/editor/actions';
import { Tooltip } from '~/components/panels/Tooltip';
import { isMobile } from '~/utils/window';
import { IState } from '~/redux/store';
import pick from 'ramda/es/pick';
import { selectEditor } from '~/redux/editor/selectors';
interface Props extends IRootState {
setSpeed: typeof setSpeed,
}
const mapStateToProps = (state: IState) =>
pick(['distance', 'estimated', 'speed'], selectEditor(state));
const mapDispatchToProps = { editorSetSpeed };
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
interface State {
dialogOpened: boolean,
dialogOpened: boolean;
}
class Component extends React.PureComponent<Props, State> {
class DistanceBarUnconnected extends React.PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
@ -31,10 +33,15 @@ class Component extends React.PureComponent<Props, State> {
min: number = 5;
max: number = 30;
marks: { [x: number]: string } = [...Array((Math.floor(this.max - this.min) / this.step) + 1)].reduce((obj, el, index) => ({
...obj,
[this.min + (index * this.step)]: String(this.min + (index * this.step)),
}), { });
marks: { [x: number]: string } = [
...Array(Math.floor(this.max - this.min) / this.step + 1),
].reduce(
(obj, el, index) => ({
...obj,
[this.min + index * this.step]: String(this.min + index * this.step),
}),
{}
);
toggleDialog = () => {
if (isMobile()) return;
@ -46,10 +53,12 @@ class Component extends React.PureComponent<Props, State> {
const {
props: { distance, estimated, speed },
state: { dialogOpened },
min, max, step, marks,
min,
max,
step,
marks,
} = this;
return (
<React.Fragment>
<div className="status-bar padded pointer tooltip-container" onClick={this.toggleDialog}>
@ -60,40 +69,24 @@ class Component extends React.PureComponent<Props, State> {
</span>
<div className="desktop-only">{toHours(estimated)}</div>
</div>
{
dialogOpened &&
{dialogOpened && (
<div className="control-dialog top left" style={{ left: 0, top: 42 }}>
<div className="helper speed-helper">
<Slider
min={min}
max={max}
step={step}
onChange={this.props.setSpeed}
onChange={this.props.editorSetSpeed}
defaultValue={15}
value={speed}
marks={marks}
/>
</div>
</div>
}
)}
</React.Fragment>
);
}
}
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
)(Component);
export const DistanceBar = connect(mapStateToProps, mapDispatchToProps)(DistanceBarUnconnected);

View file

@ -1,38 +1,26 @@
import * as React from 'react';
import { IModes, MODES } from '$constants/modes';
import React, { createElement } from 'react';
import { MODES } from '~/constants/modes';
import { RouterDialog } from '$components/dialogs/RouterDialog';
import { StickersDialog } from '$components/dialogs/StickersDialog';
import { TrashDialog } from '$components/dialogs/TrashDialog';
import { LogoDialog } from '$components/dialogs/LogoDialog';
import { SaveDialog } from '$components/dialogs/SaveDialog';
import { CancelDialog } from '$components/dialogs/CancelDialog';
import { RouterDialog } from '~/components/dialogs/RouterDialog';
import { PolylineDialog } from '~/components/dialogs/PolylineDialog';
import { StickersDialog } from '~/components/dialogs/StickersDialog';
import { TrashDialog } from '~/components/dialogs/TrashDialog';
import { LogoDialog } from '~/components/dialogs/LogoDialog';
import { SaveDialog } from '~/components/dialogs/SaveDialog';
import { CancelDialog } from '~/components/dialogs/CancelDialog';
import { GpxDialog } from '~/components/dialogs/GpxDialog';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {
setMode,
setLogo,
routerCancel,
routerSubmit,
setActiveSticker,
clearStickers,
clearPoly,
clearAll,
clearCancel,
stopEditing,
setEditing,
sendSaveRequest,
changeProvider,
} from '$redux/user/actions';
import { ProviderDialog } from '$components/dialogs/ProviderDialog';
import { ShotPrefetchDialog } from '$components/dialogs/ShotPrefetchDialog';
import { IRootState } from "$redux/user/reducer";
import { ProviderDialog } from '~/components/dialogs/ProviderDialog';
import { ShotPrefetchDialog } from '~/components/dialogs/ShotPrefetchDialog';
import { selectEditorMode } from '~/redux/editor/selectors';
interface Props extends IRootState {
width: number,
}
const mapStateToProps = state => ({ mode: selectEditorMode(state) });
type Props = ReturnType<typeof mapStateToProps> & {
width: number;
};
const DIALOG_CONTENTS: { [x: string]: any } = {
[MODES.ROUTER]: RouterDialog,
@ -43,34 +31,13 @@ const DIALOG_CONTENTS: { [x: string]: any } = {
[MODES.CONFIRM_CANCEL]: CancelDialog,
[MODES.PROVIDER]: ProviderDialog,
[MODES.SHOT_PREFETCH]: ShotPrefetchDialog,
[MODES.POLY]: PolylineDialog,
[MODES.GPX]: GpxDialog,
};
export const Component = (props: Props) => (
props.mode && DIALOG_CONTENTS[props.mode]
? React.createElement(DIALOG_CONTENTS[props.mode], { ...props })
: null
);
const EditorDialogUnconnected = (props: Props) =>
props.mode && DIALOG_CONTENTS[props.mode] ? createElement(DIALOG_CONTENTS[props.mode]) : null;
const mapStateToProps = ({ user }) => ({ ...user });
const mapDispatchToProps = dispatch => bindActionCreators({
routerCancel,
routerSubmit,
setLogo,
setActiveSticker,
clearStickers,
clearPoly,
clearAll,
clearCancel,
stopEditing,
setEditing,
setMode,
sendSaveRequest,
changeProvider,
}, dispatch);
export const EditorDialog = connect(
mapStateToProps,
mapDispatchToProps
)(Component);
const EditorDialog = connect(mapStateToProps)(EditorDialogUnconnected);
export { EditorDialog };

View file

@ -1,26 +1,57 @@
import * as React from 'react';
import { MODES } from '$constants/modes';
import React, { PureComponent } from 'react';
import { MODES } from '~/constants/modes';
import classnames from 'classnames';
import { Icon } from '$components/panels/Icon';
import { EditorDialog } from '$components/panels/EditorDialog';
import { bindActionCreators } from 'redux';
import { Icon } from '~/components/panels/Icon';
import { EditorDialog } from '~/components/panels/EditorDialog';
import { connect } from 'react-redux';
import { setMode, startEditing, stopEditing, setLogo, takeAShot, keyPressed } from '$redux/user/actions';
import { IRootState } from "$redux/user/reducer";
import { Tooltip } from "$components/panels/Tooltip";
import {
editorChangeMode,
editorKeyPressed,
editorRedo,
editorStartEditing,
editorStopEditing,
editorTakeAShot,
editorUndo,
} from '~/redux/editor/actions';
import { Tooltip } from '~/components/panels/Tooltip';
import { IState } from '~/redux/store';
import { selectEditor } from '~/redux/editor/selectors';
import { selectMap } from '~/redux/map/selectors';
interface Props extends IRootState {
routing: IRootState['features']['routing'],
setMode: typeof setMode,
startEditing: typeof startEditing,
stopEditing: typeof stopEditing,
keyPressed: EventListenerOrEventListenerObject,
}
const mapStateToProps = (state: IState) => {
const { mode, changed, editing, features, history } = selectEditor(state);
const { route, stickers } = selectMap(state);
return {
mode,
changed,
editing,
features,
history,
route,
stickers,
};
};
class Component extends React.PureComponent<Props, void> {
const mapDispatchToProps = {
editorChangeMode,
editorStartEditing,
editorStopEditing,
editorTakeAShot,
editorKeyPressed,
editorUndo,
editorRedo,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
class EditorPanelUnconnected extends PureComponent<Props, void> {
componentDidMount() {
window.addEventListener('keydown', this.props.keyPressed);
if (!this.panel) {
return;
}
window.addEventListener('keydown', this.onKeyPress as any);
const obj = document.getElementById('control-dialog');
const { width } = this.panel.getBoundingClientRect();
@ -30,32 +61,82 @@ class Component extends React.PureComponent<Props, void> {
obj.style.width = String(width);
}
panel: HTMLElement = null;
panel: HTMLDivElement | null = null;
componentWillUnmount() {
window.removeEventListener('keydown', this.props.keyPressed);
window.removeEventListener('keydown', this.onKeyPress 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);
onKeyPress = event => {
if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') return;
this.props.editorKeyPressed(event);
};
startPolyMode = () => this.props.editorChangeMode(MODES.POLY);
startStickerMode = () => this.props.editorChangeMode(MODES.STICKERS_SELECT);
startRouterMode = () => this.props.editorChangeMode(MODES.ROUTER);
startTrashMode = () => this.props.editorChangeMode(MODES.TRASH);
startSaveMode = () => {
// if (!this.props.changed) return;
this.props.setMode(MODES.SAVE);
this.props.editorChangeMode(MODES.SAVE);
};
render() {
const {
mode, changed, editing, routing,
mode,
changed,
editing,
features: { routing },
history: { records, position },
route,
stickers,
} = this.props;
const can_undo = records.length > 0 && position > 0;
const can_redo = records.length && records.length - 1 > position;
const can_clear = route.length > 0 || stickers.length > 0;
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={classnames('secondary-bar secondary-bar__undo', {
active: can_undo || can_redo || can_clear,
})}
>
<button className={classnames('undo-button', { inactive: !can_undo })} onClick={this.props.editorUndo}>
<Tooltip>Отмена (z)</Tooltip>
<Icon icon="icon-undo" size={24} />
</button>
<button
className={classnames('undo-button', {
inactive: !can_redo,
})}
onClick={this.props.editorRedo}
>
<Tooltip>Вернуть (x)</Tooltip>
<Icon icon="icon-redo" size={24} />
</button>
<button
className={classnames({
inactive: !can_clear,
})}
onClick={this.startTrashMode}
>
<Tooltip>Очистить (c)</Tooltip>
<Icon icon="icon-trash-4" size={24} />
</button>
</div>
<div className="control-bar control-bar-padded">
{
routing &&
{routing && (
<button
className={classnames({ active: mode === MODES.ROUTER })}
onClick={this.startRouterMode}
@ -63,8 +144,7 @@ class Component extends React.PureComponent<Props, void> {
<Tooltip>Автоматический маршрут</Tooltip>
<Icon icon="icon-route-2" />
</button>
}
)}
<button
className={classnames({ active: mode === MODES.POLY })}
@ -75,34 +155,20 @@ 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" />
<div className="control-bar control-bar-padded">
<button
className={classnames({ active: mode === MODES.TRASH })}
onClick={this.startTrashMode}
>
<Tooltip>Удаление элементов</Tooltip>
<Icon icon="icon-trash-4" />
</button>
</div>
<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 +180,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,13 +1,12 @@
import * as React from 'react';
import React, { memo } from 'react';
export const Icon = ({ icon, size = 32 }: { icon: string, size?: number }) => (
export const Icon = memo(({ icon, size = 32 }: { icon: string; size?: number }) => (
<svg width={size} height={size} viewBox="0 0 32 32">
<defs>
<mask id={`icon-mask-${icon}`}>
<use xlinkHref={`${require('$sprites/icon.svg')}#${icon}`} x={0} y={0} />
<use xlinkHref={`/images/icon.svg#${icon}`} x={0} y={0} />
</mask>
</defs>
<rect x="0" y="0" width="32" height="32" stroke="none" mask={`url(#icon-mask-${icon})`} />
</svg>
);
));

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import React from 'react';
import { Icon } from '~/components/panels/Icon';
type Props = {
onCancel: () => void,

View file

@ -1,4 +1,4 @@
import * as React from 'react';
import React from 'react';
import classnames from 'classnames';
export const Tooltip = ({ children, position = 'bottom' }: { children: string, position?: string }) => (

Some files were not shown because too many files have changed in this diff Show more