Skip to content

Commit

Permalink
[upload wizard] improve UX, show changes on map, add per-feature diff
Browse files Browse the repository at this point in the history
  • Loading branch information
k-yle committed Dec 27, 2023
1 parent f2e2f5e commit 2e73160
Show file tree
Hide file tree
Showing 12 changed files with 464 additions and 77 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@nivo/line": "^0.83.0",
"@rapideditor/country-coder": "^5.2.1",
"clsx": "^2.0.0",
"diff": "^5.1.0",
"jsonc-parser": "^3.2.0",
"leaflet": "^1.9.4",
Expand Down
3 changes: 3 additions & 0 deletions src/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export const Home: React.FC = () => (
<li>
<a href="/missing-streets">Missing Streets</a>
</li>
{/* <li>
<a href="#/upload">For Importers: OsmPatch Upload Tool</a>
</li> */}
</ul>
</div>
);
33 changes: 33 additions & 0 deletions src/pages/map/icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Icon, type IconOptions } from 'leaflet';

const boilerplate: Omit<IconOptions, 'iconUrl'> = {
shadowUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41],
};

export const ICONS = {
red: new Icon({
iconUrl:
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
...boilerplate,
}),
gold: new Icon({
iconUrl:
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-gold.png',
...boilerplate,
}),
green: new Icon({
iconUrl:
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
...boilerplate,
}),
violet: new Icon({
iconUrl:
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-violet.png',
...boilerplate,
}),
};
67 changes: 57 additions & 10 deletions src/pages/upload/MapPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
import { useMemo, useRef } from 'react';
import type { OsmChange } from 'osm-api';
import { FeatureGroup, MapContainer, Polygon } from 'react-leaflet';
import {
FeatureGroup,
GeoJSON,
MapContainer,
Marker,
Polygon,
} from 'react-leaflet';
import { type FeatureGroup as IFeatureGroup, LatLngBounds } from 'leaflet';
import { Layers } from '../map/Layers';
import type { OsmPatch, OsmPatchFeature } from '../../types';
import { ICONS } from '../map/icons';
import type { FetchCache } from './util';
import { type Bbox, getCsBbox } from './helpers/bbox';

const ICON_COLOUR_MAP = {
delete: 'red',
move: 'violet',
edit: 'gold',
'': 'green',
} as const;

