12 KiB
Editor (/editor) - Local Store & Snapshot Conversion
Tài liệu này mô tả chi tiết các nơi lưu trữ state (store) ở phía FrontEndUser trong /editor/[id], ý nghĩa từng biến state, state nào là “single source of truth”, state nào chỉ là cache/UI, và cách chuyển đổi qua lại giữa:
- Local session state (React state trong phiên làm việc)
- Commit snapshot (
commits.snapshot_json) - Reload trang (mất state local, load lại từ commit snapshot)
Mục tiêu: dễ debug, nhất quán dữ liệu, tránh sai semantics "reference"/"binding".
0) 5 Dataset Quan Trọng Nhất (GEO/ENT/WIKI/ENT_WIKI/GEO_ENT)
Trong /editor, 5 nhóm dữ liệu quan trọng nhất tương ứng trực tiếp với snapshot:
- GEO:
snapshot_json.geometries[]+snapshot_json.editor_feature_collection - ENT:
snapshot_json.entities[] - WIKI:
snapshot_json.wikis[] - ENT_WIKI (entity ↔ wiki):
snapshot_json.entity_wiki[] - GEO_ENT (geometry ↔ entity):
snapshot_json.geometry_entity[]
Điểm quan trọng về “store”:
-
ENT/WIKI/ENT_WIKI có store snapshot riêng trong React session:
snapshotEntities->entities[]snapshotWikis->wikis[]snapshotEntityWikiLinks->entity_wiki[]
-
GEO/GEO_ENT không có store snapshot riêng theo kiểu
snapshotGeometries/snapshotGeometryEntity.- Trong session, GEO sống ở
editor.draft(GeoJSON FeatureCollection). - Khi commit, FE build ra:
geometries[]từeditor.draft + editor.changes + baselineSnapshot.geometriesgeometry_entity[]từeditor.draft.features[].properties.entity_ids
- Trong session, GEO sống ở
Vì vậy, nếu bạn “tìm store của geo trong React state” thì bạn sẽ thấy nó nằm ở useEditorState() chứ không nằm trong useEditorSessionState().
1) Nguyên tắc chung
1.1 Single source of truth theo lớp
- Geometry (map/editor):
useEditorState(initialData)là state trung tâm chodraft/changes/undo. - Snapshot stores (phần sẽ đi vào commit snapshot):
snapshotEntities->snapshot_json.entitiessnapshotWikis->snapshot_json.wikissnapshotEntityWikiLinks->snapshot_json.entity_wiki
- Catalog/cache để tìm kiếm & hiển thị:
entityCataloglà danh sách entity “global” trong RAM (fetch + search merge). Không phải snapshot.
1.2 “reference” vs “binding”
"reference"(entities/wikis/geometries.operation) nghĩa là không sửa record trong commit đó."binding"(chỉ áp dụng choentity_wiki.operation) nghĩa là link entity ↔ wiki đang tồn tại trong snapshot."delete"nghĩa là xóa record (entities/wikis/geometries) hoặc unlink (entity_wiki).
Khi mở 1 phiên editor mới từ commit, mọi operation local đều bị “reset về baseline”:
entities[].operationvàwikis[].operationtrong session ->"reference"entity_wiki[].operationtrong session ->"binding"(nếu link còn active)
2) Local state: danh sách đầy đủ và ý nghĩa
Các state này được tạo từ useEditorSessionState() và useEditorState() trong:
FrontEndUser/src/app/editor/[id]/page.tsxFrontEndUser/src/uhm/lib/useEditorSessionState.tsFrontEndUser/src/uhm/lib/useEditorState.ts
2.1 Geometry editor state (core)
Nguồn: const editor = useEditorState(initialData)
-
initialData: FeatureCollection- Là baseline của session hiện tại để render Map ban đầu.
- Được set khi:
- mở project (load snapshot head),
- restore FE-only từ 1 commit,
- hoặc import/replace dữ liệu session.
-
editor.draft: FeatureCollection- Single source of truth cho geometry đang hiển thị + chỉnh sửa.
- Map render trực tiếp từ
draft(hoặc bản “visibleDraft” đã filter theo timeline/binding). - Đây chính là store runtime của GEO trong session.
-
editor.changes: Map<id, Change>- Diff giữa
draftvà baseline map nội bộ (initialMapRef). - Dùng để tính
pendingSaveCountvà để build snapshot geometries/update/delete.
- Diff giữa
-
editor.undoStack- Danh sách thao tác gần nhất (create/update/properties/delete).
-
editor.changeCount- Số lượng changes (để chặn commit khi không đổi gì).
-
editor.hasPersistedFeature(id)truenếu feature đã tồn tại trong baseline map nội bộ.- Dùng cho timeline filter: feature mới tạo trong session vẫn luôn visible.
2.2 Snapshot stores (persisted on commit)
Các state này là “source of truth” cho những phần non-geometry trong commit snapshot.
a) snapshotEntities: EntitySnapshot[]
- Dùng để build
snapshot_json.entities. - Bao gồm:
- entity “pin” vào project (
source:"ref",operation:"reference"), - entity tạo mới local (
source:"inline",operation:"create"), - entity bị xóa (nếu có) (
operation:"delete").
- entity “pin” vào project (
Lưu ý quan trọng:
snapshotEntitieslà nơi “giữ entity” qua các commit, kể cả entity tạo mới chưa bind geometry.buildEditorSnapshot()có logic carry-forward inline entity từpreviousSnapshotđể tránh mất entity sau commit/reload.
b) snapshotWikis: WikiSnapshot[]
- Dùng để build
snapshot_json.wikis. - Wiki hiện lưu
doclà string (HTML) (Quill) hoặcnullvới ref wiki. - Tiptap JSON cũ: được normalize sang HTML để hiển thị.
c) snapshotEntityWikiLinks: EntityWikiLinkSnapshot[]
- Dùng để build
snapshot_json.entity_wiki. operation:"binding": link đang tồn tại"delete": unlink trong snapshot- (compat)
"reference"từ snapshot cũ được normalize thành"binding"khi load.
2.3 Catalog/cache state (không persist)
entityCatalog: Entity[]
Đây là RAM cache để:
- hiển thị tên/description/status của entity,
- merge kết quả fetch + search,
- giảm tình trạng UI “cùng 1 entity nhưng 2 object khác nhau”.
Không ghi thẳng vào snapshot. Snapshot vẫn lấy từ snapshotEntities.
Trong page, danh sách entities dùng cho UI được merge:
entities = mergeEntitySearchResults(entityCatalog, snapshotEntitiesAsEntities)
Nghĩa là: snapshot entities (local) luôn được ưu tiên hiển thị trong UI.
2.4 UI-only state (không persist)
Các state sau chỉ phục vụ UX, mất khi reload:
mode(idle/select/add-*)selectedFeatureIdselectedGeometryEntityIds(list bind tạm thời cho UI, map patch sẽ sync vào feature properties)geometryMetaFormentityForm(tạo entity mới)entityFormStatus(toast/status 3s)searchKind,searchQueryentitySearchResults,wikiSearchResults,geoSearchResultstimelineDraftYear,timelineFilterEnabled- panel widths (
leftPanelWidth,rightPanelWidth)
2.5 LocalStorage (trên browser)
Hiện tại chỉ có 1 thứ persist sang LocalStorage:
backgroundVisibility(ẩn/hiện layer nền)
Các snapshot stores (snapshotEntities, snapshotWikis, snapshotEntityWikiLinks, draft) không lưu LocalStorage; chúng được persist qua commit snapshot (backend).
3) Chuyển đổi giữa local session ↔ snapshot
3.1 Load snapshot -> mở session
Luồng: openSectionEditor() -> normalizeEditorSnapshot() -> toEditorSessionSnapshot()
Khi mở session mới:
baselineSnapshot = toEditorSessionSnapshot(snapshot)initialData = baselineSnapshot.editor_feature_collection || EMPTY_FEATURE_COLLECTIONsnapshotEntities = baselineSnapshot.entities || []snapshotWikis = baselineSnapshot.wikis || []snapshotEntityWikiLinks = baselineSnapshot.entity_wiki || []
Riêng về GEO/GEO_ENT khi load:
baselineSnapshot.editor_feature_collectionlà dữ liệu map gốc đưa vàoinitialData.normalizeEditorSnapshot()sẽ rehydratefeature.properties.entity_ids/entity_idtừsnapshot.geometry_entity[](hoặc legacylink_scopes) để UI bind entity hoạt động.- Lưu ý: đây là rehydrate phục vụ editor UX, không phải dữ liệu persist chính thức trên
feature.propertiestrong snapshot.
- Lưu ý: đây là rehydrate phục vụ editor UX, không phải dữ liệu persist chính thức trên
Điểm mấu chốt: toEditorSessionSnapshot() reset operation để snapshot trở thành “baseline state”:
- entities/wikis ->
"reference" - entity_wiki active ->
"binding"
3.2 Commit session -> snapshot_json
Luồng: commitSection() -> buildEditorSnapshot({ draft, changes, snapshotEntities, snapshotWikis, snapshotEntityWikiLinks, previousSnapshot: baselineSnapshot })
buildEditorSnapshot() sẽ tạo:
editor_feature_collection(draft đã strip các field denormalized)geometries[](create/update/delete dựa trên changes + previousSnapshot)geometry_entity[](join table từ feature.properties.entity_ids)entities[](từ snapshotEntities + carry-forward inline + ensure entities referenced by joins)wikis[](từ snapshotWikis, tương tự)entity_wiki[](từ snapshotEntityWikiLinks, đã dedupe/sort)
Sau khi commit thành công:
baselineSnapshotcập nhật =toEditorSessionSnapshot(snapshot)của commit mới- snapshot stores cập nhật theo baseline mới (operation reset về
"reference"/"binding")
3.3 Reload trang -> mất local state
Khi reload:
- Toàn bộ React state reset
- App sẽ load lại snapshot từ backend (head commit)
- Các thứ bạn “tạo/sửa” chỉ còn lại nếu đã nằm trong commit snapshot
Vì vậy:
- Entity/Wiki/Link/Geometry muốn “không mất” phải đi qua Commit.
- Các state UI (selected geo, search results, form đang nhập) sẽ mất.
4) GEO Search (/geometries/entity) và tác động lên local store
Search GEO gọi:
GET /geometries/entity?name=<keyword>&limit=<n>
Khi bấm Import một geometry từ kết quả search:
- Tắt
timelineFilterEnabledđể geometry luôn nhìn thấy (không bị filter theo năm). - Add entity tương ứng vào:
snapshotEntities(source:"ref", operation:"reference")entityCatalog(để UI có name/description)
- Nếu geometry chưa có trong
editor.draft:- tạo
Featuremới vớiid = geometry.id - set
properties.typetừgeo_type(map quageoTypeCodeToTypeKey) - set
time_start/time_end/binding - set denormalized
entity_id/entity_ids/entity_name/entity_namesđể UI/joins hoạt động
- tạo
editor.createFeature(feature)và auto select feature đó.
Lưu ý: Import geo tạo ra “create change” trong editor session, nên sẽ đi vào commit snapshot.
4.1 Nhìn nhanh “5 dataset nằm ở đâu” trong session
-
GEO:
- Runtime store:
editor.draft.features[] - Persisted on commit:
snapshot_json.geometries[](build khi commit)
- Runtime store:
-
ENT:
- Runtime store (snapshot):
snapshotEntities - Persisted on commit:
snapshot_json.entities[]
- Runtime store (snapshot):
-
WIKI:
- Runtime store (snapshot):
snapshotWikis - Persisted on commit:
snapshot_json.wikis[]
- Runtime store (snapshot):
-
ENT_WIKI:
- Runtime store (snapshot):
snapshotEntityWikiLinks - Persisted on commit:
snapshot_json.entity_wiki[]
- Runtime store (snapshot):
-
GEO_ENT:
- Runtime store: denormalized tạm thời trên
editor.draft.features[].properties.entity_ids(để UI chạy) - Persisted on commit:
snapshot_json.geometry_entity[](build khi commit)
- Runtime store: denormalized tạm thời trên
5) Checklist khi debug “mất dữ liệu”
- Dữ liệu có nằm trong
snapshotEntities/snapshotWikis/snapshotEntityWikiLinks/editor.draftkhông? - Có bấm Commit chưa?
pendingSaveCountcó > 0 không (Commit button có enable không)?- Khi reload, snapshot head commit load lên có chứa các rows đó không?
- Nếu entity tạo mới bị mất:
- kiểm tra commit snapshot có
entities[].source:"inline"không - nếu có mà reload vẫn mất, kiểm tra
normalizeEditorSnapshot()có parse đúng không
6) File/entrypoints liên quan
-
Session stores:
FrontEndUser/src/uhm/lib/useEditorSessionState.tsFrontEndUser/src/uhm/lib/editor/session/useEntitySessionState.tsFrontEndUser/src/uhm/lib/editor/session/useWikiSessionState.tsFrontEndUser/src/uhm/lib/editor/session/useSectionSessionState.ts
-
Geometry editor core:
FrontEndUser/src/uhm/lib/useEditorState.ts
-
Snapshot normalization + build snapshot:
FrontEndUser/src/uhm/lib/editor/snapshot/editorSnapshot.ts
-
Open/commit/restore commands:
FrontEndUser/src/uhm/lib/editor/section/useSectionCommands.ts
-
Page wiring / UI state:
FrontEndUser/src/app/editor/[id]/page.tsx