import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import type { ForwardedRef } from 'react';
import { withFastCompare } from '../../utils';
import type { RouteInfo } from '../utils/types';
import { AnimatedPolyline, Polyline } from './polyline.component';
import type {
AnimatedPolylineRef,
AnimatedPolylineAnimationProp,
} from './polyline.component';
import type { Point } from '../../bare';
type RouteProps = {
route: RouteInfo;
};
export const Route = withFastCompare((props: RouteProps) => {
return (
{props.route.sections.map((section, i) => (
))}
);
});
export type AnimatedRouteRef = {
setRoute: (route: RouteInfo) => void;
animate: (
section: number,
point: number,
duration?: number,
onNextAnimationTick?: (point: Point, duration: number) => Promise,
abortRef?: React.RefObject
) => void;
reset: () => void;
};
export const AnimatedRoute = withFastCompare(
forwardRef(
(
props: RouteProps & {
animation?: AnimatedPolylineAnimationProp;
commonColor?: string;
commonWidth?: number;
},
ref: ForwardedRef
) => {
const [route, setRoute] = useState(props.route);
const polylines = useRef>([]);
const _currentSection = useRef(0);
const _currentPoint = useRef(0);
useEffect(() => {
setRoute(props.route);
_currentPoint.current = 0;
_currentSection.current = 0;
}, [props.route]);
const reset = useCallback(() => {
polylines.current.forEach((l) => l?.reset());
_currentPoint.current = 0;
_currentSection.current = 0;
}, []);
const animate = useCallback(
async (
section: number,
point: number,
_duration?: number,
onNextAnimationTick?: (
point: Point,
duration: number
) => Promise,
abortRef?: React.RefObject
) => {
//console.log(section, point, duration);
const duration = _duration ?? 100;
// Check if animatable
if (
route.sections.length - 1 < section ||
route.sections[section]!.points.length - 1 < point ||
_currentSection.current > section ||
(_currentPoint.current > point &&
_currentSection.current === section)
) {
return;
}
// Get sections that we need to manipulate
const sectionRange = [
...Array(section - _currentSection.current + 1).keys(),
].map((s) => s + _currentSection.current);
let steps = 0;
// If animation in same section
if (_currentSection.current === section) {
steps = point - _currentPoint.current;
} else {
// current section points left
steps +=
route.sections[_currentSection.current]!.points.length -
1 -
_currentPoint.current;
// cause section switch
steps += section - _currentSection.current + 1;
// for all sections between start and end
for (let i = _currentSection.current + 1; i < section; ++i) {
steps += route.sections[i]!.points.length;
}
steps += point;
}
// If same point
if (!steps) {
return;
}
const FRAME_TIME = 16;
const pointDuration = (duration - FRAME_TIME * steps) / steps;
// If we have less than 16 ms (1 frame time) for 1 step, we
// need to optimize animation to remove glitches
if (pointDuration < 0) {
steps = sectionRange.length;
const optimizedPointDuration =
(duration - FRAME_TIME * steps) / steps;
for await (const sectionId of sectionRange) {
if (abortRef?.current) {
return;
}
_currentSection.current = sectionId;
_currentPoint.current =
route.sections[sectionId]!.points.length - 1;
onNextAnimationTick?.(
route.sections[sectionId]!.points[_currentPoint.current]!,
optimizedPointDuration
);
await polylines.current[sectionId]?.animate(
_currentPoint.current,
optimizedPointDuration,
true
);
}
} else {
for await (const sectionId of sectionRange) {
if (abortRef?.current) {
return;
}
_currentSection.current = sectionId;
for await (const point of route.sections[sectionId]!.points) {
if (abortRef?.current) {
return;
}
onNextAnimationTick?.(point, pointDuration);
const pointId =
route.sections[sectionId]!.points.indexOf(point);
_currentPoint.current = pointId;
await polylines.current[sectionId]?.animate(
pointId,
pointDuration
);
}
}
}
},
[route]
);
useImperativeHandle(ref, () => ({
setRoute,
reset,
animate,
}));
return (
{route.sections.map((section: any, i: number) => (
(polylines.current[i] = ref)}
sectionId={i}
points={section.points}
key={section.routeIndex + '' + i}
strokeColor={props.commonColor ?? section.strokeColor ?? 'black'}
strokeWidth={props.commonWidth ?? section.strokeWidth ?? 3}
animation={props.animation}
/>
))}
);
}
)
);