12 KiB
UHM Editor - state và vòng đời dữ liệu
Tài liệu này mô tả state thật đang được dùng bởi editor hiện tại.
Entry point chính là useEditorSessionState() và useEditorState().
1. Hai lớp state chính
Editor đang tách làm hai khối:
useEditorSessionState()- state UI, session, form, project, timeline, background, wiki
useEditorState(baselineFeatureCollection, snapshotUndo)- state draft hình học, diff và undo
Nói ngắn gọn:
session statequyết định editor đang nhìn cái gì và panel đang thao tác gìeditor statequyết định geometry nào đang tồn tại trong draft và khác baseline ra sao
2. State geometry trung tâm
baselineFeatureCollection
- Nằm ở
useEditorSessionState() - Là
FeatureCollectionbaseline được nạp vào editor khi mở project hoặc restore commit - Khi thay đổi,
useEditorState()sẽ reset toàn bộ draft và baseline tương ứng
mainDraft
- Nằm trong
useEditorState() - Là working copy geometry chính dùng cho edit/commit
- Mọi thao tác create/update/delete geometry đều đi qua đây
editor.draft
- Là draft đang active theo mode
- Ở mode thường trỏ tới
mainDraft - Ở mode
replaytrỏ tớireplayDraft - Panel metadata/selection đọc từ đây, không đọc từ
mapRenderDraft
draftRef
- Ref nội bộ tương ứng với draft trong
useEditorState() - Được dùng để luôn đọc được state mới nhất mà không phải rebind callback liên tục
- Không nhầm với
renderDraftReftrongMap.tsx, vốn là dữ liệu đang render/interact trên map
initialMapRef
Map<featureId, Feature>tạo từbaselineFeatureCollection- Là baseline để tính diff giữa draft hiện tại và dữ liệu gốc của session
changes
- Kết quả
diffDraftToInitial(draft, initialMapRef.current) - Map theo
feature.properties.id - Mỗi phần tử có thể là:
createupdatedelete
Lưu ý: diff hiện chỉ là cơ chế nhận biết geometry nào đã thay đổi so với baseline. Snapshot commit thực tế vẫn được build từ toàn bộ draft cộng với các snapshot bảng phụ.
changeCount
- Số lượng geometry thay đổi hiện tại
- Được cộng thêm dirty state của wiki/entity/entity-wiki/replay để tạo
pendingSaveCount
3. Undo state
Undo được quản lý bởi useUndoStack().
Kiểu action hiện có:
createdeleteupdatepropertiessnapshot_entitiessnapshot_wikissnapshot_entity_wikireplayreplaysreplay_sessiongroup
Ý nghĩa:
- geometry create/delete/update/properties undo được trực tiếp trên
draft - snapshot entity/wiki/link undo được apply qua
snapshotUndoAPI truyền vàouseEditorState replay/replaysundo các thay đổi script replay đã flush vào collection chínhreplay_sessionundo các thay đổi stage/step/action khi đang ở modereplaygroupdùng để gom nhiều thay đổi thành một thao tác undo logic
Editor hiện có undo, nhưng chưa có redo.
4. Session state theo nhóm
4.1. Mode và selection
mode: EditorModeselectedFeatureIdsselectedGeometryEntityIds
selectedFeatureIds là state gốc cho:
- panel metadata geometry
- bind entity
- bind geometry
- focus geometry từ search/binding panel
4.2. Form state
entityForm- dùng cho form tạo entity local
geometryMetaFormtype_keytime_starttime_end
Geometry-geometry bound state không nằm trong geometryMetaForm; GeometryBindingPanel chỉnh trực tiếp feature.properties.bound_with của geometry con.
4.3. Replay state
Replay state nằm trong useEditorState():
replays- collection script đã flush vào state chính
activeReplayDraftBattleReplayđang sửa trong modereplay
replayDraftFeatureCollectionhydrate từmainDraft + activeReplayDraft.target_geometry_ids
effectiveReplaysreplayscộng overlay củaactiveReplayDraftnếu session hiện tại đã đổi nhưng chưa flush
Undo của replay session dùng stack riêng khi mode === "replay".
replay_preview là session preview trong page, dùng previewSession/useReplayPreview() và không persist. Khi thoát các chế độ preview, editor sẽ dọn dẹp hoàn toàn các map effects, highlight, và khôi phục camera view state & projection (Globe/Flat) ban đầu trước khi vào preview.
4.4. Project/session task state
useProjectSessionState() gom các cờ async vào một state machine nhỏ:
sectionTask: "idle" | "saving" | "submitting" | "opening-project"
Từ đó sinh ra:
isSavingisSubmittingisOpeningSection
Ngoài ra còn có:
activeSectionprojectStatesectionCommitsbaselineSnapshotcommitTitle
4.5. Timeline state
useTimelineState() giữ:
timelineYeartimelineDraftYearisTimelineLoadingtimelineStatus
Trong page hiện tại, timeline filter đang dùng timelineDraftYear.
Không có fetch dữ liệu project theo timelineYear; timeline đang là client-side visibility filter.
4.6. Background/session UI
useBackgroundSessionState() giữ:
backgroundVisibilityisBackgroundVisibilityReady
Giá trị thật được load từ localStorage key uhm.backgroundLayerVisibility.v1.
4.7. Wiki/session state
useWikiSessionState() giữ:
snapshotWikissnapshotEntityWikiLinks
Đây là single source of truth cho phần wiki trong snapshot commit.
4.8. Preview session states và refs (Viewer / Replay Preview)
Các states và refs điều khiển preview được khai báo trực tiếp trong page.tsx:
previewSession: ReplayPreviewSession | null- Đóng băng toàn bộ snapshot collections (replays, draft, entities, wikis, links) cùng timeline, filter và camera view state khi chạy preview.
previewAutoplayMode: "start" | "selection" | null- Trạng thái autoplay (bắt đầu từ đầu hay từ step được chọn) của Replay Preview.
previewWikiCache,previewWikiError,isPreviewWikiLoading- Cache và status để hiển thị nội dung Wiki tương tác trong sidebar preview.
previewFeaturePopupAnchor: MapFeaturePayload | null- Neo tọa độ/payload của popup hiển thị thông tin geometry khi click trên map ở preview mode.
previewActiveEntityId,isPreviewEntitySidebarOpen- Sidebar hiển thị chi tiết entity được chọn trong preview.
previewLinkEntityPopup: PreviewLinkEntityPopupState | null- Trạng thái popup điều hướng sang entity khác khi click vào link wiki trong preview.
editorOriginalMapViewStateRef: ReturnType<MapHandle["getViewState"]> | null- Ref lưu giữ camera view state và projection (Globe/Flat) ban đầu của editor trước khi bắt đầu preview, phục vụ việc khôi phục hoàn toàn bản đồ khi exit.
replayPreviewReturnRef: { mode: "replay" | "preview"; session: ReplayPreviewSession | null }- Ref ghi nhận session và mode trước đó khi chuyển tiếp từ Viewer Preview sang Replay Preview, cho phép quay trở lại đúng Viewer Preview khi click thoát Replay Preview.
5. Snapshot state
Editor đang làm việc với các snapshot collection chính ngoài geometry:
snapshotEntityRowssnapshotWikissnapshotEntityWikiLinksreplays/effectiveReplays
Chúng đại diện cho "current session snapshot", không phải danh sách delta thô.
Ví dụ:
- entity ref được giữ bằng
operation: "reference" - entity/wiki local mới tạo có thể mang
operation: "create" - link entity-wiki mới tạo dùng
operation: "binding"
Khi commit, buildEditorSnapshot() sẽ so với baselineSnapshot để chuyển các collection này thành snapshot đúng semantic cho backend.
6. Baseline snapshot là gì
baselineSnapshot là snapshot đang được xem như gốc của session hiện tại.
Nó được cập nhật khi:
- mở project
- commit thành công
- restore từ một commit
baselineSnapshot được dùng để:
- biết link nào là
reference, link nào làbinding, link nào làdelete - biết wiki/entity nào là thay đổi thực sự so với snapshot trước
- giữ lại inline entity/wiki từ snapshot trước nếu user chưa xóa chúng
7. Derived state quan trọng trong page
mapRenderDraft
- là
FeatureCollectionduy nhất trong page quyết định geometry nào được truyền xuống map - nguồn có thể là
mainDraft,replayDraft, hoặc preview draft tùy mode - đã qua filter timeline nếu
timelineFilterEnabled = true - đã qua replay preview hidden ids nếu đang preview
- geometry mới tạo trong session cũng bị timeline filter xử lý như geometry baseline
labelContextBaseDraft và mapLabelContextDraft
- chỉ dùng để enrich/lookup label entity cho map
- có thể chứa geometry bị
mapRenderDraftlọc ra - không được dùng để quyết định geometry nào render trên map
geometryChoices
- nguồn dữ liệu cho
GeometryBindingPanel - thêm trạng thái derived như orphan entity, time completeness, timeline visibility, hidden/bound/new
- ID geometry không phải label chính của row, nhưng vẫn nằm trong tooltip/title
snapshotEntityRowsVisible
- loại bỏ các row
delete - dedupe theo
id
selectedFeatures
- map từ
selectedFeatureIdssang feature thật trongeditor.draft.features
isMultiEditValid
- chỉ
truekhi tất cả geometry đang chọn cùnggeometry.type - một số thao tác bind sẽ chặn nếu giá trị này là
false
pendingSaveCount
Được tính như sau:
editor.changeCount+1nếu wiki dirty+1nếu entities dirty+1nếu entity-wiki dirty+1nếu replay dirty
Đây là con số dùng trong UI commit, không phải số record backend chắc chắn sẽ thay đổi.
8. Dirty detection
Dirty check của:
snapshotWikissnapshotEntityRowssnapshotEntityWikiLinkseditor.effectiveReplays
đều đang làm bằng cách normalize trước rồi so JSON.stringify.
Điều này đủ thực dụng cho snapshot cỡ vừa, nhưng cần lưu ý:
- không tối ưu cho dữ liệu rất lớn
- phụ thuộc vào tính ổn định của thứ tự mảng sau normalize
9. State được persist vào localStorage
Hiện editor chỉ persist hai nhóm nhỏ:
- background layer visibility
- key:
uhm.backgroundLayerVisibility.v1
- key:
- map projection
- key:
uhm:mapProjection
- key:
Editor hiện không persist toàn bộ draft/project snapshot vào localStorage. Nếu cần autosave local draft, đó là tính năng phải làm thêm, không phải behavior hiện tại.
10. Khi nào state bị reset
Reset toàn phần
Xảy ra khi:
- mở project khác
- mở lại project
- restore commit
Hiệu ứng:
baselineFeatureCollectionđổiuseEditorState()resetdraftundoStackbị clear- baseline map được build lại
Reset cục bộ
- đổi selection có thể reset
geometryMetaForm - đóng/mở wiki modal không reset snapshot wiki, chỉ reset form local của modal
11. Một số giới hạn hiện tại cần nhớ khi đọc code
- có
undo, chưa córedo - timeline state có
timelineYear, nhưng page hiện dùngtimelineDraftYearcho filtering - dirty count của commit không tương ứng một-một với số mutation backend
- map selection, bound_with filter và timeline filter đều là state client-side
- trạng thái orphan/time/timeline trong
GeometryBindingPanellà derived từ draft + visibility, không phải field persist riêng