feat: add hide/show all geometry actions and implement geometry cache optimization for map rendering
Build and Release / release (push) Successful in 1m2s
Build and Release / release (push) Successful in 1m2s
This commit is contained in:
@@ -201,7 +201,7 @@ const narrativeActionDefinitions: NarrativeActionDefinitionMap = {
|
|||||||
text: string;
|
text: string;
|
||||||
image_url?: string;
|
image_url?: string;
|
||||||
} = {
|
} = {
|
||||||
text: asString(values.text),
|
text: asString(values.text).replace(/ /g, " ").replace(/\u00a0/g, " "),
|
||||||
};
|
};
|
||||||
if (values.image_url) {
|
if (values.image_url) {
|
||||||
data.image_url = asString(values.image_url);
|
data.image_url = asString(values.image_url);
|
||||||
@@ -882,6 +882,26 @@ function GeoFunctionShortcutPanel({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ShortcutButton
|
||||||
|
label="Ẩn Toàn Bộ"
|
||||||
|
tone="slate"
|
||||||
|
onClick={() =>
|
||||||
|
onAppendActions(
|
||||||
|
[{ function_name: "hide_all_geometries", params: [] }],
|
||||||
|
"Geo: ẩn toàn bộ"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ShortcutButton
|
||||||
|
label="Hiện Toàn Bộ"
|
||||||
|
tone="green"
|
||||||
|
onClick={() =>
|
||||||
|
onAppendActions(
|
||||||
|
[{ function_name: "show_all_geometries", params: [] }],
|
||||||
|
"Geo: hiện toàn bộ"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
<ShortcutButton
|
<ShortcutButton
|
||||||
label="Đặt làm BG"
|
label="Đặt làm BG"
|
||||||
tone="teal"
|
tone="teal"
|
||||||
@@ -1410,7 +1430,7 @@ function FieldInput({
|
|||||||
|
|
||||||
if (field.kind === "rich-text") {
|
if (field.kind === "rich-text") {
|
||||||
return (
|
return (
|
||||||
<label style={{ display: "grid", gap: 6 }}>
|
<div style={{ display: "grid", gap: 6 }}>
|
||||||
{baseLabel}
|
{baseLabel}
|
||||||
<div style={{ background: "#0b1220", borderRadius: 6, border: "1px solid #334155" }} className="dark">
|
<div style={{ background: "#0b1220", borderRadius: 6, border: "1px solid #334155" }} className="dark">
|
||||||
<ReactQuillEditor
|
<ReactQuillEditor
|
||||||
@@ -1420,7 +1440,7 @@ function FieldInput({
|
|||||||
modules={quillModules}
|
modules={quillModules}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1717,7 +1737,12 @@ function replaceUiActionsByGroup(
|
|||||||
|
|
||||||
const nextGroupActions = groupOptions
|
const nextGroupActions = groupOptions
|
||||||
.filter((option) => {
|
.filter((option) => {
|
||||||
if (option === "timeline" || option === "layer_panel" || option === "zoom_panel") {
|
if (
|
||||||
|
option === "timeline" ||
|
||||||
|
option === "layer_panel" ||
|
||||||
|
option === "zoom_panel" ||
|
||||||
|
option === "wiki"
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return draft.selected[option];
|
return draft.selected[option];
|
||||||
@@ -1734,7 +1759,12 @@ function buildUiEffectsApplyLabel(
|
|||||||
) {
|
) {
|
||||||
const activeLabels = groupOptions
|
const activeLabels = groupOptions
|
||||||
.filter((option) => {
|
.filter((option) => {
|
||||||
if (option === "timeline" || option === "layer_panel" || option === "zoom_panel") {
|
if (
|
||||||
|
option === "timeline" ||
|
||||||
|
option === "layer_panel" ||
|
||||||
|
option === "zoom_panel" ||
|
||||||
|
option === "wiki"
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return draft.selected[option];
|
return draft.selected[option];
|
||||||
@@ -1744,6 +1774,9 @@ function buildUiEffectsApplyLabel(
|
|||||||
if (option === "timeline" || option === "layer_panel" || option === "zoom_panel") {
|
if (option === "timeline" || option === "layer_panel" || option === "zoom_panel") {
|
||||||
return draft.selected[option] ? `Show ${label}` : `Hide ${label}`;
|
return draft.selected[option] ? `Show ${label}` : `Hide ${label}`;
|
||||||
}
|
}
|
||||||
|
if (option === "wiki") {
|
||||||
|
return draft.wiki_id ? `Open ${label}: ${draft.wiki_id}` : `Close ${label}`;
|
||||||
|
}
|
||||||
return label;
|
return label;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -240,17 +240,12 @@ function getBackgroundGeometryIdsFromReplay(replay: BattleReplay | null): Set<st
|
|||||||
? Math.max(...stages.map((stage) => stage.id)) + 1
|
? Math.max(...stages.map((stage) => stage.id)) + 1
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const bgIds = getBackgroundGeometryIdsFromReplay(replay);
|
const initialGeoFunctions = [
|
||||||
const geometriesToHide = (replay.target_geometry_ids || []).filter(
|
{
|
||||||
(id: string) => !bgIds.has(String(id))
|
function_name: "hide_all_geometries" as const,
|
||||||
);
|
params: [],
|
||||||
const initialGeoFunctions = [];
|
},
|
||||||
if (geometriesToHide.length > 0) {
|
];
|
||||||
initialGeoFunctions.push({
|
|
||||||
function_name: "set_geometry_visibility" as const,
|
|
||||||
params: [geometriesToHide, false],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextStage: ReplayStage = {
|
const nextStage: ReplayStage = {
|
||||||
id: nextId,
|
id: nextId,
|
||||||
@@ -583,22 +578,13 @@ function getBackgroundGeometryIdsFromReplay(replay: BattleReplay | null): Set<st
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onPlayPreviewFromSelection}
|
onClick={onPlayPreviewFromSelection}
|
||||||
disabled={!replay || selectedStage == null || selectedStepIndex == null}
|
disabled={true /* Tạm thời khóa nút này */}
|
||||||
style={{
|
style={{
|
||||||
...buttonStyle,
|
...buttonStyle,
|
||||||
background:
|
background: "#1e293b",
|
||||||
!replay || selectedStage == null || selectedStepIndex == null
|
|
||||||
? "#1e293b"
|
|
||||||
: "#0f766e",
|
|
||||||
border: "none",
|
border: "none",
|
||||||
cursor:
|
cursor: "not-allowed",
|
||||||
!replay || selectedStage == null || selectedStepIndex == null
|
opacity: 0.7,
|
||||||
? "not-allowed"
|
|
||||||
: "pointer",
|
|
||||||
opacity:
|
|
||||||
!replay || selectedStage == null || selectedStepIndex == null
|
|
||||||
? 0.7
|
|
||||||
: 1,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Play từ step
|
Play từ step
|
||||||
@@ -1435,6 +1421,8 @@ const geoFunctionLabels: Record<GeoFunctionName, string> = {
|
|||||||
orbit_camera_around_geometry: "Orbit quanh geo",
|
orbit_camera_around_geometry: "Orbit quanh geo",
|
||||||
set_as_background_geometries: "Đặt làm background",
|
set_as_background_geometries: "Đặt làm background",
|
||||||
remove_from_background_geometries: "Loại khỏi background",
|
remove_from_background_geometries: "Loại khỏi background",
|
||||||
|
hide_all_geometries: "Ẩn toàn bộ geo",
|
||||||
|
show_all_geometries: "Hiện toàn bộ geo",
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildStepActionEntries(step: ReplayStep): StepActionEntry[] {
|
function buildStepActionEntries(step: ReplayStep): StepActionEntry[] {
|
||||||
@@ -1591,6 +1579,12 @@ function buildGeoActionEntry(
|
|||||||
case "remove_from_background_geometries":
|
case "remove_from_background_geometries":
|
||||||
summary = `geometry=${summarizeGeometryIdsValue(params[0])}`;
|
summary = `geometry=${summarizeGeometryIdsValue(params[0])}`;
|
||||||
break;
|
break;
|
||||||
|
case "hide_all_geometries":
|
||||||
|
summary = "Ẩn toàn bộ geo ngoại trừ background";
|
||||||
|
break;
|
||||||
|
case "show_all_geometries":
|
||||||
|
summary = "Hiện toàn bộ geo";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ export function buildPolygonLabelFeatureCollection(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelPoint = getPolygonLabelPoint(feature.geometry);
|
const labelPoint = getPolygonLabelPoint(feature.geometry, feature.properties.id);
|
||||||
if (!labelPoint) continue;
|
if (!labelPoint) continue;
|
||||||
|
|
||||||
const labelFeature: Feature = {
|
const labelFeature: Feature = {
|
||||||
@@ -485,6 +485,32 @@ export function getGeometryRepresentativePoint(geometry: Geometry): Coordinate |
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pathArrowGeometriesCache = new WeakMap<Geometry, Geometry[]>();
|
const pathArrowGeometriesCache = new WeakMap<Geometry, Geometry[]>();
|
||||||
|
const pathArrowGeometriesL2Cache = new Map<string, Geometry[]>();
|
||||||
|
const polygonLabelPointL2Cache = new Map<string, Coordinate | null>();
|
||||||
|
const MAX_L2_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
|
export function clearGeometryCaches() {
|
||||||
|
pathArrowGeometriesL2Cache.clear();
|
||||||
|
polygonLabelPointL2Cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGeometryFingerprint(geometry: Geometry): string {
|
||||||
|
const coords = geometry.coordinates;
|
||||||
|
if (!Array.isArray(coords)) return geometry.type;
|
||||||
|
|
||||||
|
if (typeof coords[0] === "number") {
|
||||||
|
return `${geometry.type}:${coords[0]},${coords[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flat = coords.flat(4) as number[];
|
||||||
|
if (flat.length === 0) return geometry.type;
|
||||||
|
|
||||||
|
const len = flat.length;
|
||||||
|
const first = flat[0];
|
||||||
|
const last = flat[len - 1];
|
||||||
|
const mid = flat[Math.floor(len / 2)];
|
||||||
|
return `${geometry.type}:${len}:${first}:${mid}:${last}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildPathArrowFeatureCollection(fc: FeatureCollection): FeatureCollection {
|
export function buildPathArrowFeatureCollection(fc: FeatureCollection): FeatureCollection {
|
||||||
const features: Feature[] = [];
|
const features: Feature[] = [];
|
||||||
@@ -494,6 +520,14 @@ export function buildPathArrowFeatureCollection(fc: FeatureCollection): FeatureC
|
|||||||
|
|
||||||
let arrowGeometries = pathArrowGeometriesCache.get(feature.geometry);
|
let arrowGeometries = pathArrowGeometriesCache.get(feature.geometry);
|
||||||
if (!arrowGeometries) {
|
if (!arrowGeometries) {
|
||||||
|
const featureId = feature.properties?.id;
|
||||||
|
const fingerprint = getGeometryFingerprint(feature.geometry);
|
||||||
|
const cacheKey = featureId ? `${featureId}:${fingerprint}` : null;
|
||||||
|
|
||||||
|
if (cacheKey && pathArrowGeometriesL2Cache.has(cacheKey)) {
|
||||||
|
arrowGeometries = pathArrowGeometriesL2Cache.get(cacheKey)!;
|
||||||
|
pathArrowGeometriesCache.set(feature.geometry, arrowGeometries);
|
||||||
|
} else {
|
||||||
arrowGeometries = [];
|
arrowGeometries = [];
|
||||||
const coordinateGroups = getLineCoordinateGroups(feature.geometry);
|
const coordinateGroups = getLineCoordinateGroups(feature.geometry);
|
||||||
const featureType = getFeatureSemanticType(feature);
|
const featureType = getFeatureSemanticType(feature);
|
||||||
@@ -503,6 +537,13 @@ export function buildPathArrowFeatureCollection(fc: FeatureCollection): FeatureC
|
|||||||
if (geometry) arrowGeometries.push(geometry);
|
if (geometry) arrowGeometries.push(geometry);
|
||||||
}
|
}
|
||||||
pathArrowGeometriesCache.set(feature.geometry, arrowGeometries);
|
pathArrowGeometriesCache.set(feature.geometry, arrowGeometries);
|
||||||
|
if (cacheKey) {
|
||||||
|
if (pathArrowGeometriesL2Cache.size >= MAX_L2_CACHE_SIZE) {
|
||||||
|
pathArrowGeometriesL2Cache.clear();
|
||||||
|
}
|
||||||
|
pathArrowGeometriesL2Cache.set(cacheKey, arrowGeometries);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const geometry of arrowGeometries) {
|
for (const geometry of arrowGeometries) {
|
||||||
@@ -1163,10 +1204,20 @@ function getLineCoordinateGroups(geometry: Geometry): Coordinate[][] {
|
|||||||
|
|
||||||
const polygonLabelPointCache = new WeakMap<Geometry, Coordinate | null>();
|
const polygonLabelPointCache = new WeakMap<Geometry, Coordinate | null>();
|
||||||
|
|
||||||
function getPolygonLabelPoint(geometry: Geometry): Coordinate | null {
|
function getPolygonLabelPoint(geometry: Geometry, featureId?: string | number): Coordinate | null {
|
||||||
if (polygonLabelPointCache.has(geometry)) {
|
if (polygonLabelPointCache.has(geometry)) {
|
||||||
return polygonLabelPointCache.get(geometry)!;
|
return polygonLabelPointCache.get(geometry)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fingerprint = getGeometryFingerprint(geometry);
|
||||||
|
const cacheKey = featureId ? `${featureId}:${fingerprint}` : null;
|
||||||
|
|
||||||
|
if (cacheKey && polygonLabelPointL2Cache.has(cacheKey)) {
|
||||||
|
const result = polygonLabelPointL2Cache.get(cacheKey)!;
|
||||||
|
polygonLabelPointCache.set(geometry, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
let result: Coordinate | null = null;
|
let result: Coordinate | null = null;
|
||||||
if (geometry.type === "Polygon") {
|
if (geometry.type === "Polygon") {
|
||||||
result = getPolygonLabelCandidate(geometry.coordinates)?.point || null;
|
result = getPolygonLabelCandidate(geometry.coordinates)?.point || null;
|
||||||
@@ -1181,7 +1232,14 @@ function getPolygonLabelPoint(geometry: Geometry): Coordinate | null {
|
|||||||
}
|
}
|
||||||
result = best?.point || null;
|
result = best?.point || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
polygonLabelPointCache.set(geometry, result);
|
polygonLabelPointCache.set(geometry, result);
|
||||||
|
if (cacheKey) {
|
||||||
|
if (polygonLabelPointL2Cache.size >= MAX_L2_CACHE_SIZE) {
|
||||||
|
polygonLabelPointL2Cache.clear();
|
||||||
|
}
|
||||||
|
polygonLabelPointL2Cache.set(cacheKey, result);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,9 @@ export type GeoFunctionName =
|
|||||||
| "set_geometry_style"
|
| "set_geometry_style"
|
||||||
| "orbit_camera_around_geometry"
|
| "orbit_camera_around_geometry"
|
||||||
| "set_as_background_geometries"
|
| "set_as_background_geometries"
|
||||||
| "remove_from_background_geometries";
|
| "remove_from_background_geometries"
|
||||||
|
| "hide_all_geometries"
|
||||||
|
| "show_all_geometries";
|
||||||
|
|
||||||
export type NarrativeFunctionName =
|
export type NarrativeFunctionName =
|
||||||
| "set_dialog";
|
| "set_dialog";
|
||||||
@@ -283,6 +285,8 @@ export type ReplayGeoFunctionParamTupleDocs = {
|
|||||||
remove_from_background_geometries: [
|
remove_from_background_geometries: [
|
||||||
geometry_ids: string[],
|
geometry_ids: string[],
|
||||||
];
|
];
|
||||||
|
hide_all_geometries: [];
|
||||||
|
show_all_geometries: [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReplayNarrativeParamTupleDocs = {
|
export type ReplayNarrativeParamTupleDocs = {
|
||||||
|
|||||||
@@ -1193,6 +1193,18 @@ function normalizeReplayMapAndGeoActions(
|
|||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "hide_all_geometries":
|
||||||
|
normalizedGeoActions.push({
|
||||||
|
function_name: "hide_all_geometries",
|
||||||
|
params: [],
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "show_all_geometries":
|
||||||
|
normalizedGeoActions.push({
|
||||||
|
function_name: "show_all_geometries",
|
||||||
|
params: [],
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export interface ReplayControllers {
|
|||||||
hideGeometries: (ids: string[]) => void;
|
hideGeometries: (ids: string[]) => void;
|
||||||
showOnlyGeometries: (ids: string[]) => void;
|
showOnlyGeometries: (ids: string[]) => void;
|
||||||
showAllGeometries: () => void;
|
showAllGeometries: () => void;
|
||||||
|
hideAllGeometries: () => void;
|
||||||
setAsBackgroundGeometries: (ids: string[]) => void;
|
setAsBackgroundGeometries: (ids: string[]) => void;
|
||||||
removeFromBackgroundGeometries: (ids: string[]) => void;
|
removeFromBackgroundGeometries: (ids: string[]) => void;
|
||||||
|
|
||||||
@@ -98,6 +99,12 @@ export const dispatchReplayAction = (
|
|||||||
case "hide_others_geometries":
|
case "hide_others_geometries":
|
||||||
controllers.showOnlyGeometries(toStringValues(params[0]));
|
controllers.showOnlyGeometries(toStringValues(params[0]));
|
||||||
return;
|
return;
|
||||||
|
case "hide_all_geometries":
|
||||||
|
controllers.hideAllGeometries();
|
||||||
|
return;
|
||||||
|
case "show_all_geometries":
|
||||||
|
controllers.showAllGeometries();
|
||||||
|
return;
|
||||||
case "pulse_geometry":
|
case "pulse_geometry":
|
||||||
controllers.effects.pulseGeometry(
|
controllers.effects.pulseGeometry(
|
||||||
map,
|
map,
|
||||||
@@ -247,8 +254,10 @@ function normalizeSingleAction(action: unknown): ReplayAction<ReplayFunctionName
|
|||||||
case "reset_camera_north":
|
case "reset_camera_north":
|
||||||
return { function_name: "set_camera_view", params: [{ bearing: 0 }] };
|
return { function_name: "set_camera_view", params: [{ bearing: 0 }] };
|
||||||
case "set_time_filter":
|
case "set_time_filter":
|
||||||
case "show_all_geometries":
|
|
||||||
return null;
|
return null;
|
||||||
|
case "hide_all_geometries":
|
||||||
|
case "show_all_geometries":
|
||||||
|
return { function_name, params: [] };
|
||||||
|
|
||||||
// Geo Functions
|
// Geo Functions
|
||||||
case "fly_to_geometries":
|
case "fly_to_geometries":
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { BattleReplay, ReplayStage, ReplayStep, DialogState } from "@/uhm/t
|
|||||||
import { dispatchReplayAction } from "./replayDispatcher";
|
import { dispatchReplayAction } from "./replayDispatcher";
|
||||||
import { mapActions } from "./mapActions";
|
import { mapActions } from "./mapActions";
|
||||||
import { createReplayMapEffects } from "./replayMapEffects";
|
import { createReplayMapEffects } from "./replayMapEffects";
|
||||||
|
import { clearGeometryCaches } from "@/uhm/components/map/mapUtils";
|
||||||
|
|
||||||
export type ReplayPreviewToast = {
|
export type ReplayPreviewToast = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -205,11 +206,13 @@ export function useReplayPreview({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clearGeometryCaches();
|
||||||
}, [getMapInstance, resetPresentation, setMapProjection]);
|
}, [getMapInstance, resetPresentation, setMapProjection]);
|
||||||
|
|
||||||
const resetPreview = useCallback(() => {
|
const resetPreview = useCallback(() => {
|
||||||
runIdRef.current += 1;
|
runIdRef.current += 1;
|
||||||
restorePreviewState();
|
restorePreviewState();
|
||||||
|
clearGeometryCaches();
|
||||||
}, [restorePreviewState]);
|
}, [restorePreviewState]);
|
||||||
|
|
||||||
const stopPreview = useCallback(() => {
|
const stopPreview = useCallback(() => {
|
||||||
@@ -217,6 +220,7 @@ export function useReplayPreview({
|
|||||||
isPlayingRef.current = false;
|
isPlayingRef.current = false;
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
getMapInstance()?.stop();
|
getMapInstance()?.stop();
|
||||||
|
clearGeometryCaches();
|
||||||
}, [getMapInstance]);
|
}, [getMapInstance]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -224,7 +228,10 @@ export function useReplayPreview({
|
|||||||
const timeoutId = window.setTimeout(() => {
|
const timeoutId = window.setTimeout(() => {
|
||||||
restorePreviewState();
|
restorePreviewState();
|
||||||
}, 0);
|
}, 0);
|
||||||
return () => window.clearTimeout(timeoutId);
|
return () => {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
clearGeometryCaches();
|
||||||
|
};
|
||||||
}, [replay?.id, restorePreviewState]);
|
}, [replay?.id, restorePreviewState]);
|
||||||
|
|
||||||
const controllers = useMemo<Parameters<typeof dispatchReplayAction>[0]>(() => ({
|
const controllers = useMemo<Parameters<typeof dispatchReplayAction>[0]>(() => ({
|
||||||
@@ -290,6 +297,13 @@ export function useReplayPreview({
|
|||||||
showAllGeometries: () => {
|
showAllGeometries: () => {
|
||||||
setHiddenGeometryIds([]);
|
setHiddenGeometryIds([]);
|
||||||
},
|
},
|
||||||
|
hideAllGeometries: () => {
|
||||||
|
setHiddenGeometryIds(
|
||||||
|
draft.features
|
||||||
|
.map((feature) => String(feature.properties.id))
|
||||||
|
.filter((id) => !backgroundIdsRef.current.has(id))
|
||||||
|
);
|
||||||
|
},
|
||||||
setDialog: setDialogWithRef,
|
setDialog: setDialogWithRef,
|
||||||
getDialog: () => dialogRef.current,
|
getDialog: () => dialogRef.current,
|
||||||
}), [
|
}), [
|
||||||
@@ -301,13 +315,12 @@ export function useReplayPreview({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const controllersRef = useRef<Parameters<typeof dispatchReplayAction>[0] | null>(null);
|
const controllersRef = useRef<Parameters<typeof dispatchReplayAction>[0] | null>(null);
|
||||||
useEffect(() => {
|
|
||||||
controllersRef.current = controllers;
|
controllersRef.current = controllers;
|
||||||
}, [controllers]);
|
|
||||||
|
|
||||||
const playFromIndex = useCallback(async (startIndex: number) => {
|
const playFromIndex = useCallback(async (startIndex: number) => {
|
||||||
if (!flatSteps.length) return;
|
if (!flatSteps.length) return;
|
||||||
|
|
||||||
|
clearGeometryCaches();
|
||||||
const map = getMapInstance();
|
const map = getMapInstance();
|
||||||
if (map) {
|
if (map) {
|
||||||
map.stop(); // Stop ongoing camera animations/transitions immediately
|
map.stop(); // Stop ongoing camera animations/transitions immediately
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ export type GeoFunctionName =
|
|||||||
| "set_geometry_style" // Đổi style trực tiếp của geometry
|
| "set_geometry_style" // Đổi style trực tiếp của geometry
|
||||||
| "orbit_camera_around_geometry" // Quay camera quanh một geometry
|
| "orbit_camera_around_geometry" // Quay camera quanh một geometry
|
||||||
| "set_as_background_geometries" // Đặt các geometry làm background (luôn hiện)
|
| "set_as_background_geometries" // Đặt các geometry làm background (luôn hiện)
|
||||||
| "remove_from_background_geometries"; // Loại các geometry khỏi background
|
| "remove_from_background_geometries" // Loại các geometry khỏi background
|
||||||
|
| "hide_all_geometries" // Ẩn toàn bộ geometry (ngoại trừ background)
|
||||||
|
| "show_all_geometries"; // Hiện toàn bộ geometry
|
||||||
|
|
||||||
export type NarrativeFunctionName =
|
export type NarrativeFunctionName =
|
||||||
| "set_dialog"; // Đặt kịch bản đối thoại/hình ảnh dẫn chuyện mới (hoặc null để xóa)
|
| "set_dialog"; // Đặt kịch bản đối thoại/hình ảnh dẫn chuyện mới (hoặc null để xóa)
|
||||||
|
|||||||
Reference in New Issue
Block a user