initial implementation of reordering

main
Inga 🏳‍🌈 6 months ago
parent 32df61715d
commit 252554df6e
  1. 14
      package-lock.json
  2. 4
      package.json
  3. 4
      src/routePlanner/export.tsx
  4. 84
      src/routePlanner/index.tsx
  5. 6
      src/routePlanner/map.tsx
  6. 18
      src/routePlanner/marker.tsx
  7. 43
      src/routePlanner/markers.tsx
  8. 21
      src/routePlanner/types.ts
  9. 31
      src/shared/collections.ts

14
package-lock.json generated

@ -6,10 +6,12 @@
"": {
"dependencies": {
"@dwayneparton/geojson-to-gpx": "^0.0.30",
"@types/sortablejs": "^1.15.5",
"file-saver": "^2.0.5",
"leaflet": "^1.9.4",
"nanoid": "^5.0.3",
"preact": "^10.13.1"
"preact": "^10.13.1",
"sortablejs": "^1.15.0"
},
"devDependencies": {
"@babel/core": "^7.23.3",
@ -1185,6 +1187,11 @@
"integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==",
"dev": true
},
"node_modules/@types/sortablejs": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.5.tgz",
"integrity": "sha512-qqqbEFbB1EZt08I1Ok2BA3Sx0zlI8oizdIguMsajk4Yo/iHgXhCb3GM6N09JOJqT9xIMYM9LTFy8vit3RNY71Q=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz",
@ -5236,6 +5243,11 @@
"node": ">=8"
}
},
"node_modules/sortablejs": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",

