UNPKG

15.2 kBJavaScriptView Raw
1import React from 'react';
2import moment from 'moment';
3import { getBounds } from './helpers';
4import { cmap } from './render';
5
6export function getFeatures(features) {
7 var keys = Object.keys(features);
8 return keys.map(item => features[item]);
9}
10
11export function processFeatures(features) {
12 const finalReport = new Map();
13 const analyzedFeatures = features.map(feature =>
14 analyzeFeature(feature[0], feature[1])
15 );
16 const keys = ['addedTags', 'changedValues', 'deletedTags'];
17 analyzedFeatures.map(item =>
18 keys.map(key =>
19 item.get(key).forEach(tag => {
20 if (finalReport.get(tag)) {
21 finalReport.set(tag, finalReport.get(tag).concat([item.get('id')]));
22 } else {
23 finalReport.set(tag, [item.get('id')]);
24 }
25 })
26 )
27 );
28 return finalReport;
29}
30
31export function analyzeFeature(newVersion, oldVersion) {
32 var oldVersionKeys = Object.keys(oldVersion.properties.tags);
33 var newVersionKeys = Object.keys(newVersion.properties.tags);
34 var addedTags = newVersionKeys.filter(
35 tag => oldVersionKeys.indexOf(tag) === -1
36 );
37 var deletedTags = oldVersionKeys.filter(
38 tag => newVersionKeys.indexOf(tag) === -1
39 );
40 var changedValues = newVersionKeys
41 .filter(
42 tag => addedTags.indexOf(tag) === -1 && deletedTags.indexOf(tag) === -1
43 )
44 .filter(
45 tag => newVersion.properties.tags[tag] !== oldVersion.properties.tags[tag]
46 );
47 var result = new Map();
48 result
49 .set('id', newVersion.properties.id)
50 .set('addedTags', addedTags.map(tag => `Added tag ${tag}`))
51 .set('deletedTags', deletedTags.map(tag => `Deleted tag ${tag}`))
52 .set(
53 'changedValues',
54 changedValues.map(tag => `Changed value of tag ${tag}`)
55 );
56 return result;
57}
58
59export function selectFeature(id: number) {
60 if (!id || !cmap) return;
61 cmap.emit('selectFeature', 'node|way', id);
62}
63
64function FeatureListItem(props) {
65 return (
66 <li>
67 <span className="cmap-pointer " onClick={() => selectFeature(props.id)}>
68 {props.id}
69 </span>
70 </li>
71 );
72}
73
74export class ChangeItem extends React.PureComponent {
75 constructor(props) {
76 super(props);
77 this.state = {
78 opened: false
79 };
80 this.tag = props.change[0];
81 this.value = props.change[1];
82 this.handleChange = this.handleChange.bind(this);
83 }
84 handleChange() {
85 this.setState({ opened: !this.state.opened });
86 }
87 render() {
88 return (
89 <div>
90 <h7
91 className="cmap-sub-heading cmap-pointer"
92 onClick={this.handleChange}
93 >
94 {this.state.opened ? '▼' : '▶'}
95 {this.tag}
96 </h7>
97 <ul
98 className="cmap-vlist"
99 style={{
100 display: this.state.opened ? 'block' : 'none'
101 }}
102 >
103 {this.value.map((id, k) => <FeatureListItem id={id} key={k} />)}
104 </ul>
105 </div>
106 );
107 }
108}
109
110export class Sidebar extends React.PureComponent {
111 constructor(props) {
112 super(props);
113 this.state = {
114 actions: true,
115 type: false,
116 changes: false,
117 mapStyle: false,
118 user: false
119 };
120 this.changeReport = [];
121 this.changedFeatures = processFeatures(
122 getFeatures(this.props.result.featureMap).filter(
123 item => item.length === 2 && item[0].properties.action === 'modify'
124 )
125 ).forEach((featureIDs, tag) => this.changeReport.push([tag, featureIDs]));
126
127 this.toggleUser = this.toggleUser.bind(this);
128 this.toggleActions = this.toggleActions.bind(this);
129 this.toggleType = this.toggleType.bind(this);
130 this.toggleChanges = this.toggleChanges.bind(this);
131 this.toggleMapStyle = this.toggleMapStyle.bind(this);
132 }
133 toggleUser() {
134 this.setState({
135 user: !this.state.user
136 });
137 }
138 toggleActions() {
139 this.setState({
140 actions: !this.state.actions
141 });
142 }
143 toggleType() {
144 this.setState({
145 type: !this.state.type
146 });
147 }
148 toggleChanges() {
149 this.setState({
150 changes: !this.state.changes
151 });
152 }
153 toggleMapStyle() {
154 this.setState({
155 mapStyle: !this.state.mapStyle
156 });
157 }
158 render() {
159 const result = this.props.result;
160 const changesetId = this.props.changesetId;
161 const filterLayers = this.props.filterLayers;
162 var date = new Date(
163 result.changeset.to ? result.changeset.to : result.changeset.from
164 );
165
166 var bbox = result.changeset.bbox;
167 var bounds = getBounds(bbox);
168 var center = bounds.getCenter();
169 var userName = result.changeset.user;
170 var userId = result.changeset.uid;
171 return (
172 <div className="cmap-sidebar">
173 <section className="cmap-changeset-section cmap-fill-light cmap-pt3">
174 <h6 className="cmap-heading">
175 Changeset:
176 <em className="cmap-changeset-id">{changesetId}</em>
177 <small className="cmap-time" title={date}>
178 ({moment(date).fromNow()})
179 </small>
180 </h6>
181 <ul className="cmap-hlist">
182 <li>
183 <a
184 target="_blank"
185 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-c-link-osm"
186 href={'https://openstreetmap.org/changeset/' + changesetId}
187 >
188 OSM
189 </a>
190 </li>
191 <li>
192 <a
193 target="_blank"
194 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-c-link-osmcha"
195 href={'https://osmcha.org/changesets/' + changesetId + '/'}
196 >
197 OSMCha
198 </a>
199 </li>
200 <li>
201 <a
202 target="_blank"
203 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-c-link-achavi"
204 href={
205 'https://overpass-api.de/achavi/?changeset=' + changesetId
206 }
207 >
208 Achavi
209 </a>
210 </li>
211 <li>
212 <a
213 target="_blank"
214 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-c-link-osmhv"
215 href={
216 'http://osmhv.openstreetmap.de/changeset.jsp?id=' +
217 changesetId
218 }
219 >
220 OSM HV
221 </a>
222 </li>
223 <li>
224 <a
225 target="_blank"
226 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-c-link-josm"
227 href={
228 'http://127.0.0.1:8111/import?url=http://www.openstreetmap.org/api/0.6/changeset/' +
229 changesetId +
230 '/download'
231 }
232 >
233 JOSM
234 </a>
235 </li>
236 <li>
237 <a
238 target="_blank"
239 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-c-link-id"
240 href={
241 'http://preview.ideditor.com/release#map=15/' +
242 center.lat +
243 '/' +
244 center.lng
245 }
246 >
247 iD
248 </a>
249 </li>
250 </ul>
251 </section>
252 <section className="cmap-user-section cmap-fill-light cmap-pb3">
253 <h6 className="cmap-heading" onClick={this.toggleUser}>
254 {this.state.user ? '▼' : '▶'}
255 User: <em className="cmap-user-id">{userName}</em>
256 </h6>
257
258 <ul
259 className="cmap-hlist"
260 style={{
261 display: this.state.user ? 'block' : 'none'
262 }}
263 >
264 <li>
265 <a
266 target="_blank"
267 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-u-link-osm"
268 href={'https://openstreetmap.org/user/' + userName}
269 >
270 OSM
271 </a>
272 </li>
273 <li>
274 <a
275 target="_blank"
276 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-u-link-hdyc"
277 href={'http://hdyc.neis-one.org/?' + userName}
278 >
279 HDYC
280 </a>
281 </li>
282 <li>
283 <a
284 target="_blank"
285 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-u-link-disc"
286 href={
287 'http://resultmaps.neis-one.org/osm-discussion-comments?uid=' +
288 userId
289 }
290 >
291 Discussions
292 </a>
293 </li>
294 <li>
295 <a
296 target="_blank"
297 className="cmap-hlist-item cmap-noselect cmap-pointer cmap-u-link-comm"
298 href={
299 'http://resultmaps.neis-one.org/osm-discussion-comments?uid=115894' +
300 userId +
301 '&commented'
302 }
303 >
304 Comments
305 </a>
306 </li>
307 </ul>
308 </section>
309 <section className="cmap-filter-action-section cmap-pt3">
310 <h6 className="cmap-heading pointer" onClick={this.toggleActions}>
311 {this.state.actions ? '▼' : '▶'}Filter by actions
312 </h6>
313
314 <ul
315 style={{
316 display: this.state.actions ? 'block' : 'none'
317 }}
318 className="cmap-hlist"
319 >
320 <li>
321 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
322 <input
323 type="checkbox"
324 value="added"
325 defaultChecked="true"
326 id="cmap-layer-selector-added"
327 onChange={filterLayers}
328 />
329 <span className="cmap-label-text">Added</span>
330 <span className="cmap-color-box cmap-color-added" />
331 </label>
332 </li>
333 <li>
334 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
335 <input
336 type="checkbox"
337 value="modified"
338 defaultChecked="true"
339 id="cmap-layer-selector-modified"
340 onChange={filterLayers}
341 />
342 <span className="cmap-label-text">Modified</span>
343 <span className="cmap-color-box cmap-color-modified-old" />
344 <span className="cmap-unicode">→</span>
345 <span className="cmap-color-box cmap-color-modified-new" />
346 </label>
347 </li>
348 <li>
349 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
350 <input
351 type="checkbox"
352 value="deleted"
353 defaultChecked="true"
354 id="cmap-layer-selector-deleted"
355 onChange={filterLayers}
356 />
357 <span className="cmap-label-text">Deleted</span>
358 <span className="cmap-color-box cmap-color-deleted" />
359 </label>
360 </li>
361 </ul>
362 </section>
363 <section className="cmap-filter-type-section">
364 <h6 className="cmap-heading pointer" onClick={this.toggleType}>
365 {this.state.type ? '▼' : '▶'}Filter by type
366 </h6>
367 <ul
368 className="cmap-hlist"
369 style={{
370 display: this.state.type ? 'block' : 'none'
371 }}
372 >
373 <li>
374 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
375 <input
376 type="checkbox"
377 value="nodes"
378 defaultChecked="true"
379 id="cmap-type-selector-nodes"
380 onClick={filterLayers}
381 />
382 <span className="cmap-label-text">Nodes</span>
383 </label>
384 </li>
385 <li>
386 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
387 <input
388 type="checkbox"
389 value="ways"
390 defaultChecked="true"
391 id="cmap-type-selector-ways"
392 onChange={filterLayers}
393 />
394 <span className="cmap-label-text">Ways</span>
395 </label>
396 </li>
397 <li>
398 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
399 <input
400 type="checkbox"
401 value="relations"
402 defaultChecked="true"
403 id="cmap-type-selector-relations"
404 onChange={filterLayers}
405 />
406 <span className="cmap-label-text">Relations</span>
407 </label>
408 </li>
409 </ul>
410 </section>
411 <section className="cmap-filter-changes-section cmap-pb3">
412 <h6 className="cmap-heading pointer" onClick={this.toggleChanges}>
413 {this.state.changes ? '▼' : '▶'}Tags added / updated / deleted
414 </h6>
415 <ul
416 className="cmap-sub-hlist"
417 style={{ display: this.state.changes ? 'block' : 'none' }}
418 >
419 {this.changeReport
420 .sort()
421 .map((change, k) => <ChangeItem key={k} change={change} />)}
422 </ul>
423 </section>
424 <section className="cmap-map-style-section cmap-pb3">
425 <h6 className="cmap-heading pointer" onClick={this.toggleMapStyle}>
426 {this.state.mapStyle ? '▼' : '▶'}Map style
427 </h6>
428
429 <ul
430 className="cmap-hlist"
431 style={{
432 display: this.state.mapStyle ? 'block' : 'none'
433 }}
434 >
435 <li>
436 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
437 <input
438 type="radio"
439 value="satellite"
440 defaultChecked="true"
441 name="baselayer"
442 id="cmap-baselayer-satellite"
443 onChange={this.props.toggleLayer}
444 />
445 <span className="cmap-label-text">Satellite</span>
446 </label>
447 </li>
448 <li>
449 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
450 <input
451 type="radio"
452 value="streets"
453 name="baselayer"
454 id="cmap-baselayer-streets"
455 onChange={this.props.toggleLayer}
456 />
457 <span className="cmap-label-text">Streets</span>
458 </label>
459 </li>
460 <li>
461 <label className="cmap-hlist-item cmap-noselect cmap-pointer">
462 <input
463 type="radio"
464 value="dark"
465 name="baselayer"
466 id="cmap-baselayer-dark"
467 onChange={this.props.toggleLayer}
468 />
469 <span className="cmap-label-text">Dark</span>
470 </label>
471 </li>
472 </ul>
473 </section>
474 </div>
475 );
476 }
477}