refactor: extract editor dnd helpers
This commit is contained in:
@@ -167,8 +167,24 @@ function populateSessionConflictDiffPanel(panel, context, deps) {
|
||||
});
|
||||
}
|
||||
|
||||
function runSessionConflictAction(action, deps) {
|
||||
deps = deps || {};
|
||||
if (typeof action !== 'function') return;
|
||||
try {
|
||||
var result = action();
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.catch(function(error) {
|
||||
if (typeof deps.onError === 'function') deps.onError(error);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (typeof deps.onError === 'function') deps.onError(error);
|
||||
}
|
||||
}
|
||||
|
||||
window.__mnoteDocumentConflictPanelRuntime = {
|
||||
clearSessionConflictSurface: clearSessionConflictSurface,
|
||||
createSessionConflictPanel: createSessionConflictPanel,
|
||||
populateSessionConflictDiffPanel: populateSessionConflictDiffPanel
|
||||
populateSessionConflictDiffPanel: populateSessionConflictDiffPanel,
|
||||
runSessionConflictAction: runSessionConflictAction
|
||||
};
|
||||
|
||||
@@ -2122,28 +2122,40 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
await persistSession(session);
|
||||
};
|
||||
|
||||
const runSessionConflictAction = (action, onError) => {
|
||||
const runtime = window.__mnoteDocumentConflictPanelRuntime;
|
||||
if (runtime && typeof runtime.runSessionConflictAction === 'function') {
|
||||
runtime.runSessionConflictAction(action, { onError });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = action();
|
||||
if (result && typeof result.then === 'function') result.catch(onError);
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const renderSessionConflictSurface = (session, message) => {
|
||||
clearSessionConflictSurface(session);
|
||||
sessionViews(session).forEach((view) => {
|
||||
const host = view.runtimeDescriptor.root.closest('.document-pane') || view.runtimeDescriptor.root;
|
||||
if (!(host instanceof HTMLElement)) return;
|
||||
const runtime = window.__mnoteDocumentConflictPanelRuntime;
|
||||
const handleConflictActionError = (error) => {
|
||||
setSessionStatus(session, 'external-change-conflict', error instanceof Error ? error.message : String(error));
|
||||
renderSessionConflictSurface(session, error instanceof Error ? error.message : String(error));
|
||||
};
|
||||
let panel = null;
|
||||
if (runtime && typeof runtime.createSessionConflictPanel === 'function') {
|
||||
panel = runtime.createSessionConflictPanel(session, message, {
|
||||
externalConflictMessage,
|
||||
conflictSourceLabel,
|
||||
onAcceptDisk: () => {
|
||||
acceptDiskVersion(session).catch((error) => {
|
||||
setSessionStatus(session, 'external-change-conflict', error instanceof Error ? error.message : String(error));
|
||||
renderSessionConflictSurface(session, error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
runSessionConflictAction(() => acceptDiskVersion(session), handleConflictActionError);
|
||||
},
|
||||
onKeepCurrent: () => {
|
||||
keepCurrentEditorVersion(session).catch((error) => {
|
||||
setSessionStatus(session, 'external-change-conflict', error instanceof Error ? error.message : String(error));
|
||||
renderSessionConflictSurface(session, error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
runSessionConflictAction(() => keepCurrentEditorVersion(session), handleConflictActionError);
|
||||
},
|
||||
onOpenDiff: (_session, createdPanel) => {
|
||||
openConflictDiffPanel(session, createdPanel);
|
||||
@@ -2189,16 +2201,10 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
panel.append(heading, text, meta, actions, diffPanel);
|
||||
|
||||
acceptDisk.addEventListener('click', () => {
|
||||
acceptDiskVersion(session).catch((error) => {
|
||||
setSessionStatus(session, 'external-change-conflict', error instanceof Error ? error.message : String(error));
|
||||
renderSessionConflictSurface(session, error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
runSessionConflictAction(() => acceptDiskVersion(session), handleConflictActionError);
|
||||
});
|
||||
keepCurrent.addEventListener('click', () => {
|
||||
keepCurrentEditorVersion(session).catch((error) => {
|
||||
setSessionStatus(session, 'external-change-conflict', error instanceof Error ? error.message : String(error));
|
||||
renderSessionConflictSurface(session, error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
runSessionConflictAction(() => keepCurrentEditorVersion(session), handleConflictActionError);
|
||||
});
|
||||
openDiff.addEventListener('click', () => {
|
||||
openConflictDiffPanel(session, panel);
|
||||
@@ -5659,6 +5665,9 @@ mod tests {
|
||||
assert!(DOCUMENT_CONFLICT_PANEL_RUNTIME_JS.contains("onUseCurrent"));
|
||||
assert!(DOCUMENT_CONFLICT_PANEL_RUNTIME_JS.contains("onUseDisk"));
|
||||
assert!(DOCUMENT_CONFLICT_PANEL_RUNTIME_JS.contains("onSaveMerge"));
|
||||
assert!(DOCUMENT_CONFLICT_PANEL_RUNTIME_JS.contains("function runSessionConflictAction"));
|
||||
assert!(DOCUMENT_CONFLICT_PANEL_RUNTIME_JS
|
||||
.contains("runSessionConflictAction: runSessionConflictAction"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,92 @@
|
||||
//! Block handle 拖拽命中的纯几何 helper。
|
||||
//!
|
||||
//! 本模块不查询 DOM、不处理 Leptos signal,也不分发 editor command;
|
||||
//! 调用方负责传入已经从页面中读取的坐标。
|
||||
|
||||
use crate::editor_runtime::block_hover_state::DropPlacement;
|
||||
|
||||
pub(crate) struct HandleCorridorGeometry {
|
||||
pub(crate) stage_left: f64,
|
||||
pub(crate) stage_top: f64,
|
||||
pub(crate) block_left: f64,
|
||||
pub(crate) block_top: f64,
|
||||
pub(crate) block_bottom: f64,
|
||||
pub(crate) handle_left: f64,
|
||||
pub(crate) handle_right: f64,
|
||||
}
|
||||
|
||||
pub(crate) fn pointer_in_handle_corridor_geometry(
|
||||
client_x: i32,
|
||||
client_y: i32,
|
||||
geometry: &HandleCorridorGeometry,
|
||||
) -> bool {
|
||||
let x = f64::from(client_x) - geometry.stage_left;
|
||||
let y = f64::from(client_y) - geometry.stage_top;
|
||||
let corridor_left = (geometry.handle_left - 12.0).min(geometry.block_left);
|
||||
let corridor_right = (geometry.block_left + 18.0).max(geometry.handle_right + 12.0);
|
||||
|
||||
x >= corridor_left
|
||||
&& x <= corridor_right
|
||||
&& y >= geometry.block_top - 18.0
|
||||
&& y <= geometry.block_bottom + 18.0
|
||||
}
|
||||
|
||||
pub(crate) fn drop_placement_from_block_point(
|
||||
client_y: i32,
|
||||
block_top: f64,
|
||||
block_height: f64,
|
||||
) -> DropPlacement {
|
||||
let midpoint = block_top + (block_height / 2.0);
|
||||
if f64::from(client_y) <= midpoint {
|
||||
DropPlacement::Before
|
||||
} else {
|
||||
DropPlacement::After
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn drop_indicator_top(
|
||||
stage_top: f64,
|
||||
block_top: f64,
|
||||
block_bottom: f64,
|
||||
placement: DropPlacement,
|
||||
) -> f64 {
|
||||
match placement {
|
||||
DropPlacement::Before => block_top - stage_top,
|
||||
DropPlacement::After => block_bottom - stage_top,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pointer_corridor_accepts_handle_lane_and_block_edge() {
|
||||
let geometry = HandleCorridorGeometry {
|
||||
stage_left: 100.0,
|
||||
stage_top: 20.0,
|
||||
block_left: 140.0,
|
||||
block_top: 40.0,
|
||||
block_bottom: 80.0,
|
||||
handle_left: 108.0,
|
||||
handle_right: 130.0,
|
||||
};
|
||||
|
||||
assert!(pointer_in_handle_corridor_geometry(118, 80, &geometry));
|
||||
assert!(pointer_in_handle_corridor_geometry(250, 80, &geometry));
|
||||
assert!(!pointer_in_handle_corridor_geometry(90, 80, &geometry));
|
||||
assert!(!pointer_in_handle_corridor_geometry(118, 28, &geometry));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_placement_splits_block_at_midpoint() {
|
||||
assert_eq!(
|
||||
drop_placement_from_block_point(50, 40.0, 20.0),
|
||||
DropPlacement::Before
|
||||
);
|
||||
assert_eq!(
|
||||
drop_placement_from_block_point(51, 40.0, 20.0),
|
||||
DropPlacement::After
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub(crate) mod attachment_links;
|
||||
pub(crate) mod attachment_upload;
|
||||
pub(crate) mod block_dnd;
|
||||
pub(crate) mod block_hover_state;
|
||||
pub(crate) mod block_menu_document;
|
||||
pub(crate) mod block_menu_overlay;
|
||||
|
||||
@@ -21,12 +21,16 @@ use web_sys::{
|
||||
};
|
||||
|
||||
use editor_runtime::attachment_upload::dispatch_editor_upload_request;
|
||||
use editor_runtime::block_dnd::{
|
||||
drop_indicator_top, drop_placement_from_block_point, pointer_in_handle_corridor_geometry,
|
||||
HandleCorridorGeometry,
|
||||
};
|
||||
use editor_runtime::block_menu_overlay;
|
||||
use editor_runtime::block_menu_legacy_html::{
|
||||
duplicate_top_level_block_html, reorder_top_level_block_html,
|
||||
};
|
||||
use editor_runtime::block_hover_state::{
|
||||
BlockMenuLayout, DropIndicatorState, DropPlacement, HoveredBlockState, PendingDragState,
|
||||
BlockMenuLayout, DropIndicatorState, HoveredBlockState, PendingDragState,
|
||||
};
|
||||
use editor_runtime::command_sync::{
|
||||
read_editor_snapshot, sync_editor_outputs, sync_persisted_editor_command,
|
||||
@@ -5416,8 +5420,6 @@ fn pointer_in_handle_corridor(client_x: i32, client_y: i32, block: &HoveredBlock
|
||||
let block_left = block_rect.left() - stage_rect.left();
|
||||
let block_top = block_rect.top() - stage_rect.top();
|
||||
let block_bottom = block_rect.bottom() - stage_rect.top();
|
||||
let x = f64::from(client_x) - stage_rect.left();
|
||||
let y = f64::from(client_y) - stage_rect.top();
|
||||
let handle_left = handle_rect
|
||||
.as_ref()
|
||||
.map(|rect| rect.left() - stage_rect.left())
|
||||
@@ -5426,10 +5428,20 @@ fn pointer_in_handle_corridor(client_x: i32, client_y: i32, block: &HoveredBlock
|
||||
.as_ref()
|
||||
.map(|rect| rect.right() - stage_rect.left())
|
||||
.unwrap_or_else(|| handle_left + HANDLE_TRIGGER_WIDTH);
|
||||
let corridor_left = (handle_left - 12.0).min(block_left);
|
||||
let corridor_right = (block_left + 18.0).max(handle_right + 12.0);
|
||||
|
||||
x >= corridor_left && x <= corridor_right && y >= block_top - 18.0 && y <= block_bottom + 18.0
|
||||
pointer_in_handle_corridor_geometry(
|
||||
client_x,
|
||||
client_y,
|
||||
&HandleCorridorGeometry {
|
||||
stage_left: stage_rect.left(),
|
||||
stage_top: stage_rect.top(),
|
||||
block_left,
|
||||
block_top,
|
||||
block_bottom,
|
||||
handle_left,
|
||||
handle_right,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn drop_indicator_from_target(
|
||||
@@ -5442,16 +5454,8 @@ fn drop_indicator_from_target(
|
||||
let stage = editor_stage_element()?;
|
||||
let block_rect = block.get_bounding_client_rect();
|
||||
let stage_rect = stage.get_bounding_client_rect();
|
||||
let midpoint = block_rect.top() + (block_rect.height() / 2.0);
|
||||
let placement = if f64::from(client_y) <= midpoint {
|
||||
DropPlacement::Before
|
||||
} else {
|
||||
DropPlacement::After
|
||||
};
|
||||
let top = match placement {
|
||||
DropPlacement::Before => block_rect.top() - stage_rect.top(),
|
||||
DropPlacement::After => block_rect.bottom() - stage_rect.top(),
|
||||
};
|
||||
let placement = drop_placement_from_block_point(client_y, block_rect.top(), block_rect.height());
|
||||
let top = drop_indicator_top(stage_rect.top(), block_rect.top(), block_rect.bottom(), placement);
|
||||
|
||||
Some(DropIndicatorState {
|
||||
index: hovered.index,
|
||||
|
||||
Reference in New Issue
Block a user