diff --git a/src/app/editor/[id]/page.tsx b/src/app/editor/[id]/page.tsx index 60d779d..a9aed89 100644 --- a/src/app/editor/[id]/page.tsx +++ b/src/app/editor/[id]/page.tsx @@ -11,6 +11,7 @@ import SelectedGeometryPanel from "@/uhm/components/editor/SelectedGeometryPanel import ReplayTimelineSidebar from "@/uhm/components/editor/ReplayTimelineSidebar"; import ReplayEffectsSidebar from "@/uhm/components/editor/ReplayEffectsSidebar"; import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay"; +import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel"; import PublicWikiSidebar from "@/uhm/components/wiki/PublicWikiSidebar"; import WikiSidebarPanel from "@/uhm/components/wiki/WikiSidebarPanel"; import ProjectEntityRefsPanel from "@/uhm/components/editor/ProjectEntityRefsPanel"; @@ -46,7 +47,10 @@ import { buildFeatureEntityPatch } from "@/uhm/lib/editor/entity/entityBinding"; import { newId } from "@/uhm/lib/utils/id"; import { loadBackgroundLayerVisibilityFromStorage, + persistBackgroundLayerVisibility, } from "@/uhm/lib/editor/background/backgroundVisibilityStorage"; +import { BACKGROUND_LAYER_OPTIONS } from "@/uhm/lib/map/styles/backgroundLayers"; +import { GEO_TYPE_KEYS } from "@/uhm/lib/map/geo/geoTypeMap"; import { deepClone } from "@/uhm/lib/editor/draft/draftDiff"; import { useProjectCommands } from "@/uhm/lib/editor/project/useProjectCommands"; import { useReplayPreview } from "@/uhm/lib/replay/useReplayPreview"; @@ -2231,6 +2235,7 @@ function EditorPageContent() { imageOverlay={imageOverlay} onImageOverlayChange={setImageOverlay} onBindGeometries={handleBindGeometries} + showViewportControls={!isReplayPreviewMode || replayPreview.zoomPanelVisible} /> ) : (
@@ -2239,11 +2244,7 @@ function EditorPageContent() { ) : null} + {isReplayPreviewMode ? ( + + ) : null} {!isReplayPreviewMode || replayPreview.timelineVisible ? ( { }); } +// ReplayPreviewLayerPanel is imported from "@/uhm/components/editor/ReplayPreviewLayerPanel" + function isTypingTarget(target: EventTarget | null): boolean { if (!(target instanceof HTMLElement)) return false; const tagName = target.tagName.toLowerCase(); diff --git a/src/uhm/components/Map.tsx b/src/uhm/components/Map.tsx index b0fc372..6ecb404 100644 --- a/src/uhm/components/Map.tsx +++ b/src/uhm/components/Map.tsx @@ -61,6 +61,7 @@ type MapProps = { imageOverlay?: MapImageOverlay | null; onImageOverlayChange?: (overlay: MapImageOverlay) => void; onBindGeometries?: (targetId: string | number, sourceIds: (string | number)[]) => void; + showViewportControls?: boolean; }; const Map = forwardRef(function Map({ @@ -90,6 +91,7 @@ const Map = forwardRef(function Map({ imageOverlay = null, onImageOverlayChange, onBindGeometries, + showViewportControls = true, }, ref) { // Ref giữ mode mới nhất cho MapLibre handlers được register một lần. const modeRef = useRef(mode); @@ -273,16 +275,17 @@ const Map = forwardRef(function Map({
) : null} -
+ {showViewportControls ? ( +
(function Map({ {zoomLevel.toFixed(1)}x
-
+ + ) : null} ); }); diff --git a/src/uhm/components/editor/ReplayEffectsSidebar.tsx b/src/uhm/components/editor/ReplayEffectsSidebar.tsx index 119b342..89b2695 100644 --- a/src/uhm/components/editor/ReplayEffectsSidebar.tsx +++ b/src/uhm/components/editor/ReplayEffectsSidebar.tsx @@ -62,12 +62,12 @@ type ActionDefinition = { }; type NarrativeActionDefinitionMap = Record>; +type UiVisibleOptionName = "timeline" | "layer_panel" | "zoom_panel"; type UiEffectsDraftState = { selected: Record; + visible: Record; wiki_id: string; message: string; - header_id: string; - speed: string; }; type MapCameraOptionName = "center" | "zoom" | "bearing" | "pitch"; type MapCameraDraftState = { @@ -84,28 +84,20 @@ type CurrentMapViewState = { const uiOptionChoices: Array<{ label: string; value: UIOptionName }> = [ { label: "Timeline", value: "timeline" }, { label: "Layer Panel", value: "layer_panel" }, - { label: "Wiki Panel", value: "wiki_panel" }, - { label: "Close Wiki Panel", value: "close_wiki_panel" }, { label: "Zoom Panel", value: "zoom_panel" }, { label: "Wiki", value: "wiki" }, { label: "Toast", value: "toast" }, - { label: "Wiki Header", value: "wiki_header" }, - { label: "Playback Speed", value: "playback_speed" }, ]; const uiSimpleOptionValues: UIOptionName[] = [ "timeline", "layer_panel", - "wiki_panel", - "close_wiki_panel", "zoom_panel", ]; const uiInputOptionValues: UIOptionName[] = [ "wiki", "toast", - "wiki_header", - "playback_speed", ]; const mapCameraOptionChoices: Array<{ label: string; value: MapCameraOptionName }> = [ @@ -148,107 +140,51 @@ const buttonStyle = { }; const narrativeActionDefinitions: NarrativeActionDefinitionMap = { - set_title: { - label: "Tiêu đề step", - fields: [{ name: "title", label: "Title", kind: "text", placeholder: "Tiêu đề" }], - create: () => ({ function_name: "set_title", params: [""] }), - deserialize: (params) => ({ title: asString(params[0]) }), - serialize: (values) => [asString(values.title)], - }, - clear_title: { - label: "Xóa tiêu đề", - fields: [], - create: () => ({ function_name: "clear_title", params: [] }), - deserialize: () => ({}), - serialize: () => [], - }, - set_descriptions: { - label: "Mô tả", - fields: [{ name: "text", label: "Text", kind: "textarea", placeholder: "Nội dung diễn giải" }], - create: () => ({ function_name: "set_descriptions", params: [""] }), - deserialize: (params) => ({ text: asString(params[0]) }), - serialize: (values) => [asString(values.text)], - }, - clear_descriptions: { - label: "Xóa mô tả", - fields: [], - create: () => ({ function_name: "clear_descriptions", params: [] }), - deserialize: () => ({}), - serialize: () => [], - }, - show_dialog_box: { + set_dialog: { label: "Dialog box", fields: [ - { name: "avatar", label: "Avatar", kind: "text", placeholder: "avatar url" }, - { name: "text", label: "Text", kind: "textarea", placeholder: "Lời thoại" }, - { - name: "side", - label: "Side", - kind: "select", - options: [ - { label: "Left", value: "left" }, - { label: "Right", value: "right" }, - ], - }, - { name: "speaker", label: "Speaker", kind: "text", placeholder: "Tên nhân vật" }, + { name: "clear", label: "Ẩn dialog (Clear)", kind: "boolean" }, + { name: "avatar", label: "Avatar URL", kind: "text", placeholder: "https://... (avatar)" }, + { name: "text", label: "Nội dung", kind: "textarea", placeholder: "Lời thoại / Dẫn chuyện" }, + { name: "image_url", label: "Ảnh tư liệu", kind: "text", placeholder: "https://... (ảnh đè)" }, + { name: "image_caption", label: "Chú thích ảnh", kind: "text", placeholder: "Chú thích ảnh" }, ], - create: () => ({ function_name: "show_dialog_box", params: ["", "", "left", ""] }), - deserialize: (params) => ({ - avatar: asString(params[0]), - text: asString(params[1]), - side: normalizeSelectValue(asString(params[2]), "left"), - speaker: asString(params[3]), - }), - serialize: (values) => [ - asString(values.avatar), - asString(values.text), - normalizeSelectValue(asString(values.side), "left"), - asString(values.speaker), - ], - }, - clear_dialog_box: { - label: "Đóng dialog box", - fields: [], - create: () => ({ function_name: "clear_dialog_box", params: [] }), - deserialize: () => ({}), - serialize: () => [], - }, - display_historical_image: { - label: "Ảnh lịch sử", - fields: [ - { name: "url", label: "URL", kind: "text", placeholder: "https://..." }, - { name: "caption", label: "Caption", kind: "textarea", placeholder: "Chú thích" }, - ], - create: () => ({ function_name: "display_historical_image", params: ["", ""] }), - deserialize: (params) => ({ - url: asString(params[0]), - caption: asString(params[1]), - }), - serialize: (values) => compactTrailingUndefined([ - asString(values.url), - emptyToUndefined(asString(values.caption)), - ]), - }, - clear_historical_image: { - label: "Xóa ảnh lịch sử", - fields: [], - create: () => ({ function_name: "clear_historical_image", params: [] }), - deserialize: () => ({}), - serialize: () => [], - }, - set_step_subtitle: { - label: "Phụ đề", - fields: [{ name: "subtitle", label: "Subtitle", kind: "textarea", placeholder: "Để trống để ẩn subtitle" }], - create: () => ({ function_name: "set_step_subtitle", params: [""] }), - deserialize: (params) => ({ subtitle: params[0] == null ? "" : asString(params[0]) }), - serialize: (values) => [emptyToNull(asString(values.subtitle))], - }, - clear_step_subtitle: { - label: "Xóa phụ đề", - fields: [], - create: () => ({ function_name: "clear_step_subtitle", params: [] }), - deserialize: () => ({}), - serialize: () => [], + create: () => ({ function_name: "set_dialog", params: [{ avatar: "", text: "", image_url: "", image_caption: "" }] }), + deserialize: (params) => { + const data: any = params[0]; + if (data === null) { + return { + clear: true, + avatar: "", + text: "", + image_url: "", + image_caption: "", + }; + } + return { + clear: false, + avatar: asString(data?.avatar), + text: asString(data?.text), + image_url: asString(data?.image_url), + image_caption: asString(data?.image_caption), + }; + }, + serialize: (values) => { + if (values.clear) { + return [null]; + } + const data: any = { + avatar: asString(values.avatar), + text: asString(values.text), + }; + if (values.image_url) { + data.image_url = asString(values.image_url); + } + if (values.image_caption) { + data.image_caption = asString(values.image_caption); + } + return [data]; + }, }, }; @@ -306,11 +242,6 @@ export default function ReplayEffectsSidebar({ }) .map((id) => byId.get(id) || { id, label: id }); }, [geometryChoices, selectedFeatureIds]); - const selectedGeometryIds = useMemo( - () => selectedGeometryItems.map((item) => item.id), - [selectedGeometryItems] - ); - const updateStep = (label: string, updater: (step: ReplayStep) => void) => { if (!selectedStage || selectedStepIndex == null) return; onMutateReplay(label, (draftReplay) => { @@ -362,7 +293,6 @@ export default function ReplayEffectsSidebar({ <> onAppendActions( - [{ function_name: "show_labels", params: [] }], + [{ function_name: "set_labels_visible", params: [true] }], "Map: show labels" ) } @@ -443,7 +373,7 @@ function MapFunctionShortcutPanel({ tone="slate" onClick={() => onAppendActions( - [{ function_name: "hide_labels", params: [] }], + [{ function_name: "set_labels_visible", params: [false] }], "Map: hide labels" ) } @@ -453,7 +383,7 @@ function MapFunctionShortcutPanel({ tone="green" onClick={() => onAppendActions( - [{ function_name: "enable_timeline_filter", params: [] }], + [{ function_name: "set_timeline_filter", params: [true] }], "Map: enable timeline filter" ) } @@ -463,41 +393,11 @@ function MapFunctionShortcutPanel({ tone="slate" onClick={() => onAppendActions( - [{ function_name: "disable_timeline_filter", params: [] }], + [{ function_name: "set_timeline_filter", params: [false] }], "Map: disable timeline filter" ) } /> - - onAppendActions( - [{ function_name: "set_time_filter", params: [safeYear] }], - `Map: set timeline ${safeYear}` - ) - } - /> - - onAppendActions( - [{ function_name: "reset_camera_north", params: [] }], - "Map: reset camera north" - ) - } - /> - - onAppendActions( - [{ function_name: "show_all_geometries", params: [] }], - "Map: show all geometries" - ) - } - /> @@ -553,7 +453,7 @@ function GeoFunctionShortcutPanel({ disabled={!hasSelection} onClick={() => onAppendActions( - [{ function_name: "show_geometries", params: [selectedIds] }], + [{ function_name: "set_geometry_visibility", params: [selectedIds, true] }], `Geo: show ${selectedCount} geo` ) } @@ -564,89 +464,22 @@ function GeoFunctionShortcutPanel({ disabled={!hasSelection} onClick={() => onAppendActions( - [{ function_name: "hide_geometries", params: [selectedIds] }], + [{ function_name: "set_geometry_visibility", params: [selectedIds, false] }], `Geo: hide ${selectedCount} geo` ) } /> - - onAppendActions( - selectedIds.map((id) => ({ - function_name: "pulse_geometry", - params: [id, "#f59e0b", 2, 1800], - })), - `Geo: pulse ${selectedCount} geo` - ) - } - /> - - onAppendActions( - selectedIds.map((id) => ({ - function_name: "animate_dashed_border", - params: [id, "#38bdf8", 2, 1, 3000], - })), - `Geo: dashed border ${selectedCount} geo` - ) - } - /> - - onAppendActions( - [{ function_name: "orbit_camera_around_geometry", params: [firstId, 8, 45, 1, 5000] }], - `Geo: orbit ${firstId || "main"}` - ) - } - /> - - onAppendActions( - selectedIds.map((id) => ({ - function_name: "show_geometry_label", - params: [id, "", "#ffffff", 14], - })), - `Geo: label ${selectedCount} geo` - ) - } - /> onAppendActions( - [{ function_name: "dim_other_geometries", params: [selectedIds] }], + [{ function_name: "hide_others_geometries", params: [selectedIds] }], `Geo: hide others ngoài ${selectedCount} geo` ) } /> - - onAppendActions( - [{ - function_name: "set_geometry_style", - params: [selectedIds, "#f97316", 0.35, "#fdba74", 2], - }], - `Geo: style ${selectedCount} geo` - ) - } - /> @@ -849,36 +682,6 @@ function UiInputEffectsPanel({ /> ) : null} - {draft.selected.wiki_header ? ( - onChangeDraft({ header_id: asString(nextValue) })} - /> - ) : null} - - {draft.selected.playback_speed ? ( - onChangeDraft({ speed: asString(nextValue) })} - /> - ) : null} -