implemented label editing

main
Inga 🏳‍🌈 6 months ago
parent 9bf8c03c1d
commit 7b95b70a10
  1. 7
      .eslintrc.cjs
  2. 2
      src/routePlanner/map.tsx
  3. 15
      src/routePlanner/marker.tsx
  4. 3
      src/routePlanner/style.css
  5. 30
      src/routePlanner/types.ts
  6. 182
      src/routePlanner/useMarkers.test.ts
  7. 71
      src/routePlanner/useMarkers.ts

@ -22,12 +22,9 @@ module.exports = {
'error',
{ ignoreArrowShorthand: true },
],
/*'@typescript-eslint/no-invalid-void-type': [
'@typescript-eslint/restrict-template-expressions': [
'error',
{ allowAsThisParameter: true },
{ allowNever: true },
],
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',*/
},
};

@ -85,7 +85,7 @@ export const MapComponent = ({ markers, onMapClick }: MapProps) => {
permanent: true,
direction: 'center',
className: 'text',
content: marker.label,
content: marker.shortLabel,
},
circle,
)

@ -1,15 +1,24 @@
import { useCallback } from 'preact/hooks';
import { MarkerProps } from './types';
export const MarkerComponent = ({ marker, isFirst, isLast }: MarkerProps) => {
const changeLongLabel = useCallback(() => {
const newLongLabel = prompt('Enter new label', marker.longLabel);
if (newLongLabel?.length) {
marker.changeLongLabel(newLongLabel);
}
}, [marker]);
return (
<li key={marker.key}>
{`Waypoint ${marker.label} (${marker.key.substring(0, 4)})`}
{`Waypoint ${marker.shortLabel} (${marker.longLabel})`}
<span class="buttons">
<button onClick={changeLongLabel}></button>
<button onClick={marker.moveUp} disabled={isFirst}>
🔼
</button>
<button onClick={marker.moveDown} disabled={isLast}>
🔽
</button>
<button onClick={marker.remove}>🗑</button>
</span>

@ -20,7 +20,7 @@ section.route-planner > section.markers {
}
section.route-planner > section.markers ol {
padding: 0
padding: 0;
}
section.route-planner > section.markers li::before {
@ -45,6 +45,7 @@ section.route-planner > section.markers li .buttons {
section.route-planner > section.markers li button {
background: transparent;
border: 0;
color: white;
cursor: pointer;
}

@ -5,29 +5,17 @@ export type Marker = Waypoint & {
remove: () => void;
moveUp: () => void;
moveDown: () => void;
label: string;
changeLongLabel: (newLongLabel: string) => void;
/**
* Should be at most 1-2 characters (to be displayed on map markers), can change at any time
*/
shortLabel: string;
/**
* Displayed in the list, should only change when explicitly requested by user
*/
longLabel: string;
};
type GenericReducerAction<TType extends string, TData> = {
type: TType;
data: TData;
};
export type ReducerAction =
| GenericReducerAction<
'add',
{
coordinates: Coordinates;
remove: (key: string) => void;
moveUp: (key: string) => void;
moveDown: (key: string) => void;
}
>
| GenericReducerAction<'remove', { key: string }>
| GenericReducerAction<'moveUp', { key: string }>
| GenericReducerAction<'moveDown', { key: string }>
| GenericReducerAction<'reorder', { oldIndex: number; newIndex: number }>;
export type ReorderMarkersParams = {
oldIndex: number;
newIndex: number;

@ -1,6 +1,10 @@
import t from 'tap';
import { Marker, ReducerAction } from './types';
import { createChangeStateMethods, markersReducer } from './useMarkers.js';
import { Marker } from './types';
import {
ReducerAction,
createChangeStateMethods,
markersReducer,
} from './useMarkers.js';
// Testing entire `useMarkers` custom hook would require us to create test preact components here
// and render them into virtual DOM and check the results, which is just too much hassle
@ -38,9 +42,11 @@ const createReducersWithFivePoints = () => {
const markerPattern = {
key: String,
longLabel: String,
moveUp: Function,
moveDown: Function,
remove: Function,
changeLongLabel: Function,
};
void t.test('reducer', (t) => {
@ -58,11 +64,11 @@ void t.test('reducer methods', (t) => {
void t.test('create markers with correct labels', (t) => {
const reducers = createReducersWithFivePoints();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -71,10 +77,10 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[0]?.remove();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates2 },
{ ...markerPattern, label: '2', coordinates: coordinates3 },
{ ...markerPattern, label: '3', coordinates: coordinates4 },
{ ...markerPattern, label: '4', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates5 },
]);
t.end();
});
@ -83,10 +89,10 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[2]?.remove();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates4 },
{ ...markerPattern, label: '4', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates5 },
]);
t.end();
});
@ -95,10 +101,10 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[4]?.remove();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
]);
t.end();
});
@ -107,11 +113,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[0]?.moveUp();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -120,11 +126,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[0]?.moveDown();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates2 },
{ ...markerPattern, label: '2', coordinates: coordinates1 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -133,11 +139,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[1]?.moveUp();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates2 },
{ ...markerPattern, label: '2', coordinates: coordinates1 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -146,11 +152,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[1]?.moveDown();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates3 },
{ ...markerPattern, label: '3', coordinates: coordinates2 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -159,11 +165,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[2]?.moveUp();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates3 },
{ ...markerPattern, label: '3', coordinates: coordinates2 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -172,11 +178,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[2]?.moveDown();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates4 },
{ ...markerPattern, label: '4', coordinates: coordinates3 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -185,11 +191,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[3]?.moveUp();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates4 },
{ ...markerPattern, label: '4', coordinates: coordinates3 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -198,11 +204,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[3]?.moveDown();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates5 },
{ ...markerPattern, label: '5', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates4 },
]);
t.end();
});
@ -211,11 +217,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[4]?.moveUp();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates5 },
{ ...markerPattern, label: '5', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates4 },
]);
t.end();
});
@ -224,11 +230,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[4]?.moveDown();
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates2 },
{ ...markerPattern, label: '3', coordinates: coordinates3 },
{ ...markerPattern, label: '4', coordinates: coordinates4 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -237,11 +243,11 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.reorderMarkers({ oldIndex: 1, newIndex: 3 });
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates3 },
{ ...markerPattern, label: '3', coordinates: coordinates4 },
{ ...markerPattern, label: '4', coordinates: coordinates2 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
@ -250,11 +256,29 @@ void t.test('reducer methods', (t) => {
const reducers = createReducersWithFivePoints();
reducers.reorderMarkers({ oldIndex: 3, newIndex: 1 });
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, label: '1', coordinates: coordinates1 },
{ ...markerPattern, label: '2', coordinates: coordinates4 },
{ ...markerPattern, label: '3', coordinates: coordinates2 },
{ ...markerPattern, label: '4', coordinates: coordinates3 },
{ ...markerPattern, label: '5', coordinates: coordinates5 },
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '3', coordinates: coordinates2 },
{ ...markerPattern, shortLabel: '4', coordinates: coordinates3 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});
void t.test('renames marker', (t) => {
const reducers = createReducersWithFivePoints();
reducers.getMarkers()[2]?.changeLongLabel('new label');
t.matchOnlyStrict(reducers.getMarkers(), [
{ ...markerPattern, shortLabel: '1', coordinates: coordinates1 },
{ ...markerPattern, shortLabel: '2', coordinates: coordinates2 },
{
...markerPattern,
shortLabel: '3',
coordinates: coordinates3,
longLabel: 'new label',
},
{ ...markerPattern, shortLabel: '4', coordinates: coordinates4 },
{ ...markerPattern, shortLabel: '5', coordinates: coordinates5 },
]);
t.end();
});

