mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-05 12:14:43 +00:00
feat: UndoManager's onPush now can access the change event (#588)
Include `origin` and `commitMessage` in the UndoManager's onPush and onPop #584
This commit is contained in:
parent
42949c0e24
commit
479c2268f3
7 changed files with 106 additions and 55 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1213,6 +1213,7 @@ name = "loro-ffi"
|
|||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"loro 1.1.0",
|
||||
"loro-internal 1.1.0",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
|
|
|
@ -9,4 +9,5 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
loro = { path = "../loro", features = ["counter", "jsonpath"] }
|
||||
loro-internal = { path = "../loro-internal", features = ["counter", "jsonpath"] }
|
||||
serde_json = { workspace = true }
|
||||
|
|
|
@ -56,11 +56,16 @@ impl UndoManager {
|
|||
/// Set the listener for push events.
|
||||
/// The listener will be called when a new undo/redo item is pushed into the stack.
|
||||
pub fn set_on_push(&self, on_push: Option<Arc<dyn OnPush>>) {
|
||||
let on_push = on_push.map(|x| {
|
||||
Box::new(move |u, c| loro::undo::UndoItemMeta::from(x.on_push(u, c)))
|
||||
as loro::undo::OnPush
|
||||
});
|
||||
self.0.write().unwrap().set_on_push(on_push)
|
||||
if let Some(on_push) = on_push {
|
||||
self.0
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_on_push(Some(Box::new(move |u, c, e| {
|
||||
loro::undo::UndoItemMeta::from(on_push.on_push(u, c, e))
|
||||
})));
|
||||
} else {
|
||||
self.0.write().unwrap().set_on_push(None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the listener for pop events.
|
||||
|
@ -78,7 +83,8 @@ pub trait OnPush: Send + Sync {
|
|||
fn on_push(
|
||||
&self,
|
||||
undo_or_redo: loro::undo::UndoOrRedo,
|
||||
couter_span: loro::CounterSpan,
|
||||
counter_span: loro::CounterSpan,
|
||||
diff_event: Option<loro_internal::event::DiffEvent>,
|
||||
) -> UndoItemMeta;
|
||||
}
|
||||
|
||||
|
@ -86,7 +92,7 @@ pub trait OnPop: Send + Sync {
|
|||
fn on_pop(
|
||||
&self,
|
||||
undo_or_redo: loro::undo::UndoOrRedo,
|
||||
couter_span: loro::CounterSpan,
|
||||
counter_span: loro::CounterSpan,
|
||||
undo_meta: UndoItemMeta,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
delta::TreeExternalDiff,
|
||||
event::{Diff, EventTriggerKind},
|
||||
version::Frontiers,
|
||||
ContainerDiff, DocDiff, LoroDoc, Subscription,
|
||||
ContainerDiff, DiffEvent, DocDiff, LoroDoc, Subscription,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -155,7 +155,9 @@ impl UndoOrRedo {
|
|||
|
||||
/// When a undo/redo item is pushed, the undo manager will call the on_push callback to get the meta data of the undo item.
|
||||
/// The returned cursors will be recorded for a new pushed undo item.
|
||||
pub type OnPush = Box<dyn Fn(UndoOrRedo, CounterSpan) -> UndoItemMeta + Send + Sync>;
|
||||
pub type OnPush = Box<
|
||||
dyn for<'a> Fn(UndoOrRedo, CounterSpan, Option<DiffEvent<'a>>) -> UndoItemMeta + Send + Sync,
|
||||
>;
|
||||
pub type OnPop = Box<dyn Fn(UndoOrRedo, CounterSpan, UndoItemMeta) + Send + Sync>;
|
||||
|
||||
struct UndoManagerInner {
|
||||
|
@ -386,7 +388,7 @@ impl UndoManagerInner {
|
|||
}
|
||||
}
|
||||
|
||||
fn record_checkpoint(&mut self, latest_counter: Counter) {
|
||||
fn record_checkpoint(&mut self, latest_counter: Counter, event: Option<DiffEvent>) {
|
||||
if Some(latest_counter) == self.next_counter {
|
||||
return;
|
||||
}
|
||||
|
@ -402,7 +404,7 @@ impl UndoManagerInner {
|
|||
let meta = self
|
||||
.on_push
|
||||
.as_ref()
|
||||
.map(|x| x(UndoOrRedo::Undo, span))
|
||||
.map(|x| x(UndoOrRedo::Undo, span, event))
|
||||
.unwrap_or_default();
|
||||
|
||||
if !self.undo_stack.is_empty() && now - self.last_undo_time < self.merge_interval {
|
||||
|
@ -471,7 +473,7 @@ impl UndoManager {
|
|||
inner.redo_stack.compose_remote_event(event.events);
|
||||
inner.next_counter = Some(id.counter + 1);
|
||||
} else {
|
||||
inner.record_checkpoint(id.counter + 1);
|
||||
inner.record_checkpoint(id.counter + 1, Some(event));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -553,7 +555,10 @@ impl UndoManager {
|
|||
|
||||
doc.commit_then_renew();
|
||||
let counter = get_counter_end(doc, self.peer());
|
||||
self.inner.try_lock().unwrap().record_checkpoint(counter);
|
||||
self.inner
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.record_checkpoint(counter, None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -695,7 +700,13 @@ impl UndoManager {
|
|||
let mut meta = inner
|
||||
.on_push
|
||||
.as_ref()
|
||||
.map(|x| x(kind.opposite(), CounterSpan::new(end_counter, new_counter)))
|
||||
.map(|x| {
|
||||
x(
|
||||
kind.opposite(),
|
||||
CounterSpan::new(end_counter, new_counter),
|
||||
None,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if matches!(kind, UndoOrRedo::Undo) && get_opposite(&mut inner).is_empty() {
|
||||
// If it's the first undo, we use the cursors from the users
|
||||
|
|
|
@ -4462,53 +4462,65 @@ impl UndoManager {
|
|||
/// Every time an undo step or redo step is pushed, the on push event listener will be called.
|
||||
#[wasm_bindgen(skip_typescript)]
|
||||
pub fn setOnPush(&mut self, on_push: JsValue) {
|
||||
let doc = Arc::downgrade(&self.doc);
|
||||
let on_push = on_push.dyn_into::<js_sys::Function>().ok();
|
||||
if let Some(on_push) = on_push {
|
||||
let on_push = observer::Observer::new(on_push);
|
||||
self.undo.set_on_push(Some(Box::new(move |kind, span| {
|
||||
let is_undo = JsValue::from_bool(matches!(kind, UndoOrRedo::Undo));
|
||||
let counter_range = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&counter_range,
|
||||
&JsValue::from_str("start"),
|
||||
&JsValue::from_f64(span.start as f64),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&counter_range,
|
||||
&JsValue::from_str("end"),
|
||||
&JsValue::from_f64(span.end as f64),
|
||||
)
|
||||
.unwrap();
|
||||
self.undo
|
||||
.set_on_push(Some(Box::new(move |kind, span, event| {
|
||||
let is_undo = JsValue::from_bool(matches!(kind, UndoOrRedo::Undo));
|
||||
let counter_range = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&counter_range,
|
||||
&JsValue::from_str("start"),
|
||||
&JsValue::from_f64(span.start as f64),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&counter_range,
|
||||
&JsValue::from_str("end"),
|
||||
&JsValue::from_f64(span.end as f64),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut undo_item_meta = UndoItemMeta::new();
|
||||
match on_push.call2(&is_undo, &counter_range) {
|
||||
Ok(v) => {
|
||||
if let Ok(obj) = v.dyn_into::<js_sys::Object>() {
|
||||
if let Ok(value) =
|
||||
js_sys::Reflect::get(&obj, &JsValue::from_str("value"))
|
||||
{
|
||||
let value: LoroValue = value.into();
|
||||
undo_item_meta.value = value;
|
||||
}
|
||||
if let Ok(cursors) =
|
||||
js_sys::Reflect::get(&obj, &JsValue::from_str("cursors"))
|
||||
{
|
||||
let cursors: js_sys::Array = cursors.into();
|
||||
for cursor in cursors.iter() {
|
||||
let cursor = js_to_cursor(cursor).unwrap_throw();
|
||||
undo_item_meta.add_cursor(&cursor.pos);
|
||||
let mut undo_item_meta = UndoItemMeta::new();
|
||||
let r = if let Some(e) = event {
|
||||
if let Some(doc_ref) = doc.upgrade() {
|
||||
let diff = diff_event_to_js_value(e, &doc_ref);
|
||||
on_push.call3(&is_undo, &counter_range, &diff)
|
||||
} else {
|
||||
on_push.call2(&is_undo, &counter_range)
|
||||
}
|
||||
} else {
|
||||
on_push.call2(&is_undo, &counter_range)
|
||||
};
|
||||
match r {
|
||||
Ok(v) => {
|
||||
if let Ok(obj) = v.dyn_into::<js_sys::Object>() {
|
||||
if let Ok(value) =
|
||||
js_sys::Reflect::get(&obj, &JsValue::from_str("value"))
|
||||
{
|
||||
let value: LoroValue = value.into();
|
||||
undo_item_meta.value = value;
|
||||
}
|
||||
if let Ok(cursors) =
|
||||
js_sys::Reflect::get(&obj, &JsValue::from_str("cursors"))
|
||||
{
|
||||
let cursors: js_sys::Array = cursors.into();
|
||||
for cursor in cursors.iter() {
|
||||
let cursor = js_to_cursor(cursor).unwrap_throw();
|
||||
undo_item_meta.add_cursor(&cursor.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
throw_error_after_micro_task(e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
throw_error_after_micro_task(e);
|
||||
}
|
||||
}
|
||||
|
||||
undo_item_meta
|
||||
})));
|
||||
undo_item_meta
|
||||
})));
|
||||
} else {
|
||||
self.undo.set_on_push(None);
|
||||
}
|
||||
|
@ -5067,7 +5079,7 @@ export type UndoConfig = {
|
|||
mergeInterval?: number,
|
||||
maxUndoSteps?: number,
|
||||
excludeOriginPrefixes?: string[],
|
||||
onPush?: (isUndo: boolean, counterRange: { start: number, end: number }) => { value: Value, cursors: Cursor[] },
|
||||
onPush?: (isUndo: boolean, counterRange: { start: number, end: number }, event?: LoroEventBatch) => { value: Value, cursors: Cursor[] },
|
||||
onPop?: (isUndo: boolean, value: { value: Value, cursors: Cursor[] }, counterRange: { start: number, end: number }) => void
|
||||
};
|
||||
export type Container = LoroList | LoroMap | LoroText | LoroTree | LoroMovableList;
|
||||
|
|
|
@ -229,4 +229,24 @@ describe("undo", () => {
|
|||
expect(doc.getCursorPos(poppedCursors[0]).offset).toBe(0);
|
||||
expect(doc.getCursorPos(poppedCursors[1]).offset).toBe(5);
|
||||
});
|
||||
|
||||
test("it can retrieve event in onPush event", async () => {
|
||||
const doc = new LoroDoc();
|
||||
let ran = false;
|
||||
const undo = new UndoManager(doc, {
|
||||
mergeInterval: 0,
|
||||
onPush: (isUndo, counterRange, event) => {
|
||||
expect(event).toBeDefined();
|
||||
expect(event?.by).toBe("local");
|
||||
expect(event?.origin).toBe("test");
|
||||
ran = true;
|
||||
return { value: null, cursors: [] };
|
||||
}
|
||||
});
|
||||
|
||||
doc.getText("text").insert(0, "hello");
|
||||
doc.commit({ origin: "test" });
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
expect(ran).toBeTruthy();
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1536,7 +1536,7 @@ fn undo_manager_events() -> anyhow::Result<()> {
|
|||
let pop_count_clone = pop_count.clone();
|
||||
let popped_value = Arc::new(Mutex::new(LoroValue::Null));
|
||||
let popped_value_clone = popped_value.clone();
|
||||
undo.set_on_push(Some(Box::new(move |_source, span| {
|
||||
undo.set_on_push(Some(Box::new(move |_source, span, _| {
|
||||
push_count_clone.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
UndoItemMeta {
|
||||
value: LoroValue::I64(span.start as i64),
|
||||
|
@ -1583,7 +1583,7 @@ fn undo_transform_cursor_position() -> anyhow::Result<()> {
|
|||
let mut undo = UndoManager::new(&doc);
|
||||
let cursors: Arc<Mutex<Vec<Cursor>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
let cursors_clone = cursors.clone();
|
||||
undo.set_on_push(Some(Box::new(move |_, _| {
|
||||
undo.set_on_push(Some(Box::new(move |_, _, _| {
|
||||
let mut ans = UndoItemMeta::new();
|
||||
let cursors = cursors_clone.try_lock().unwrap();
|
||||
for c in cursors.iter() {
|
||||
|
|
Loading…
Reference in a new issue