feat: add hide/show all geometry actions and optimize preview cache management
Build and Release / release (push) Successful in 56s
Build and Release / release (push) Successful in 56s
This commit is contained in:
@@ -201,7 +201,7 @@ const narrativeActionDefinitions: NarrativeActionDefinitionMap = {
|
||||
text: string;
|
||||
image_url?: string;
|
||||
} = {
|
||||
text: asString(values.text),
|
||||
text: asString(values.text).replace(/ /g, " ").replace(/\u00a0/g, " "),
|
||||
};
|
||||
if (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
|
||||
label="Đặt làm BG"
|
||||
tone="teal"
|
||||
@@ -1410,7 +1430,7 @@ function FieldInput({
|
||||
|
||||
if (field.kind === "rich-text") {
|
||||
return (
|
||||
<label style={{ display: "grid", gap: 6 }}>
|
||||
<div style={{ display: "grid", gap: 6 }}>
|
||||
{baseLabel}
|
||||
<div style={{ background: "#0b1220", borderRadius: 6, border: "1px solid #334155" }} className="dark">
|
||||
<ReactQuillEditor
|
||||
@@ -1420,7 +1440,7 @@ function FieldInput({
|
||||
modules={quillModules}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1717,7 +1737,12 @@ function replaceUiActionsByGroup(
|
||||
|
||||
const nextGroupActions = groupOptions
|
||||
.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 draft.selected[option];
|
||||
@@ -1734,7 +1759,12 @@ function buildUiEffectsApplyLabel(
|
||||
) {
|
||||
const activeLabels = groupOptions
|
||||
.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 draft.selected[option];
|
||||
@@ -1744,6 +1774,9 @@ function buildUiEffectsApplyLabel(
|
||||
if (option === "timeline" || option === "layer_panel" || option === "zoom_panel") {
|
||||
return draft.selected[option] ? `Show ${label}` : `Hide ${label}`;
|
||||
}
|
||||
if (option === "wiki") {
|
||||
return draft.wiki_id ? `Open ${label}: ${draft.wiki_id}` : `Close ${label}`;
|
||||
}
|
||||
return label;
|
||||
});
|
||||
|
||||
|
||||
@@ -240,17 +240,12 @@ function getBackgroundGeometryIdsFromReplay(replay: BattleReplay | null): Set<st
|
||||
? Math.max(...stages.map((stage) => stage.id)) + 1
|
||||
: 0;
|
||||
|
||||
const bgIds = getBackgroundGeometryIdsFromReplay(replay);
|
||||
const geometriesToHide = (replay.target_geometry_ids || []).filter(
|
||||
(id: string) => !bgIds.has(String(id))
|
||||
);
|
||||
const initialGeoFunctions = [];
|
||||
if (geometriesToHide.length > 0) {
|
||||
initialGeoFunctions.push({
|
||||
function_name: "set_geometry_visibility" as const,
|
||||
params: [geometriesToHide, false],
|
||||
});
|
||||
}
|
||||
const initialGeoFunctions = [
|
||||
{
|
||||
function_name: "hide_all_geometries" as const,
|
||||
params: [],
|
||||
},
|
||||
];
|
||||
|
||||
const nextStage: ReplayStage = {
|
||||
id: nextId,
|
||||
@@ -583,22 +578,13 @@ function getBackgroundGeometryIdsFromReplay(replay: BattleReplay | null): Set<st
|
||||
<button
|
||||
type="button"
|
||||
onClick={onPlayPreviewFromSelection}
|
||||
disabled={!replay || selectedStage == null || selectedStepIndex == null}
|
||||
disabled={true /* Tạm thời khóa nút này */}
|
||||
style={{
|
||||
...buttonStyle,
|
||||
background:
|
||||
!replay || selectedStage == null || selectedStepIndex == null
|
||||
? "#1e293b"
|
||||
: "#0f766e",
|
||||
background: "#1e293b",
|
||||
border: "none",
|
||||
cursor:
|
||||
!replay || selectedStage == null || selectedStepIndex == null
|
||||
? "not-allowed"
|
||||
: "pointer",
|
||||
opacity:
|
||||
!replay || selectedStage == null || selectedStepIndex == null
|
||||
? 0.7
|
||||
: 1,
|
||||
cursor: "not-allowed",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
Play từ step
|
||||
@@ -1435,6 +1421,8 @@ const geoFunctionLabels: Record<GeoFunctionName, string> = {
|
||||
orbit_camera_around_geometry: "Orbit quanh geo",
|
||||
set_as_background_geometries: "Đặt làm 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[] {
|
||||
@@ -1591,6 +1579,12 @@ function buildGeoActionEntry(
|
||||
case "remove_from_background_geometries":
|
||||
summary = `geometry=${summarizeGeometryIdsValue(params[0])}`;
|
||||
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 {
|
||||
|
||||
@@ -338,7 +338,7 @@ export function buildPolygonLabelFeatureCollection(
|
||||
continue;
|
||||
}
|
||||
|
||||
const labelPoint = getPolygonLabelPoint(feature.geometry);
|
||||
const labelPoint = getPolygonLabelPoint(feature.geometry, feature.properties.id);
|
||||
if (!labelPoint) continue;
|
||||
|
||||
const labelFeature: Feature = {
|
||||
@@ -485,6 +485,32 @@ export function getGeometryRepresentativePoint(geometry: Geometry): Coordinate |
|
||||
}
|
||||
|
||||
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 {
|
||||
const features: Feature[] = [];
|
||||
@@ -494,15 +520,30 @@ export function buildPathArrowFeatureCollection(fc: FeatureCollection): FeatureC
|
||||
|
||||
let arrowGeometries = pathArrowGeometriesCache.get(feature.geometry);
|
||||
if (!arrowGeometries) {
|
||||
arrowGeometries = [];
|
||||
const coordinateGroups = getLineCoordinateGroups(feature.geometry);
|
||||
const featureType = getFeatureSemanticType(feature);
|
||||
const isRetreat = featureType === "retreat_route";
|
||||
for (const coordinates of coordinateGroups) {
|
||||
const geometry = buildPathArrowGeometry(coordinates, isRetreat);
|
||||
if (geometry) arrowGeometries.push(geometry);
|
||||
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 = [];
|
||||
const coordinateGroups = getLineCoordinateGroups(feature.geometry);
|
||||
const featureType = getFeatureSemanticType(feature);
|
||||
const isRetreat = featureType === "retreat_route";
|
||||
for (const coordinates of coordinateGroups) {
|
||||
const geometry = buildPathArrowGeometry(coordinates, isRetreat);
|
||||
if (geometry) arrowGeometries.push(geometry);
|
||||
}
|
||||
pathArrowGeometriesCache.set(feature.geometry, arrowGeometries);
|
||||
if (cacheKey) {
|
||||
if (pathArrowGeometriesL2Cache.size >= MAX_L2_CACHE_SIZE) {
|
||||
pathArrowGeometriesL2Cache.clear();
|
||||
}
|
||||
pathArrowGeometriesL2Cache.set(cacheKey, arrowGeometries);
|
||||
}
|
||||
}
|
||||
pathArrowGeometriesCache.set(feature.geometry, arrowGeometries);
|
||||
}
|
||||
|
||||
for (const geometry of arrowGeometries) {
|
||||
@@ -1163,10 +1204,20 @@ function getLineCoordinateGroups(geometry: Geometry): Coordinate[][] {
|
||||
|
||||
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)) {
|
||||
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;
|
||||
if (geometry.type === "Polygon") {
|
||||
result = getPolygonLabelCandidate(geometry.coordinates)?.point || null;
|
||||
@@ -1181,7 +1232,14 @@ function getPolygonLabelPoint(geometry: Geometry): Coordinate | null {
|
||||
}
|
||||
result = best?.point || null;
|
||||
}
|
||||
|
||||
polygonLabelPointCache.set(geometry, result);
|
||||
if (cacheKey) {
|
||||
if (polygonLabelPointL2Cache.size >= MAX_L2_CACHE_SIZE) {
|
||||
polygonLabelPointL2Cache.clear();
|
||||
}
|
||||
polygonLabelPointL2Cache.set(cacheKey, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user