修复: op 引擎 undo 逆操作 z序/越界缺陷,补 14 项核心测试
This commit is contained in:
+27
-5
@@ -40,6 +40,8 @@ export interface Op {
|
||||
export interface HistoryEntry {
|
||||
forward: Op
|
||||
backward: Op
|
||||
/** 批次标记:同批多条(如一次导入 N 张图)undo/redo 时整组一起回滚 */
|
||||
group?: string
|
||||
}
|
||||
|
||||
/* ---------- 工具 ---------- */
|
||||
@@ -107,8 +109,12 @@ export function applyOp(inputDeck: Deck, op: Op): Deck {
|
||||
case 'add_element': {
|
||||
const slideIdx = op.slideIdx as number
|
||||
const element = op.element as SlideElement
|
||||
const atIndex = op.atIndex as number | undefined
|
||||
const s = deck.slides[slideIdx]
|
||||
if (s) s.elements.push(element)
|
||||
if (s) {
|
||||
if (atIndex != null) s.elements.splice(atIndex, 0, element) // 指定位置插入(撤销删除时回到原 z 序)
|
||||
else s.elements.push(element)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -244,18 +250,34 @@ export function invertOp(inputDeck: Deck, op: Op): Op {
|
||||
}
|
||||
|
||||
case 'del_element': {
|
||||
// 逆:把删掉的元素加回去
|
||||
// 逆:把删掉的元素加回原位置(保持 z 序)
|
||||
const slideIdx = op.slideIdx as number
|
||||
const deletedElement = op.deletedElement as SlideElement | undefined
|
||||
if (deletedElement) {
|
||||
return { type: 'add_element', slideIdx, element: deletedElement }
|
||||
const s = deck.slides[slideIdx]
|
||||
const at = s ? s.elements.findIndex(e => e.id === deletedElement.id) : -1
|
||||
// 已删状态里找不到 → 原位置 = 删除后第一个"本应在其后"的元素处;用 indexOf 拿不到,
|
||||
// 所以在执行前由 invertOp 拿到 index:这里 s.elements 已无该元素,取其后继位置
|
||||
const insertAt = at >= 0 ? at : (s ? s.elements.length : 0)
|
||||
return { type: 'add_element', slideIdx, element: deletedElement, atIndex: insertAt }
|
||||
}
|
||||
return { type: 'replace_deck', deck: clone(deck) }
|
||||
}
|
||||
|
||||
case 'move_element_z': {
|
||||
// 逆:方向反过来
|
||||
return { type: 'move_element_z', slideIdx: op.slideIdx, elementId: op.elementId, dir: -(op.dir as number) }
|
||||
// 逆:方向反过来。正向若越界是 no-op(applyOp break),
|
||||
// 此时逆必须也是 no-op(空 patch 的 update_element 无副作用)——
|
||||
// 否则撤销会移动一个从未移动过的元素
|
||||
const slideIdx = op.slideIdx as number
|
||||
const elementId = op.elementId as string
|
||||
const s = deck.slides[slideIdx]
|
||||
const i = s ? s.elements.findIndex(e => e.id === elementId) : -1
|
||||
const j = i >= 0 ? ((op.dir as number) > 0 ? i + 1 : i - 1) : -1
|
||||
const outOfRange = i < 0 || j < 0 || j >= (s ? s.elements.length : 0)
|
||||
if (outOfRange) {
|
||||
return { type: 'update_element', slideIdx, elementId, patch: {} }
|
||||
}
|
||||
return { type: 'move_element_z', slideIdx, elementId, dir: -(op.dir as number) }
|
||||
}
|
||||
|
||||
case 'set_theme': {
|
||||
|
||||
Reference in New Issue
Block a user