@ -12,10 +12,12 @@
},
"dependencies": {
"@dwayneparton/geojson-to-gpx": "^0.0.30",
"@types/sortablejs": "^1.15.5",
"file-saver": "^2.0.5",
"leaflet": "^1.9.4",
"nanoid": "^5.0.3",
"preact": "^10.13.1"
"preact": "^10.13.1",
"sortablejs": "^1.15.0"
},
"devDependencies": {
"@babel/core": "^7.23.3",

@ -1,9 +1,9 @@
import { exportMarkersToGpx } from '../exporters/gpx';
import { InternalProps } from './types';
import { ExportProps } from './types';
import 'leaflet/dist/leaflet.css';
export const ExportComponent = ({ markers }: InternalProps) => {
export const ExportComponent = ({ markers }: ExportProps) => {
return (
<>
<button onClick={() => exportMarkersToGpx(markers)}>

@ -1,47 +1,99 @@
import { nanoid } from 'nanoid';
import { useMemo, useState } from 'preact/hooks';
import { reorderElements } from '../shared/collections';
import { Coordinates } from '../shared/types';
import { ExportComponent } from './export';
import { MapComponent } from './map';
import { MarkersComponent } from './markers';
import { Marker } from './types';
import { Marker, ReorderMarkersParams } from './types';
import './style.css';
export const RoutePlanner = () => {
const [markers, setMarkers] = useState<Marker[]>([]);
const onMarkerRemove = useMemo(
() => (keyToRemove: string) =>
setMarkers((markers) =>
markers
.filter(({ key }) => key !== keyToRemove)
.map((marker, i) => ({ ...marker, index: i + 1 })),
),
const onMarkersReorder = useMemo(
() =>
({ oldIndex, newIndex }: ReorderMarkersParams): void =>
setMarkers((oldMarkers) => {
console.log(
`Reordering markers: from ${oldIndex} to ${newIndex}`,
);
const newMarkers = reorderElements(
oldMarkers,
oldIndex,
newIndex,
);
return newMarkers.map((marker, i) => ({
...marker,
label: `${i + 1}`,
remove: () =>
onMarkersReorder({
oldIndex: i,
newIndex: -1,
}),
...(i - 1 >= 0 && {
moveDown: () =>
onMarkersReorder({
oldIndex: i,
newIndex: i - 1,
}),
}),
...(i + 1 < newMarkers.length && {
moveUp: () =>
onMarkersReorder({
oldIndex: i,
newIndex: i + 1,
}),
}),
}));
}),
[],
);
const onMapClick = useMemo(
() => (coordinates: Coordinates) => {
const key = nanoid(10);
const remove = () => onMarkerRemove(key);
setMarkers((markers) => [
...markers,
...markers.slice(0, markers.length - 1),
...markers
.slice(markers.length - 1, markers.length)
.map((marker) => ({
...marker,
moveUp: () =>
onMarkersReorder({
oldIndex: markers.length - 1,
newIndex: markers.length,
}),
})),
{
key,
index: markers.length + 1,
remove,
key: nanoid(10),
label: `${markers.length + 1}`,
remove: () =>
onMarkersReorder({
oldIndex: markers.length,
newIndex: -1,
}),
...(markers.length && {
moveDown: () =>
onMarkersReorder({
oldIndex: markers.length,
newIndex: markers.length - 1,
}),
}),
coordinates,
},
]);
},
[onMarkerRemove],
[onMarkersReorder],
);
return (
<section class="route-planner">
<section class="markers">
<MarkersComponent markers={markers} />
<MarkersComponent
onMarkersReorder={onMarkersReorder}
markers={markers}
/>
</section>
<section class="export">
<ExportComponent markers={markers} />

@ -1,10 +1,10 @@
import leaflet from 'leaflet';
import { useEffect, useRef } from 'preact/hooks';
import { InternalMapProps } from './types';
import { MapProps } from './types';
import 'leaflet/dist/leaflet.css';
export const MapComponent = ({ markers, onMapClick }: InternalMapProps) => {
export const MapComponent = ({ markers, onMapClick }: MapProps) => {
const mapContainerRef = useRef<HTMLDivElement>(null);
const mapRef = useRef<leaflet.Map | undefined>(undefined);
@ -85,7 +85,7 @@ export const MapComponent = ({ markers, onMapClick }: InternalMapProps) => {
permanent: true,
direction: 'center',
className: 'text',
content: `${marker.index}`,
content: marker.label,
},
circle,
)

@ -0,0 +1,18 @@
import { MarkerProps } from './types';
export const MarkerComponent = ({ marker }: MarkerProps) => {
return (
<li key={marker.key}>
{`Waypoint ${marker.label} (${marker.key.substring(0, 4)})`}
<span class="buttons">
<button onClick={marker.moveDown} disabled={!marker.moveDown}>
🔼
</button>
<button onClick={marker.moveUp} disabled={!marker.moveUp}>
🔽
</button>
<button onClick={marker.remove}>🗑</button>
</span>
</li>
);
};

@ -1,17 +1,44 @@
import { InternalProps } from './types';
import { useEffect, useRef } from 'preact/hooks';
import Sortable from 'sortablejs';
import { MarkerComponent } from './marker';
import { MarkersProps } from './types';
import 'leaflet/dist/leaflet.css';
export const MarkersComponent = ({
markers,
onMarkersReorder,
}: MarkersProps) => {
const listContainerRef = useRef<HTMLOListElement>(null);
useEffect(() => {
if (!listContainerRef.current) {
return;
}
const sortable = Sortable.create(listContainerRef.current, {
onEnd: (event) => {
if (
event.oldIndex === undefined ||
event.newIndex === undefined
) {
return;
}
onMarkersReorder({
oldIndex: event.oldIndex,
newIndex: event.newIndex,
});
},
});
return () => {
sortable.destroy();
};
}, [listContainerRef, markers, onMarkersReorder]);
export const MarkersComponent = ({ markers }: InternalProps) => {
return (
<>
<h3>Markers</h3>
<ol>
<ol ref={listContainerRef}>
{markers.map((marker) => (
<li key={marker.key}>
{`Waypoint ${marker.index}`}
<button onClick={marker.remove}>🗑</button>
</li>
<MarkerComponent key={marker.key} marker={marker} />
))}
</ol>
</>

@ -3,17 +3,30 @@ import { Coordinates, Waypoint } from '../shared/types';
export type Marker = Waypoint & {
key: string;
remove: () => void;
index: number;
moveUp?: () => void;
moveDown?: () => void;
label: string;
};
export type InternalProps = {
export type ReorderMarkersParams = {
oldIndex: number;
newIndex: number;
};
export type MarkersProps = {
markers: Marker[];
onMarkersReorder: (params: ReorderMarkersParams) => void;
};
export type InternalMapProps = InternalProps & {
export type MapProps = {
markers: Marker[];
onMapClick: (coordinates: Coordinates) => void;
};
export type MarkerListElementProps = {
export type ExportProps = {
markers: Marker[];
};
export type MarkerProps = {
marker: Marker;
};

@ -0,0 +1,31 @@
export const reorderElements = <T>(
collection: T[],
oldIndex: number,
newIndex: number,
) => {
if (newIndex < 0) {
return [
...collection.slice(0, oldIndex),
...collection.slice(oldIndex + 1, collection.length),
];
}
if (oldIndex < newIndex) {
return [
...collection.slice(0, oldIndex),
...collection.slice(oldIndex + 1, newIndex + 1),
...collection.slice(oldIndex, oldIndex + 1),
...collection.slice(newIndex + 1, collection.length),
];
}
if (oldIndex > newIndex) {
return [
...collection.slice(0, newIndex),
...collection.slice(oldIndex, oldIndex + 1),
...collection.slice(newIndex, oldIndex),
...collection.slice(oldIndex + 1, collection.length),
];
}
return collection;
};
Loading…
Cancel
Save