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.
 
 
 

198 lines
5.6 KiB

import { nanoid } from 'nanoid';
import { Dispatch, useMemo, useReducer } from 'preact/hooks';
import { reorderElements } from '../shared/collections.js';
import { Coordinates } from '../shared/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 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.remove(key),
moveUp: () => action.data.moveUp(key),
moveDown: () => action.data.moveDown(key),
changeLongLabel: (newLongLabel: string) =>
action.data.changeLongLabel(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>,
) => {
const reorderMarkers = ({
oldIndex,
newIndex,
}: ReorderMarkersParams): void =>
dispatchMarkers({
type: 'reorder',
data: { oldIndex, newIndex },
});
const moveMarkerUp = (key: string) =>
dispatchMarkers({
type: 'moveUp',
data: { key },
});
const moveMarkerDown = (key: string) =>
dispatchMarkers({
type: 'moveDown',
data: { key },
});
const removeMarker = (key: string) =>
dispatchMarkers({
type: 'remove',
data: { key },
});
const changeMarkerLongLabel = (key: string, newLongLabel: string) =>
dispatchMarkers({
type: 'changeLongLabel',
data: { key, newLongLabel },
});
const addMarker = (coordinates: Coordinates) =>
dispatchMarkers({
type: 'add',
data: {
coordinates,
moveUp: moveMarkerUp,
moveDown: moveMarkerDown,
remove: removeMarker,
changeLongLabel: changeMarkerLongLabel,
},
});
return {
reorderMarkers,
moveMarkerUp,
moveMarkerDown,
removeMarker,
addMarker,
changeMarkerLongLabel,
};
};
export const useMarkers = () => {
const [markers, dispatchMarkers] = useReducer(markersReducer, []);
const changeStateMethods = useMemo(
() => createChangeStateMethods(dispatchMarkers),
[],
);
return [markers, changeStateMethods] as const;
};