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
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;
|
|
};
|
|
|