scale label

This commit is contained in:
taDuc
2026-05-03 01:04:50 +07:00
parent 12c351c68a
commit fca188f0be
4 changed files with 143 additions and 186 deletions

View File

@@ -9,16 +9,13 @@ Nguồn tham chiếu trong code:
- Type snapshot: `FrontEndAdmin/src/uhm/types/sections.ts` (`EditorSnapshot`) - Type snapshot: `FrontEndAdmin/src/uhm/types/sections.ts` (`EditorSnapshot`)
- Build snapshot khi commit: `FrontEndAdmin/src/uhm/lib/editor/snapshot/editorSnapshot.ts` (`buildEditorSnapshot`) - Build snapshot khi commit: `FrontEndAdmin/src/uhm/lib/editor/snapshot/editorSnapshot.ts` (`buildEditorSnapshot`)
## 1) Schema tổng quan (v1) ## 1) Schema tổng quan (v2)
Hiện tại snapshot được ghi với `schema_version: 1`. Hiện tại snapshot mới được ghi với `schema_version: 2`**đã bỏ hẳn `section`** (vì flow BEGo đã có `commits.project_id`).
```ts ```ts
export type CommitSnapshotV1 = { export type CommitSnapshotV2 = {
schema_version: 1; schema_version: 2;
// Project/section đang được edit (FE vẫn giữ tên "section" cho compatibility)
section: { id: string; title: string };
// GeoJSON draft để render map + làm nguồn dựng geometries/link_scopes // GeoJSON draft để render map + làm nguồn dựng geometries/link_scopes
editor_feature_collection?: FeatureCollection; editor_feature_collection?: FeatureCollection;
@@ -67,9 +64,8 @@ Ngoài ra snapshot có `entity_wikis[]` để nối entity <-> wiki.
```mermaid ```mermaid
classDiagram classDiagram
class CommitSnapshotV1 { class CommitSnapshotV2 {
+number schema_version +number schema_version
+SectionRef section
+FeatureCollection editor_feature_collection? +FeatureCollection editor_feature_collection?
+EntitySnapshot[] entities? +EntitySnapshot[] entities?
+GeometrySnapshot[] geometries? +GeometrySnapshot[] geometries?
@@ -78,10 +74,6 @@ classDiagram
+EntityWikiLinkSnapshot[] entity_wikis? +EntityWikiLinkSnapshot[] entity_wikis?
} }
class SectionRef {
+string id
+string title
}
class FeatureCollection { class FeatureCollection {
+string type // "FeatureCollection" +string type // "FeatureCollection"
@@ -174,25 +166,23 @@ classDiagram
+string id +string id
} }
CommitSnapshotV1 --> SectionRef CommitSnapshotV2 --> FeatureCollection
CommitSnapshotV1 --> FeatureCollection
FeatureCollection --> Feature FeatureCollection --> Feature
Feature --> FeatureProperties Feature --> FeatureProperties
CommitSnapshotV1 --> EntitySnapshot CommitSnapshotV2 --> EntitySnapshot
CommitSnapshotV1 --> GeometrySnapshot CommitSnapshotV2 --> GeometrySnapshot
CommitSnapshotV1 --> LinkScopeSnapshot CommitSnapshotV2 --> LinkScopeSnapshot
CommitSnapshotV1 --> WikiSnapshot CommitSnapshotV2 --> WikiSnapshot
CommitSnapshotV1 --> EntityWikiLinkSnapshot CommitSnapshotV2 --> EntityWikiLinkSnapshot
``` ```
## 4) Ý nghĩa từng phần ## 4) Ý nghĩa từng phần
### 4.1 `section` ### 4.1 (Bỏ) `section`
Chỉ là “ref” tối thiểu để biết commit này thuộc project nào: Từ `schema_version: 2`, snapshot **không còn** field `section`.
- `section.id` = `project_id` Nguồn chuẩn để biết commit thuộc project nào là `commits.project_id` (record/endpoint context), không phải snapshot.
- `section.title` = title tại thời điểm commit (phục vụ UI)
### 4.2 `editor_feature_collection` ### 4.2 `editor_feature_collection`
@@ -305,8 +295,7 @@ Ví dụ dưới đây thể hiện:
```json ```json
{ {
"schema_version": 1, "schema_version": 2,
"section": { "id": "019d...project", "title": "Project A" },
"editor_feature_collection": { "editor_feature_collection": {
"type": "FeatureCollection", "type": "FeatureCollection",
"features": [ "features": [

View File

@@ -285,15 +285,15 @@ export default function Map({
"background-color": "#0b1220", "background-color": "#0b1220",
}, },
}, },
{ {
id: "graticules-line", id: "graticules-line",
type: "line", type: "line",
source: "base", source: "base",
"source-layer": "graticules", "source-layer": "ne_10m_graticules_10",
paint: { paint: {
"line-color": "#334155", "line-color": "#334155",
"line-width": [ "line-width": [
"interpolate", "interpolate",
["linear"], ["linear"],
["zoom"], ["zoom"],
0, 0.3, 0, 0.3,
@@ -303,34 +303,34 @@ export default function Map({
"line-opacity": 0.55, "line-opacity": 0.55,
}, },
}, },
{ {
id: "land", id: "land",
type: "fill", type: "fill",
source: "base", source: "base",
"source-layer": "land", "source-layer": "ne_10m_land",
paint: { paint: {
"fill-color": "#1e293b", "fill-color": "#1e293b",
"fill-opacity": 0.25, "fill-opacity": 0.25,
}, },
}, },
{ {
id: "bg-countries-fill", id: "bg-countries-fill",
type: "fill", type: "fill",
source: "base", source: "base",
"source-layer": "countries", "source-layer": "ne_10m_admin_0_countries",
paint: { paint: {
"fill-color": COUNTRY_FILL_COLOR_EXPRESSION, "fill-color": COUNTRY_FILL_COLOR_EXPRESSION,
"fill-opacity": 0.38, "fill-opacity": 0.38,
}, },
}, },
{ {
id: "bg-country-borders-line", id: "bg-country-borders-line",
type: "line", type: "line",
source: "base", source: "base",
"source-layer": "country_borders", "source-layer": "ne_10m_admin_0_boundary_lines_land",
paint: { paint: {
"line-color": "#cbd5e1", "line-color": "#cbd5e1",
"line-width": [ "line-width": [
"interpolate", "interpolate",
["linear"], ["linear"],
["zoom"], ["zoom"],
@@ -341,87 +341,55 @@ export default function Map({
"line-opacity": 0.85, "line-opacity": 0.85,
}, },
}, },
{ {
id: "country-labels", id: "country-labels",
type: "symbol", type: "symbol",
source: "base", source: "base",
// New tiles build uses NaturalEarth label points layer name. // A dedicated label-point layer (1 point per country) to avoid per-tile duplicates.
// If your tile pipeline exposes a different name, adjust here. "source-layer": "country_labels",
"source-layer": "ne_10m_admin_0_label_points", minzoom: 0,
minzoom: 0, layout: {
layout: { "text-field": [
"text-field": [ "coalesce",
"coalesce", ["get", "NAME_EN"],
["get", "sr_subunit"], ["get", "NAME"],
["get", "NAME_EN"], ["get", "ADMIN"],
["get", "NAME"], ["get", "name"],
["get", "ADMIN"], "",
["get", "name"], ],
"",
],
"text-size": [ "text-size": [
"interpolate", "interpolate",
["linear"], ["linear"],
["zoom"], ["zoom"],
0, 10, 0, 15,
3, 11, 1, 16,
6, 14, 2, 17,
4, 19,
6, 23,
], ],
"text-max-width": 10, "text-padding": 0,
"text-allow-overlap": false, "text-max-width": 10,
"symbol-placement": "point", // Prefer showing labels earlier (even if it means some collisions).
}, "text-allow-overlap": true,
paint: { "text-ignore-placement": true,
"text-color": "#e2e8f0", "symbol-placement": "point",
"text-halo-color": "#0b1220", },
"text-halo-width": 1.2, paint: {
"text-halo-blur": 0.5, "text-color": "#e2e8f0",
}, "text-halo-color": "#0b1220",
}, "text-halo-width": 1.2,
// Fallback for tile pipelines that expose country labels under a generic "labels" layer. "text-halo-blur": 0.5,
// Hidden/shown together with "country-labels" toggle. },
{ },
id: "country-labels-alt", {
type: "symbol", id: "regions-line",
source: "base", type: "line",
"source-layer": "labels", source: "base",
minzoom: 0, "source-layer": "ne_10m_geography_regions_polys",
layout: { paint: {
"text-field": [ "line-color": "#475569",
"coalesce", "line-width": [
["get", "name"], "interpolate",
["get", "title"],
["get", "NAME"],
"",
],
"text-size": [
"interpolate",
["linear"],
["zoom"],
0, 10,
3, 11,
6, 14,
],
"text-max-width": 10,
"text-allow-overlap": false,
"symbol-placement": "point",
},
paint: {
"text-color": "#e2e8f0",
"text-halo-color": "#0b1220",
"text-halo-width": 1.2,
"text-halo-blur": 0.5,
},
},
{
id: "regions-line",
type: "line",
source: "base",
"source-layer": "regions",
paint: {
"line-color": "#475569",
"line-width": [
"interpolate",
["linear"], ["linear"],
["zoom"], ["zoom"],
0, 0.2, 0, 0.2,
@@ -431,25 +399,25 @@ export default function Map({
"line-opacity": 0.6, "line-opacity": 0.6,
}, },
}, },
{ {
id: "lakes-fill", id: "lakes-fill",
type: "fill", type: "fill",
source: "base", source: "base",
"source-layer": "lakes", "source-layer": "ne_10m_lakes",
paint: { paint: {
"fill-color": "#1d4ed8", "fill-color": "#1d4ed8",
"fill-opacity": 0.45, "fill-opacity": 0.45,
}, },
}, },
{ {
id: "rivers-line", id: "rivers-line",
type: "line", type: "line",
source: "base", source: "base",
"source-layer": "rivers", "source-layer": "ne_10m_rivers_lake_centerlines",
paint: { paint: {
"line-color": "#38bdf8", "line-color": "#38bdf8",
"line-width": [ "line-width": [
"interpolate", "interpolate",
["linear"], ["linear"],
["zoom"], ["zoom"],
0, 0.25, 0, 0.25,
@@ -459,14 +427,14 @@ export default function Map({
"line-opacity": 0.85, "line-opacity": 0.85,
}, },
}, },
{ {
id: "geolines-line", id: "geolines-line",
type: "line", type: "line",
source: "base", source: "base",
"source-layer": "geolines", "source-layer": "ne_10m_geographic_lines",
paint: { paint: {
"line-color": "#94a3b8", "line-color": "#94a3b8",
"line-width": 1.2, "line-width": 1.2,
"line-opacity": 0.8, "line-opacity": 0.8,
}, },
}, },
@@ -1186,15 +1154,6 @@ function applyBackgroundLayerVisibility(
visibility[layer.id] ? "visible" : "none" visibility[layer.id] ? "visible" : "none"
); );
} }
// Keep fallback country label layer in sync with the primary toggle.
if (map.getLayer("country-labels-alt")) {
map.setLayoutProperty(
"country-labels-alt",
"visibility",
visibility["country-labels"] ? "visible" : "none"
);
}
} }
function syncRasterBaseVisibility(map: maplibregl.Map, shouldShow: boolean) { function syncRasterBaseVisibility(map: maplibregl.Map, shouldShow: boolean) {

View File

@@ -10,11 +10,10 @@ import type { EntityWikiLinkSnapshot } from "@/uhm/types/sections";
export function normalizeEditorSnapshot(raw: unknown): EditorSnapshot | null { export function normalizeEditorSnapshot(raw: unknown): EditorSnapshot | null {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null; if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
const snapshot = raw as EditorSnapshot; const snapshot = raw as EditorSnapshot;
if ( // Accept legacy snapshots (v1) and new ones (v2+). We only require that a FeatureCollection,
snapshot.editor_feature_collection && // if present, is structurally valid. Everything else is treated as optional.
snapshot.editor_feature_collection.type === "FeatureCollection" && const fc = (snapshot as any).editor_feature_collection as FeatureCollection | undefined;
Array.isArray(snapshot.editor_feature_collection.features) if (fc && fc.type === "FeatureCollection" && Array.isArray(fc.features)) {
) {
return snapshot; return snapshot;
} }
return { return {
@@ -225,11 +224,7 @@ export function buildEditorSnapshot(options: {
}); });
return { return {
schema_version: 1, schema_version: 2,
section: {
id: options.section.id,
title: options.section.title,
},
editor_feature_collection: JSON.parse(JSON.stringify(options.draft)) as FeatureCollection, editor_feature_collection: JSON.parse(JSON.stringify(options.draft)) as FeatureCollection,
entities: Array.from(entityRows.values()).map((entity) => { entities: Array.from(entityRows.values()).map((entity) => {
const id = String(entity.id || ""); const id = String(entity.id || "");

View File

@@ -60,8 +60,10 @@ export type SectionSubmission = {
content?: string | null; content?: string | null;
}; };
export type EditorSnapshot = { export type EditorSnapshotV1 = {
schema_version: number; schema_version: 1;
// Legacy: before BEGo flow moved fully to project/commit records, FE stored a minimal "section" ref
// inside snapshot_json. New snapshots omit this entirely.
section: { section: {
id: string; id: string;
title: string; title: string;
@@ -74,6 +76,18 @@ export type EditorSnapshot = {
entity_wikis?: EntityWikiLinkSnapshot[]; entity_wikis?: EntityWikiLinkSnapshot[];
}; };
export type EditorSnapshotV2 = {
schema_version: 2;
editor_feature_collection?: FeatureCollection;
entities?: EntitySnapshot[];
geometries?: GeometrySnapshot[];
link_scopes?: LinkScopeSnapshot[];
wikis?: WikiSnapshot[];
entity_wikis?: EntityWikiLinkSnapshot[];
};
export type EditorSnapshot = EditorSnapshotV1 | EditorSnapshotV2;
export type EditorLoadResponse = { export type EditorLoadResponse = {
section: Section; section: Section;
state: SectionState; state: SectionState;