refactor: undo feature cover every single part of editor

This commit is contained in:
taDuc
2026-05-23 12:23:01 +07:00
parent 3b4ff71b9a
commit 282b365287
47 changed files with 2184 additions and 3311 deletions
+194
View File
@@ -0,0 +1,194 @@
# UHM Editor - replay actions catalog
Cập nhật: 2026-05-22.
Tài liệu này mô tả action catalog của replay editor/preview hiện tại. Shape chuẩn nằm ở `src/uhm/types/projects.ts`; dispatcher runtime nằm ở `src/uhm/lib/replay/replayDispatcher.ts`.
## 1. Replay shape
```ts
type BattleReplay = {
id: string;
geometry_id: string;
target_geometry_ids: string[];
detail: ReplayStage[];
};
type ReplayStage = {
id: number;
title?: string;
detail_time_start: string;
detail_time_stop: string;
steps: ReplayStep[];
};
type ReplayStep = {
duration: number;
use_UI_function: ReplayAction<UIOptionName>[];
use_map_function: ReplayAction<MapFunctionName>[];
use_geo_function: ReplayAction<GeoFunctionName>[];
use_narrow_function: ReplayAction<NarrativeFunctionName>[];
};
type ReplayAction<T> = {
function_name: T;
params: unknown[];
};
```
Ghi chú:
- `use_narrow_function` là tên field hiện tại cho nhóm narrative.
- `params` là tuple positional, không phải object schema.
- `target_geometry_ids` là source truth cho replay draft; không persist `replayDraft`.
- `detail_time_start/detail_time_stop` là string theo form replay hiện tại, không phải `time_start/time_end` số của geometry.
## 2. Runtime execution order
Preview flatten replay thành danh sách step theo thứ tự stage/step.
Trong mỗi step, dispatcher chạy các group action từ step hiện tại. Duration của step quyết định thời gian chờ trước step tiếp theo. Preview state có thể đổi:
- map camera/labels
- timeline visible/filter/year
- hidden geometry ids
- title/descriptions/subtitle/dialog/image/toast
- wiki sidebar/open wiki
- playback speed
Stop/reset preview khôi phục presentation state và một phần map/timeline baseline.
## 3. UI actions
| Action | Params | Runtime hiện tại |
| --- | --- | --- |
| `timeline` | `[visible: boolean]` | Ẩn/hiện TimelineBar trong preview |
| `layer_panel` | `[visible: boolean]` | No-op hiện tại |
| `wiki_panel` | `[visible: boolean]` | Mở/đóng wiki sidebar preview |
| `close_wiki_panel` | `[]` | Đóng wiki sidebar và clear active wiki |
| `zoom_panel` | `[visible: boolean]` | No-op hiện tại |
| `wiki` | `[wikiId: string]` | Mở wiki sidebar và active wiki id |
| `toast` | `[message: string]` | Hiện toast tạm thời |
| `wiki_header` | `[headerId: string]` | No-op hiện tại |
| `playback_speed` | `[speed: number]` | Đổi tốc độ phát preview |
Legacy shape vẫn được dispatcher đọc:
```ts
{ function_name: "UI", params: [optionName, ...payload] }
```
Shape mới nên dùng trực tiếp:
```ts
{ function_name: "timeline", params: [true] }
```
## 4. Map actions
| Action | Params | Runtime hiện tại |
| --- | --- | --- |
| `set_camera_view` | `[state]` | `map.easeTo` center/zoom/pitch/bearing/duration |
| `set_time_filter` | `[year: number]` | Set replay preview timeline year |
| `enable_timeline_filter` | `[]` | Bật timeline filter |
| `disable_timeline_filter` | `[]` | Tắt timeline filter |
| `toggle_labels` | `[visible: boolean]` | Legacy labels toggle |
| `show_labels` | `[]` | Hiện symbol text labels |
| `hide_labels` | `[]` | Ẩn symbol text labels |
| `show_all_geometries` | `[]` | Clear hidden geometry ids |
| `reset_camera_north` | `[]` | Set bearing về 0 |
`set_camera_view` chấp nhận center dạng `[lng, lat]` hoặc `{ lng, lat }`.
## 5. Geo actions
| Action | Params | Runtime hiện tại |
| --- | --- | --- |
| `fly_to_geometry` | `[geometryId]` | Legacy: fly tới một geometry |
| `fly_to_geometries` | `[geometryIds, duration?]` | Fit/fly tới nhiều geometry |
| `set_geometry_visibility` | `[geometryIds, visible]` | Legacy: show/hide theo boolean |
| `show_geometries` | `[geometryIds]` | Bỏ ids khỏi hidden set |
| `hide_geometries` | `[geometryIds]` | Thêm ids vào hidden set |
| `fit_to_geometries` | `[geometryIds, duration?]` | Legacy: dùng fly/fit tới geometry |
| `orbit_camera_around_geometry` | `[geometryId, zoom?, pitch?, turns?, duration?]` | Ease camera quanh bbox geometry |
| `pulse_geometry` | `[geometryId, color?, repeat?, duration?]` | No-op trong dispatcher hiện tại |
| `animate_dashed_border` | `[geometryId, color?, width?, speed?, duration?]` | No-op trong dispatcher hiện tại |
| `set_geometry_style` | `[geometryIds, fill?, opacity?, stroke?, width?]` | No-op trong dispatcher hiện tại |
| `show_geometry_label` | `[geometryId, text?, color?, size?]` | No-op trong dispatcher hiện tại |
| `follow_geometry_path` | `[geometryId, duration?]` | Legacy: fly theo một path bằng fit/fly |
| `follow_geometries_path` | `[geometryIds, duration?, zoom?, padding?]` | Hiện dùng fly/fit tới nhiều geometry |
| `dim_other_geometries` | `[geometryIds]` | Chỉ hiện target ids, ẩn các geometry khác |
Các action visual effect no-op vẫn có trong composer để giữ schema và chuẩn bị cho runtime effect sau này.
## 6. Narrative actions
| Action | Params | Runtime hiện tại |
| --- | --- | --- |
| `set_title` | `[title: string]` | Set title overlay |
| `clear_title` | `[]` | Clear title |
| `set_descriptions` | `[text: string]` | Set description overlay |
| `clear_descriptions` | `[]` | Clear descriptions |
| `show_dialog_box` | `[avatar, text, side, speaker?]` | Hiện dialog, side là `left` hoặc `right` |
| `clear_dialog_box` | `[]` | Clear dialog |
| `display_historical_image` | `[url, caption?]` | Hiện image overlay lịch sử |
| `clear_historical_image` | `[]` | Clear image |
| `set_step_subtitle` | `[subtitle: string | null]` | Set subtitle |
| `clear_step_subtitle` | `[]` | Clear subtitle |
## 7. Composer shortcuts hiện có
Map shortcuts:
- `show_labels`
- `hide_labels`
- `enable_timeline_filter`
- `disable_timeline_filter`
- `set_time_filter`
- `reset_camera_north`
- `show_all_geometries`
Geo shortcuts:
- `fly_to_geometries`
- `follow_geometries_path`
- `show_geometries`
- `hide_geometries`
- `pulse_geometry`
- `animate_dashed_border`
- `orbit_camera_around_geometry`
- `show_geometry_label`
- `dim_other_geometries`
- `set_geometry_style`
Narrative composer hiện hỗ trợ đầy đủ các narrative actions ở mục 6.
## 8. Normalization và migration
Khi load snapshot:
- Replay thiếu `geometry_id` có thể fallback từ `id`.
- `target_geometry_ids` được normalize/dedupe, MAIN geo đứng đầu.
- Snapshot cũ có `replay_features` được chuyển thành `target_geometry_ids`.
- UI legacy action `{ function_name: "UI", params: [...] }` được normalize sang option action.
- Unknown action/function bị bỏ qua trong normalize/dispatcher.
- Normalizer snapshot hiện giữ các action đang có trong type/UI, gồm `close_wiki_panel`, `show_all_geometries` và các narrative `clear_*`.
## 9. Undo và commit boundary
- Replay mode dùng `replayUndoStack`, tách khỏi main undo.
- Sửa stage/step/action đi qua `editor.mutateActiveReplay`.
- Mỗi mutation tạo `replay_session` undo action.
- Thoát hoặc chuyển replay flush session vào `replays[]`.
- Commit đọc `editor.effectiveReplays`, nên có thể commit khi vẫn đang ở replay mode.
- Replay mode hiện không cho create/update/delete geometry.
## 10. Checklist khi thêm replay action
1. Thêm function name vào `src/uhm/types/projects.ts`.
2. Thêm label/summary trong `ReplayTimelineSidebar`.
3. Thêm composer hoặc shortcut trong `ReplayEffectsSidebar`.
4. Thêm runtime trong `replayDispatcher.ts` và action module phù hợp.
5. Thêm normalize support trong `editorSnapshot.ts`.
6. Xác định action có cần reset khi stop preview không.
7. Cập nhật file này và `commit_snapshot.ts`.