refactor: optimize replay preview state management and cleanup editor UI components
Build and Release / release (push) Successful in 1m0s

This commit is contained in:
taDuc
2026-05-28 18:24:04 +07:00
parent 0dbe26fd4e
commit b94f5f44cb
4 changed files with 214 additions and 73 deletions
@@ -35,6 +35,7 @@ type Props = {
onFocusHistoricalGeometry: (payload: HistoricalGeometryFocusPayload) => void;
onClearFocus: () => void;
leftOffset?: number;
style?: CSSProperties;
};
export default function PresentPlaceSearch({
@@ -43,6 +44,7 @@ export default function PresentPlaceSearch({
onFocusHistoricalGeometry,
onClearFocus,
leftOffset = 18,
style,
}: Props) {
const [mode, setMode] = useState<SearchMode>("present");
const [query, setQuery] = useState("");
@@ -284,6 +286,7 @@ export default function PresentPlaceSearch({
zIndex: 18,
width: "min(392px, calc(100vw - 36px))",
pointerEvents: "auto",
...style,
}}
onMouseDown={(event) => event.stopPropagation()}
>
+70 -57
View File
@@ -93,6 +93,13 @@ export function useReplayPreview({
const baselineRef = useRef<PreviewBaseline | null>(null);
const effects = useMemo(() => createReplayMapEffects(), []);
const selectedStageIdRef = useRef(selectedStageId);
const selectedStepIndexRef = useRef(selectedStepIndex);
useEffect(() => {
selectedStageIdRef.current = selectedStageId;
selectedStepIndexRef.current = selectedStepIndex;
}, [selectedStageId, selectedStepIndex]);
const flatSteps = useMemo(() => flattenReplaySteps(replay), [replay]);
useEffect(() => {
@@ -203,61 +210,60 @@ export function useReplayPreview({
}, [restorePreviewState]);
const controllersRef = useRef<Parameters<typeof dispatchReplayAction>[0] | null>(null);
useEffect(() => {
controllersRef.current = {
map: getMapInstance(),
draft,
effects,
setTimelineVisible,
setTimelineFilterEnabled,
setLayerPanelVisible,
setZoomPanelVisible,
setSidebarOpen,
onSelectWiki: (id) => {
const nextId = String(id || "").trim();
setActiveWikiId(nextId || null);
},
addToast,
setPlaybackSpeed: (nextSpeed) => {
const safe = Number.isFinite(nextSpeed) && nextSpeed > 0 ? nextSpeed : 1;
playbackSpeedRef.current = safe;
setPlaybackSpeed(safe);
},
onYearChange: setTimelineYear,
showGeometries: (ids) => {
const nextIds = normalizeIdList(ids);
if (!nextIds.length) return;
setHiddenGeometryIds((prev) => prev.filter((id) => !nextIds.includes(id)));
},
hideGeometries: (ids) => {
const nextIds = normalizeIdList(ids);
if (!nextIds.length) return;
setHiddenGeometryIds((prev) => {
const seen = new Set(prev);
for (const id of nextIds) {
seen.add(id);
}
return Array.from(seen);
});
},
showOnlyGeometries: (ids) => {
const keepIds = new Set(normalizeIdList(ids));
if (!keepIds.size) return;
setHiddenGeometryIds(
draft.features
.map((feature) => String(feature.properties.id))
.filter((id) => !keepIds.has(id))
);
},
showAllGeometries: () => {
setHiddenGeometryIds([]);
},
setDialog: setDialogWithRef,
getDialog: () => dialogRef.current,
};
}, [addToast, draft, effects, getMapInstance]);
controllersRef.current = {
map: getMapInstance(),
draft,
effects,
setTimelineVisible,
setTimelineFilterEnabled,
setLayerPanelVisible,
setZoomPanelVisible,
setSidebarOpen,
onSelectWiki: (id) => {
const nextId = String(id || "").trim();
setActiveWikiId(nextId || null);
},
addToast,
setPlaybackSpeed: (nextSpeed) => {
const safe = Number.isFinite(nextSpeed) && nextSpeed > 0 ? nextSpeed : 1;
playbackSpeedRef.current = safe;
setPlaybackSpeed(safe);
},
onYearChange: setTimelineYear,
showGeometries: (ids) => {
const nextIds = normalizeIdList(ids);
if (!nextIds.length) return;
setHiddenGeometryIds((prev) => prev.filter((id) => !nextIds.includes(id)));
},
hideGeometries: (ids) => {
const nextIds = normalizeIdList(ids);
if (!nextIds.length) return;
setHiddenGeometryIds((prev) => {
const seen = new Set(prev);
for (const id of nextIds) {
seen.add(id);
}
return Array.from(seen);
});
},
showOnlyGeometries: (ids) => {
const keepIds = new Set(normalizeIdList(ids));
if (!keepIds.size) return;
setHiddenGeometryIds(
draft.features
.map((feature) => String(feature.properties.id))
.filter((id) => !keepIds.has(id))
);
},
showAllGeometries: () => {
setHiddenGeometryIds([]);
},
setDialog: setDialogWithRef,
getDialog: () => dialogRef.current,
};
const playFromIndex = useCallback(async (startIndex: number) => {
console.log("playFromIndex starting at:", startIndex, "flatSteps count:", flatSteps.length);
if (!flatSteps.length) return;
const safeStartIndex = Math.max(0, Math.min(flatSteps.length - 1, startIndex));
resetPresentation();
@@ -271,7 +277,10 @@ export function useReplayPreview({
setIsPlaying(true);
for (let index = safeStartIndex; index < flatSteps.length; index += 1) {
if (runIdRef.current !== runId) return;
if (runIdRef.current !== runId) {
console.log("playFromIndex loop aborted because runId changed");
return;
}
const current = flatSteps[index];
setActiveCursor({
@@ -282,7 +291,10 @@ export function useReplayPreview({
onSelectStep(current.stageId, current.stepIndex);
const controllers = controllersRef.current;
if (!controllers) return;
if (!controllers) {
console.warn("playFromIndex aborted: controllersRef.current is null!");
return;
}
controllers.map = getMapInstance();
controllers.draft = draft;
@@ -322,9 +334,10 @@ export function useReplayPreview({
}, [playFromIndex]);
const playFromSelection = useCallback(() => {
const selectedIndex = findReplayStepIndex(flatSteps, selectedStageId, selectedStepIndex);
const selectedIndex = findReplayStepIndex(flatSteps, selectedStageIdRef.current, selectedStepIndexRef.current);
console.log("playFromSelection called: selectedIndex =", selectedIndex, "selectedStageId =", selectedStageIdRef.current, "selectedStepIndex =", selectedStepIndexRef.current);
void playFromIndex(selectedIndex >= 0 ? selectedIndex : 0);
}, [flatSteps, playFromIndex, selectedStageId, selectedStepIndex]);
}, [flatSteps, playFromIndex]);
return {
isPlaying,