fix ref geometry ref rerollable

This commit is contained in:
taDuc
2026-05-26 10:09:02 +07:00
parent 9d04076921
commit e403413965
4 changed files with 75 additions and 29 deletions
+24 -7
View File
@@ -808,7 +808,8 @@ function EditorPageContent() {
}, [ }, [
activeTimelineFilterEnabled, activeTimelineFilterEnabled,
activeTimelineYear, activeTimelineYear,
editor, editor.mainDraft,
editor.replayDraft,
isReplayEditMode, isReplayEditMode,
isReplayPreviewMode, isReplayPreviewMode,
isViewerPreviewMode, isViewerPreviewMode,
@@ -1193,7 +1194,6 @@ function EditorPageContent() {
internalSetMode, internalSetMode,
mode, mode,
resetReplayPreview, resetReplayPreview,
restoreEditorOriginalMapState,
selectedFeatureIds, selectedFeatureIds,
setHideOutside, setHideOutside,
setReplayFeatureId, setReplayFeatureId,
@@ -1540,12 +1540,23 @@ function EditorPageContent() {
]); ]);
// Visibility cuối cùng theo type/layer, có override riêng cho replay edit/preview. // Visibility cuối cùng theo type/layer, có override riêng cho replay edit/preview.
const replayMarkerGeometryId = useMemo(() => {
if (isReplayPreviewMode) {
const id = String(previewSession?.replay?.geometry_id || replayFeatureId || "").trim();
return id.length ? id : null;
}
if (isReplayEditMode && replayFeatureId) {
return String(replayFeatureId);
}
return null;
}, [isReplayEditMode, isReplayPreviewMode, previewSession?.replay?.geometry_id, replayFeatureId]);
const effectiveGeometryVisibility = useMemo(() => { const effectiveGeometryVisibility = useMemo(() => {
const visibility: Record<string, boolean> = { ...geometryVisibility }; const visibility: Record<string, boolean> = { ...geometryVisibility };
if ((isReplayEditMode || isReplayPreviewMode) && replayFeatureId) { if ((isReplayEditMode || isReplayPreviewMode) && replayMarkerGeometryId) {
// Ẩn chính geo được chọn làm replay (marker kịch bản) // Ẩn chính geo được chọn làm replay (marker kịch bản)
visibility[String(replayFeatureId)] = false; visibility[replayMarkerGeometryId] = false;
if (isReplayEditMode && hideOutside) { if (isReplayEditMode && hideOutside) {
// Trong mode replay, ta chỉ hiển thị những gì có trong draft của replay đó // Trong mode replay, ta chỉ hiển thị những gì có trong draft của replay đó
@@ -1553,7 +1564,7 @@ function EditorPageContent() {
// Ẩn tất cả các geo KHÔNG nằm trong draft replay hiện tại // Ẩn tất cả các geo KHÔNG nằm trong draft replay hiện tại
Object.keys(visibility).forEach(fid => { Object.keys(visibility).forEach(fid => {
if (fid === String(replayFeatureId)) { if (fid === replayMarkerGeometryId) {
visibility[fid] = false; visibility[fid] = false;
} else { } else {
visibility[fid] = currentReplayFeatureIds.has(fid); visibility[fid] = currentReplayFeatureIds.has(fid);
@@ -1569,7 +1580,7 @@ function EditorPageContent() {
hideOutside, hideOutside,
isReplayEditMode, isReplayEditMode,
isReplayPreviewMode, isReplayPreviewMode,
replayFeatureId, replayMarkerGeometryId,
]); ]);
// Load project editor payload, xử lý auth và pending-submission lock. // Load project editor payload, xử lý auth và pending-submission lock.
@@ -2422,10 +2433,16 @@ function EditorPageContent() {
}); });
const handleRerollGeometryId = useCallback((oldId: string | number) => { const handleRerollGeometryId = useCallback((oldId: string | number) => {
const feature = editor.draft.features.find((item) => String(item.properties.id) === String(oldId));
if (!feature || feature.properties.source === "ref") {
flashEntityFormStatus("Không thể đổi ID geometry ref vì đây là identity từ backend.");
return;
}
const nextId = newId(); const nextId = newId();
editor.changeFeatureId(oldId, nextId); editor.changeFeatureId(oldId, nextId);
setSelectedFeatureIds((prev) => prev.map((id) => String(id) === String(oldId) ? nextId : id)); setSelectedFeatureIds((prev) => prev.map((id) => String(id) === String(oldId) ? nextId : id));
}, [editor, setSelectedFeatureIds]); }, [editor, flashEntityFormStatus, setSelectedFeatureIds]);
const handleRerollEntityId = useCallback((oldId: string, nextId: string) => { const handleRerollEntityId = useCallback((oldId: string, nextId: string) => {
const activeEntity = entities.find(e => e.id === oldId); const activeEntity = entities.find(e => e.id === oldId);
@@ -87,6 +87,10 @@ export default function SelectedGeometryPanel({
if (!selectedFeatures || selectedFeatures.length === 0) return null; if (!selectedFeatures || selectedFeatures.length === 0) return null;
const representativeFeature = selectedFeatures[0]; const representativeFeature = selectedFeatures[0];
const canRerollGeometryId =
!isBulkMode &&
representativeFeature.properties.source !== "ref" &&
Boolean(onRerollGeometryId);
const groupedGeometryTypeOptions = groupGeometryTypeOptions(GEOMETRY_TYPE_OPTIONS); const groupedGeometryTypeOptions = groupGeometryTypeOptions(GEOMETRY_TYPE_OPTIONS);
const featureGeometryPreset = resolveFeatureGeometryPreset(representativeFeature); const featureGeometryPreset = resolveFeatureGeometryPreset(representativeFeature);
@@ -225,7 +229,7 @@ export default function SelectedGeometryPanel({
<div style={{ color: "#94a3b8", fontSize: "11px", overflowWrap: "anywhere", minWidth: 0, flex: 1 }}> <div style={{ color: "#94a3b8", fontSize: "11px", overflowWrap: "anywhere", minWidth: 0, flex: 1 }}>
{isBulkMode ? `Đang chọn ${selectedFeatures.length} geometries` : `ID: ${representativeFeature.properties.id}`} {isBulkMode ? `Đang chọn ${selectedFeatures.length} geometries` : `ID: ${representativeFeature.properties.id}`}
</div> </div>
{!isBulkMode && onRerollGeometryId && ( {canRerollGeometryId && onRerollGeometryId && (
<button <button
type="button" type="button"
onClick={() => onRerollGeometryId(representativeFeature.properties.id)} onClick={() => onRerollGeometryId(representativeFeature.properties.id)}
+21 -2
View File
@@ -23,6 +23,7 @@ type FeatureLabelInfo = {
label: string; label: string;
timeEnd: number | null; timeEnd: number | null;
}; };
const rasterBaseVisibilityGenerationByMap = new WeakMap<maplibregl.Map, number>();
export function applyBackgroundLayerVisibility( export function applyBackgroundLayerVisibility(
map: maplibregl.Map, map: maplibregl.Map,
@@ -47,19 +48,34 @@ export function applyBackgroundLayerVisibility(
} }
export function syncRasterBaseVisibility(map: maplibregl.Map, shouldShow: boolean) { export function syncRasterBaseVisibility(map: maplibregl.Map, shouldShow: boolean) {
const generation = nextRasterBaseVisibilityGeneration(map);
const isCurrentRequest = () => rasterBaseVisibilityGenerationByMap.get(map) === generation;
if (shouldShow) { if (shouldShow) {
void ensureRasterBaseLayer(map).catch((error) => { void ensureRasterBaseLayer(map, isCurrentRequest).catch((error) => {
console.error("Failed to load proxied raster background.", error); console.error("Failed to load proxied raster background.", error);
if (isCurrentRequest()) {
removeRasterBaseLayer(map); removeRasterBaseLayer(map);
}
}); });
return; return;
} }
removeRasterBaseLayer(map); removeRasterBaseLayer(map);
} }
export async function ensureRasterBaseLayer(map: maplibregl.Map) { function nextRasterBaseVisibilityGeneration(map: maplibregl.Map) {
const next = (rasterBaseVisibilityGenerationByMap.get(map) || 0) + 1;
rasterBaseVisibilityGenerationByMap.set(map, next);
return next;
}
export async function ensureRasterBaseLayer(
map: maplibregl.Map,
isCurrentRequest: () => boolean = () => true
) {
if (!map.getSource(RASTER_BASE_SOURCE_ID)) { if (!map.getSource(RASTER_BASE_SOURCE_ID)) {
const source = await createRasterBaseSource(); const source = await createRasterBaseSource();
if (!isCurrentRequest()) return;
if (map.getSource(RASTER_BASE_SOURCE_ID)) { if (map.getSource(RASTER_BASE_SOURCE_ID)) {
// Another caller already added the source while we were waiting. // Another caller already added the source while we were waiting.
} else { } else {
@@ -67,6 +83,8 @@ export async function ensureRasterBaseLayer(map: maplibregl.Map) {
} }
} }
if (!isCurrentRequest()) return;
const beforeId = getRasterBaseInsertBeforeLayerId(map); const beforeId = getRasterBaseInsertBeforeLayerId(map);
if (!map.getLayer(RASTER_BASE_LAYER_ID)) { if (!map.getLayer(RASTER_BASE_LAYER_ID)) {
map.addLayer(createRasterBaseLayer(), beforeId); map.addLayer(createRasterBaseLayer(), beforeId);
@@ -74,6 +92,7 @@ export async function ensureRasterBaseLayer(map: maplibregl.Map) {
map.moveLayer(RASTER_BASE_LAYER_ID, beforeId); map.moveLayer(RASTER_BASE_LAYER_ID, beforeId);
} }
if (!isCurrentRequest()) return;
map.setLayoutProperty(RASTER_BASE_LAYER_ID, "visibility", "visible"); map.setLayoutProperty(RASTER_BASE_LAYER_ID, "visibility", "visible");
} }
+23 -17
View File
@@ -34,18 +34,18 @@ export default function TimelineBar({
const effectiveDisabled = disabled; const effectiveDisabled = disabled;
const safeYear = clampYearValue(year, lower, upper); const safeYear = clampYearValue(year, lower, upper);
const [localYear, setLocalYear] = useState(safeYear); const [localYear, setLocalYear] = useState<number | null>(null);
const displayYear = localYear ?? safeYear;
// Đồng bộ prop year với localYear khi prop year thay đổi từ bên ngoài const localYearRef = useRef(displayYear);
useEffect(() => {
setLocalYear(safeYear);
}, [safeYear]);
const localYearRef = useRef(localYear);
localYearRef.current = localYear;
const onYearChangeRef = useRef(onYearChange); const onYearChangeRef = useRef(onYearChange);
useEffect(() => {
localYearRef.current = displayYear;
}, [displayYear]);
useEffect(() => {
onYearChangeRef.current = onYearChange; onYearChangeRef.current = onYearChange;
}, [onYearChange]);
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null); const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
const intervalRef = useRef<NodeJS.Timeout | null>(null); const intervalRef = useRef<NodeJS.Timeout | null>(null);
@@ -66,6 +66,7 @@ export default function TimelineBar({
const handleLocalYearChange = useCallback((nextVal: number) => { const handleLocalYearChange = useCallback((nextVal: number) => {
const clamped = clampYearValue(Math.trunc(nextVal), lower, upper); const clamped = clampYearValue(Math.trunc(nextVal), lower, upper);
localYearRef.current = clamped;
setLocalYear(clamped); setLocalYear(clamped);
const now = Date.now(); const now = Date.now();
@@ -81,6 +82,11 @@ export default function TimelineBar({
} }
}, [lower, upper, commitYearChange]); }, [lower, upper, commitYearChange]);
const finishLocalYearChange = useCallback(() => {
commitYearChange(localYearRef.current);
setLocalYear(null);
}, [commitYearChange]);
const startChangingYear = (direction: number) => { const startChangingYear = (direction: number) => {
if (effectiveDisabled) return; if (effectiveDisabled) return;
const nextVal = localYearRef.current + direction; const nextVal = localYearRef.current + direction;
@@ -108,7 +114,7 @@ export default function TimelineBar({
clearInterval(intervalRef.current); clearInterval(intervalRef.current);
intervalRef.current = null; intervalRef.current = null;
} }
commitYearChange(localYearRef.current); finishLocalYearChange();
}; };
useEffect(() => { useEffect(() => {
@@ -163,10 +169,10 @@ export default function TimelineBar({
min={lower} min={lower}
max={upper} max={upper}
step={1} step={1}
value={localYear} value={displayYear}
onChange={(event) => handleLocalYearChange(Number(event.target.value))} onChange={(event) => handleLocalYearChange(Number(event.target.value))}
onMouseUp={() => commitYearChange(localYearRef.current)} onMouseUp={finishLocalYearChange}
onTouchEnd={() => commitYearChange(localYearRef.current)} onTouchEnd={finishLocalYearChange}
disabled={effectiveDisabled} disabled={effectiveDisabled}
className={styles.slider} className={styles.slider}
aria-label="Timeline year" aria-label="Timeline year"
@@ -180,12 +186,12 @@ export default function TimelineBar({
min={lower} min={lower}
max={upper} max={upper}
step={1} step={1}
value={localYear} value={displayYear}
onChange={(event) => handleLocalYearChange(Number(event.target.value))} onChange={(event) => handleLocalYearChange(Number(event.target.value))}
onBlur={() => commitYearChange(localYearRef.current)} onBlur={finishLocalYearChange}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
commitYearChange(localYearRef.current); finishLocalYearChange();
} }
}} }}
disabled={effectiveDisabled} disabled={effectiveDisabled}