@ -2,37 +2,74 @@ import { nanoid } from 'nanoid';
import { Dispatch, useMemo, useReducer } from 'preact/hooks';
import { reorderElements } from '../shared/collections.js';
import { Coordinates } from '../shared/types';
import { Marker, ReducerAction, ReorderMarkersParams } from './types';
import { Marker, ReorderMarkersParams } from './types';
type GenericReducerAction<TType extends string, TData> = {
type: TType;
data: TData;
};
type GenericMarkerChangeReducerAction<TType extends string, TData = unknown> = {
type: TType;
data: TData & { key: string };
};
export type ReducerAction =
| GenericReducerAction<
'add',
{
coordinates: Coordinates;
remove: (key: string) => void;
moveUp: (key: string) => void;
moveDown: (key: string) => void;
changeLongLabel: (key: string, newLongLabel: string) => void;
}
>
| GenericMarkerChangeReducerAction<'remove'>
| GenericMarkerChangeReducerAction<'moveUp'>
| GenericMarkerChangeReducerAction<'moveDown'>
| GenericMarkerChangeReducerAction<
'changeLongLabel',
{ newLongLabel: string }
>
| GenericReducerAction<'reorder', { oldIndex: number; newIndex: number }>;
const reindexMarkers = (markers: Marker[]) =>
markers.map((marker, i) => {
const newLabel = `${i + 1}`;
if (marker.label === newLabel) {
const newShortLabel = `${i + 1}`;
if (marker.shortLabel === newShortLabel) {
return marker;
}
return {
...marker,
label: newLabel,
shortLabel: newShortLabel,
};
});
const handleDefaultCase = (discriminator: never): never => {
throw new Error(`Unsupported type ${discriminator}`);
};
export const markersReducer = (
markers: Marker[],
action: ReducerAction,
): Marker[] => {
const type = action.type;
switch (action.type) {
switch (type) {
case 'add': {
const key = nanoid(10);
return reindexMarkers([
...markers,
{
key,
label: 'placeholder',
shortLabel: 'placeholder',
longLabel: key.substring(0, 4),
remove: () => action.data.remove(key),
moveUp: () => action.data.moveUp(key),
moveDown: () => action.data.moveDown(key),
changeLongLabel: (newLongLabel: string) =>
action.data.changeLongLabel(key, newLongLabel),
coordinates: action.data.coordinates,
},
]);
@ -66,6 +103,18 @@ export const markersReducer = (
reorderElements(markers, oldIndex, oldIndex + 1),
);
}
case 'changeLongLabel': {
return markers.map((marker) => {
if (marker.key !== action.data.key) {
return marker;
}
return {
...marker,
longLabel: action.data.newLongLabel,
};
});
}
case 'reorder': {
return reindexMarkers(
reorderElements(
@ -76,7 +125,7 @@ export const markersReducer = (
);
}
default:
throw new Error(`Unsupported type ${type}`);
return handleDefaultCase(type);
}
};
@ -110,6 +159,12 @@ export const createChangeStateMethods = (
data: { key },
});
const changeMarkerLongLabel = (key: string, newLongLabel: string) =>
dispatchMarkers({
type: 'changeLongLabel',
data: { key, newLongLabel },
});
const addMarker = (coordinates: Coordinates) =>
dispatchMarkers({
type: 'add',
@ -118,6 +173,7 @@ export const createChangeStateMethods = (
moveUp: moveMarkerUp,
moveDown: moveMarkerDown,
remove: removeMarker,
changeLongLabel: changeMarkerLongLabel,
},
});
@ -127,6 +183,7 @@ export const createChangeStateMethods = (
moveMarkerDown,
removeMarker,
addMarker,
changeMarkerLongLabel,
};
};

Loading…
Cancel
Save