export const MapPreview: React.FC<{
diff: OsmChange;
osmPatch?: OsmPatch;
fetchCache: FetchCache | undefined;
bboxFromOsmPatch: Bbox | undefined;
}> = ({ diff, fetchCache, bboxFromOsmPatch }) => {
setFocusedFeature(feature: OsmPatchFeature): void;
}> = ({ diff, osmPatch, fetchCache, bboxFromOsmPatch, setFocusedFeature }) => {
const polygonGroup = useRef<IFeatureGroup>(null);

// TODO: react hook to download all nodes of ways/relations that were touched
// for small features. Render each feature on the map
const bbox = useMemo(
() => getCsBbox(diff, fetchCache, bboxFromOsmPatch),
[diff, fetchCache, bboxFromOsmPatch],
Expand All @@ -28,12 +43,10 @@ export const MapPreview: React.FC<{
<MapContainer
style={{ width: 500, height: 500, margin: 'auto' }}
scrollWheelZoom
ref={(map) =>
map?.fitBounds(
new LatLngBounds(
[bbox.minLat, bbox.minLng],
[bbox.maxLat, bbox.maxLng],
),
bounds={
new LatLngBounds(
{ lat: bbox.minLat, lng: bbox.minLng },
{ lat: bbox.maxLat, lng: bbox.maxLng },
)
}
>
Expand All @@ -49,6 +62,40 @@ export const MapPreview: React.FC<{
]}
/>
</FeatureGroup>

{/* we only support nice previews for osmPatch files */}
{osmPatch && (
<FeatureGroup>
{osmPatch.features.map((feature) => {
const colour = ICON_COLOUR_MAP[feature.properties.__action || ''];

if (feature.geometry.type === 'Point') {
// this one has to be handled seprately
// if we want to customise the colour, since
// colour is only customisable by changing the
// icon itself.
const [lng, lat] = feature.geometry.coordinates;
return (
<Marker
position={{ lat, lng }}
key={feature.id}
icon={ICONS[colour]}
eventHandlers={{ click: () => setFocusedFeature(feature) }}
/>
);
}

return (
<GeoJSON
key={feature.id}
data={feature}
pathOptions={{ color: colour }}
eventHandlers={{ click: () => setFocusedFeature(feature) }}
/>
);
})}
</FeatureGroup>
)}
</MapContainer>
);
};
56 changes: 42 additions & 14 deletions src/pages/upload/TagChanges.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Fragment, useEffect, useState } from 'react';
import clsx from 'clsx';
import type { OsmChange } from 'osm-api';
import { type FetchCache, type ToFetch, fetchChunked } from './util';
import classes from './Upload.module.css';

type SimpleRecord = {
[key: string]: { [value: string]: number };
Expand All @@ -13,6 +15,8 @@ type Change = {
changed: SimpleRecord;
};

const ARROW = '→';

async function calcTagChanges(
diff: OsmChange,
fetchCache: FetchCache | undefined,
Expand Down Expand Up @@ -76,7 +80,7 @@ async function calcTagChanges(
} else if (newTags[key] !== oldTags[key]) {
// value changed

const value = `${oldTags[key]} ~~> ${newTags[key]}`;
const value = `${oldTags[key]} ${ARROW} ${newTags[key]}`;

out.changed[key] ||= {};
out.changed[key][value] ||= 0;
Expand All @@ -88,7 +92,28 @@ async function calcTagChanges(
return out;
}

const renderSimpleSection = (list: SimpleRecord, prefix: string) =>
const RenderValue: React.FC<{ value: string; className: string }> = ({
value,
className,
}) => {
if (value.includes(ARROW)) {
const [before, after] = value.split(ARROW);
return (
<span>
<code className={classes.changedOld}>{before.trim()}</code> {ARROW}{' '}
<code className={classes.changedNew}>{after.trim()}</code>
</span>
);
}

return <code className={className}>{value}</code>;
};

const renderSimpleSection = (
list: SimpleRecord,
className: string,
label: string,
) =>
Object.entries(list).map(([key, vals]) => {
const tags = Object.entries(vals);

Expand All @@ -100,7 +125,7 @@ const renderSimpleSection = (list: SimpleRecord, prefix: string) =>
children = tags.map(([value], index) => (
<Fragment key={value}>
{!!index && '/'}
<code>{value}</code>
<RenderValue value={value} className={className} />
</Fragment>
));
keyCount = ` (${tags.length})`;
Expand All @@ -109,21 +134,22 @@ const renderSimpleSection = (list: SimpleRecord, prefix: string) =>
<ul>
{tags.map(([value, count]) => (
<li key={value}>
<code>{value}</code> {count > 1 && `(${count})`}
<RenderValue value={value} className={className} />
{count > 1 && `(${count})`}
</li>
))}
</ul>
);
keyCount = '';
}
} else {
children = <code>{tags[0][0]}</code>;
children = <RenderValue value={tags[0][0]} className={className} />;
keyCount = ` (${tags[0][1]})`;
}
return (
<li key={prefix + key} className={prefix}>
{prefix}
{keyCount} <code>{key}=</code>
<li key={className + key}>
{label}
{keyCount} <code className={className}>{key}=</code>
{children}
</li>
);
Expand All @@ -145,15 +171,17 @@ export const TagChanges: React.FC<{

const noChanges = Object.values(changes).every((x) => !Object.keys(x).length);
if (noChanges) {
return <div className="alert error">No tags changed!</div>;
return (
<div className={clsx(classes.alert, classes.error)}>No tags changed!</div>
);
}

return (
<ul className="tagChanges">
{renderSimpleSection(changes.added, 'Added')}
{renderSimpleSection(changes.changed, 'Changed')}
{renderSimpleSection(changes.removed, 'Removed')}
{renderSimpleSection(changes.featureDeleted, 'Deleted')}
<ul className={classes.tagChanges}>
{renderSimpleSection(changes.added, classes.added, 'Added')}
{renderSimpleSection(changes.changed, classes.changedOld, 'Changed')}
{renderSimpleSection(changes.removed, classes.removed, 'Removed')}
{renderSimpleSection(changes.featureDeleted, classes.deleted, 'Deleted')}
</ul>
);
};
36 changes: 0 additions & 36 deletions src/pages/upload/Upload.css

This file was deleted.

71 changes: 71 additions & 0 deletions src/pages/upload/Upload.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
:root {
/* same colours as OSMCha */
--added: #b7e8e0;
--removed: #e4b3bb;
--changedOld: #efdab0;
--changedNew: #f3f3c2;
}

.added {
background: var(--added);
}
.removed {
background: var(--removed);
}
.deleted {
background: var(--removed);
}
.changedOld {
background: var(--changedOld);
}
.changedNew {
background: var(--changedNew);
}

.uploadRoot {
margin: 32px;
text-align: center;
}

.tagChanges {
overflow-wrap: break-word;
text-align: left;
}

.alert {
background: #ddf4ff;
border-color: #54aeff66;
border-radius: 6px;
border-style: solid;
border-width: 1px;
padding: 20px;
display: inline-block;
min-width: 400px;
}
.alert.error {
background: #ffebe9;
border-color: #ff818266;
}

.diffTable {
width: 100%;

& tr:nth-of-type(odd) {
background: #f8f8f8;
}

& tr > td:first-child {
font-weight: 600;
}

& td {
text-align: left;
}

& td,
& th {
border: 1px solid #dadada;
padding: 6px;
color: #666;
}
}
Loading

0 comments on commit 2e73160

Please sign in to comment.