Small preact-based (like React.js) project https://inga-lovinde.github.io/static/komoot-demo/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

160 lines
4.7 KiB

import { nanoid } from 'nanoid';
import type { Dispatch } from 'preact/hooks';
import { reorderElements } from '../../../shared/collections.js';
import { Coordinates } from '../../../shared/types';
import { Marker, ReorderListParams } 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;
dispatcher: Dispatch<ReducerAction>;
}
>
| GenericMarkerChangeReducerAction<'remove'>
| GenericMarkerChangeReducerAction<'moveUp'>
| GenericMarkerChangeReducerAction<'moveDown'>
| GenericMarkerChangeReducerAction<
'changeLongLabel',
{ newLongLabel: string }
>
| GenericReducerAction<'reorder', ReorderListParams>;
const reindexMarkers = (markers: Marker[]) =>
markers.map((marker, i) => {
const newShortLabel = `${i + 1}`;
if (marker.shortLabel === newShortLabel) {
return marker;
}
return {
...marker,
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 (type) {
case 'add': {
const key = nanoid(10);
return reindexMarkers([
...markers,
{
key,
shortLabel: 'placeholder',
longLabel: key.substring(0, 4),
remove: () =>
action.data.dispatcher({
type: 'remove',
data: { key },
}),
moveUp: () =>
action.data.dispatcher({
type: 'moveUp',
data: { key },
}),
moveDown: () =>
action.data.dispatcher({
type: 'moveDown',
data: { key },
}),
changeLongLabel: (newLongLabel: string) =>
action.data.dispatcher({
type: 'changeLongLabel',
data: { key, newLongLabel },
}),
coordinates: action.data.coordinates,
},
]);
}
case 'remove': {
return reindexMarkers([
...markers.filter(({ key }) => key !== action.data.key),
]);
}
case 'moveUp': {
const oldIndex = markers.findIndex(
({ key }) => key === action.data.key,
);
if (oldIndex < 1) {
return markers;
}
return reindexMarkers(
reorderElements(markers, oldIndex, oldIndex - 1),
);
}
case 'moveDown': {
const oldIndex = markers.findIndex(
({ key }) => key === action.data.key,
);
if (oldIndex < 0 || oldIndex + 1 >= markers.length) {
return markers;
}
return reindexMarkers(
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(
markers,
action.data.oldIndex,
action.data.newIndex,
),
);
}
default:
return handleDefaultCase(type);
}
};
export const createChangeStateMethods = (
dispatchMarkers: Dispatch<ReducerAction>,
) => ({
addMarker: (coordinates: Coordinates) =>
dispatchMarkers({
type: 'add',
data: {
coordinates,
dispatcher: dispatchMarkers,
},
}),
reorderMarkers: ({ oldIndex, newIndex }: ReorderListParams): void =>
dispatchMarkers({
type: 'reorder',
data: { oldIndex, newIndex },
}),
});