import{ax as i,z as a,A as n,b5 as h}from"./chunks/framework.V2ssSR2R.js";const E=JSON.parse('{"title":"historyService事件","description":"","frontmatter":{},"headers":[],"relativePath":"api/editor/historyServiceEvents.md","filePath":"api/editor/historyServiceEvents.md"}'),l={name:"api/editor/historyServiceEvents.md"};function k(p,s,t,e,d,r){return n(),a("div",null,[...s[0]||(s[0]=[h(`
详情: 页面切换
事件回调函数: (undoRedo: UndoRedo) => void
export class UndoRedo<T = any> {
/**
* 由 {@link UndoRedo.serialize} 产出的快照重建一个 UndoRedo 实例。
* 游标会被夹紧到 [0, length] 区间,避免脏数据导致越界。
*
* @param options.isSavedStep 可选谓词:若提供,则把游标定位到「最近一条满足该谓词的记录」之后
* (即恢复到最近一个已保存点);找不到匹配记录时退回快照中的原游标。
*/
public static fromSerialized<T = any>(
data: SerializedUndoRedo<T>,
options: { isSavedStep?: (element: T) => boolean } = {},
): UndoRedo<T> {
const undoRedo = new UndoRedo<T>(data.listMaxSize);
const list = Array.isArray(data.elementList) ? data.elementList.map((item) => cloneDeep(item)) : [];
let cursor = Number.isFinite(data.listCursor) ? data.listCursor : list.length;
// 本地数据同样遵循容量上限:超出时裁掉最旧的记录(与 pushElement 的 shift 行为一致),并同步回退游标。
const overflow = list.length - undoRedo.listMaxSize;
if (overflow > 0) {
list.splice(0, overflow);
cursor -= overflow;
}
// 若指定了「已保存」谓词,则把游标移动到最近一条已保存记录之后;在裁剪后的 list 上查找以保证索引正确。
if (options.isSavedStep) {
for (let i = list.length - 1; i >= 0; i--) {
if (options.isSavedStep(list[i])) {
cursor = i + 1;
break;
}
}
}
undoRedo.elementList = list;
undoRedo.listCursor = Math.max(0, Math.min(cursor, list.length));
return undoRedo;
}
private elementList: T[];
private listCursor: number;
private listMaxSize: number;
constructor(listMaxSize = 100) {
const minListMaxSize = 2;
this.elementList = [];
this.listCursor = 0;
this.listMaxSize = listMaxSize > minListMaxSize ? listMaxSize : minListMaxSize;
}
/**
* 导出当前栈的可序列化快照(深克隆,避免外部改动污染内部状态)。
* 配合 {@link UndoRedo.fromSerialized} 可在持久化后完整还原撤销/重做栈。
*/
public serialize(): SerializedUndoRedo<T> {
return {
elementList: this.elementList.map((item) => cloneDeep(item)),
listCursor: this.listCursor,
listMaxSize: this.listMaxSize,
};
}
public pushElement(element: T): void {
// 新元素进来时,把游标之外的元素全部丢弃,并把新元素放进来
this.elementList.splice(this.listCursor, this.elementList.length - this.listCursor, cloneDeep(element));
this.listCursor += 1;
// 如果list中的元素超过maxSize,则移除第一个元素
if (this.elementList.length > this.listMaxSize) {
this.elementList.shift();
this.listCursor -= 1;
}
}
public canUndo(): boolean {
return this.listCursor > 0;
}
/** 返回被撤销的操作 */
public undo(): T | null {
if (!this.canUndo()) {
return null;
}
this.listCursor -= 1;
return cloneDeep(this.elementList[this.listCursor]);
}
public canRedo() {
return this.elementList.length > this.listCursor;
}
/** 返回被重做的操作 */
public redo(): T | null {
if (!this.canRedo()) {
return null;
}
const element = cloneDeep(this.elementList[this.listCursor]);
this.listCursor += 1;
return element;
}
public getCurrentElement(): T | null {
if (this.listCursor < 1) {
return null;
}
return cloneDeep(this.elementList[this.listCursor - 1]);
}
/**
* 用 \`element\` 替换当前游标所在元素(cursor - 1),并丢弃其后的重做尾部(与 {@link pushElement} 的丢尾一致),
* 游标位置保持不变(元素数量不增)。cursor 为 0(无已应用元素)时不做任何操作并返回 false。
* 用于「连续同类记录合并」等就地替换最新一条的场景。
*/
public replaceCurrentElement(element: T): boolean {
if (this.listCursor < 1) return false;
this.elementList.splice(this.listCursor - 1, this.elementList.length - (this.listCursor - 1), cloneDeep(element));
return true;
}
/**
* 对当前游标所在元素(cursor - 1)做就地更新;cursor 为 0(全部已撤销)时不做任何操作。
* 用于给「当前步骤」打标记(如标记为已保存)等元数据写入场景。
*/
public updateCurrentElement(updater: (element: T) => void): void {
if (this.listCursor < 1) return;
updater(this.elementList[this.listCursor - 1]);
}
/** 对栈内全部元素做就地更新。用于批量清理元数据(如清空所有元素的已保存标记)。 */
public updateElements(updater: (element: T, index: number) => void): void {
this.elementList.forEach(updater);
}
/**
* 返回栈内全部元素的浅克隆数组(按时间顺序,索引 0 为最早一步)。
* 仅用于历史面板等只读展示场景,不应直接修改返回值。
*/
public getElementList(): T[] {
return this.elementList.slice();
}
/**
* 当前游标位置:表示已应用的步骤数量。
* - cursor === 0 表示全部已撤销
* - cursor === length 表示已重做到末尾
* 历史面板用于区分"已应用 / 已撤销"两段。
*/
public getCursor(): number {
return this.listCursor;
}
/** 栈内总步数。 */
public getLength(): number {
return this.elementList.length;
}
}详情: 历史记录发生变化
事件回调函数: (state: StepValue | null) => void
export interface StepValue extends BaseStepValue<MNode> {
/** 页面信息 */
data: { name: string; id: Id };
/** 操作前选中的节点 ID,用于撤销后恢复选择状态 */
selectedBefore: Id[];
/** 操作后选中的节点 ID,用于重做后恢复选择状态 */
selectedAfter: Id[];
modifiedNodeIds: Map<Id, Id>;
}/**
* 历史记录操作类型:
* - \`add\` / \`remove\` / \`update\`:普通可撤销/重做的节点变更;
* - \`initial\`:页面「未修改的初始状态」基线(设置 root 时生成),作为页面栈 index 0 的固定底线 step。
* 该 step 不可被撤销/回滚(cursor 不会低于它),仅用于历史面板底部的初始行展示。
*/
export type HistoryOpType = 'add' | 'remove' | 'update' | 'initial';/**
* 历史记录的「操作途径」——标记本次变更由哪条交互入口触发,仅用于历史面板展示 / 业务埋点,
* 不影响 undo/redo 行为。缺省(未传)时 UI 视为「未知」。
*
* - \`stage\`:画布(拖拽 / 缩放 / 排序等舞台直接操作)
* - \`tree\`:树形面板(图层 / 数据源 / 代码块等树形结构里的拖拽 / 菜单操作)
* - \`component-panel\`:组件面板(左侧组件列表点击 / 拖拽新增组件)
* - \`props\`:配置面板表单(属性表单字段编辑)
* - \`code\`:源码编辑器(配置面板「源码」面板里直接编辑 JSON/代码后保存)
* - \`stage-contextmenu\`:画布右键菜单(舞台上节点的右键上下文菜单)
* - \`tree-contextmenu\`:树面板右键菜单(图层 / 数据源 / 代码块等树形列表上的右键上下文菜单)
* - \`toolbar\`:工具栏菜单(顶部导航工具栏按钮)
* - \`shortcut\`:键盘快捷键
* - \`rollback\`:历史回滚(历史面板里对某条历史「回滚」,反向应用为一条新记录,类 git revert)
* - \`api\`:代码 / 接口调用(程序化触发)
* - \`ai\`:AI 生成 / 智能助手触发的变更
* - \`unknown\`:未知来源
*
* 通过 \`(string & {})\` 允许业务侧扩展自定义途径字符串,同时保留内置值的自动补全。
*/
export type HistoryOpSource =
| 'initial'
| 'stage'
| 'tree'
| 'component-panel'
| 'props'
| 'code'
| 'root-code'
| 'stage-contextmenu'
| 'tree-contextmenu'
| 'toolbar'
| 'shortcut'
| 'rollback'
| 'api'
| 'ai'
// 同步
| 'sync'
| 'unknown'
| (string & {});export type Id = string | number;export type MNode = MComponent | MContainer | MIteratorContainer | MPage | MApp | MPageFragment;TIP
当游标处于历史栈边界(已经无法继续撤销或重做)时,UndoRedo.undo() / redo() 返回 null,对应 change 回调收到的 state 为 null
详情: 代码块历史记录发生变化(pushCodeBlock / undoCodeBlock / redoCodeBlock 成功时触发)
事件回调函数: (codeBlockId: Id, step: CodeBlockStepValue) => void
/**
* 代码块历史记录条目。按 codeBlock.id 分组保存到 historyState.codeBlockState。
* 变更内容统一由 \`diff\`(单项)表达,每项见 {@link StepDiffItem}:
* - 新增(opType 'add'):仅 \`newSchema\`(新内容);
* - 更新(opType 'update'):\`oldSchema\` + \`newSchema\`,并可带 \`changeRecords\` 做局部更新;
* - 删除(opType 'remove'):仅 \`oldSchema\`(删除前内容)。
*/
export interface CodeBlockStepValue extends BaseStepValue<CodeBlockContent> {
/** 关联的代码块 id */
id: Id;
}/**
* 历史记录的「操作途径」——标记本次变更由哪条交互入口触发,仅用于历史面板展示 / 业务埋点,
* 不影响 undo/redo 行为。缺省(未传)时 UI 视为「未知」。
*
* - \`stage\`:画布(拖拽 / 缩放 / 排序等舞台直接操作)
* - \`tree\`:树形面板(图层 / 数据源 / 代码块等树形结构里的拖拽 / 菜单操作)
* - \`component-panel\`:组件面板(左侧组件列表点击 / 拖拽新增组件)
* - \`props\`:配置面板表单(属性表单字段编辑)
* - \`code\`:源码编辑器(配置面板「源码」面板里直接编辑 JSON/代码后保存)
* - \`stage-contextmenu\`:画布右键菜单(舞台上节点的右键上下文菜单)
* - \`tree-contextmenu\`:树面板右键菜单(图层 / 数据源 / 代码块等树形列表上的右键上下文菜单)
* - \`toolbar\`:工具栏菜单(顶部导航工具栏按钮)
* - \`shortcut\`:键盘快捷键
* - \`rollback\`:历史回滚(历史面板里对某条历史「回滚」,反向应用为一条新记录,类 git revert)
* - \`api\`:代码 / 接口调用(程序化触发)
* - \`ai\`:AI 生成 / 智能助手触发的变更
* - \`unknown\`:未知来源
*
* 通过 \`(string & {})\` 允许业务侧扩展自定义途径字符串,同时保留内置值的自动补全。
*/
export type HistoryOpSource =
| 'initial'
| 'stage'
| 'tree'
| 'component-panel'
| 'props'
| 'code'
| 'root-code'
| 'stage-contextmenu'
| 'tree-contextmenu'
| 'toolbar'
| 'shortcut'
| 'rollback'
| 'api'
| 'ai'
// 同步
| 'sync'
| 'unknown'
| (string & {});export interface CodeBlockContent {
/** 代码块名称 */
name: string;
/** 代码块内容 */
content: ((...args: any[]) => any) | Function;
/** 参数定义 */
params: CodeParam[] | [];
/** 注释 */
desc?: string;
/** 扩展字段 */
[propName: string]: any;
}export type Id = string | number;TIP
oldContent 为 nullnewContent 为 nullundo / redo 返回 null(边界状态)时不会触发该事件详情: 数据源历史记录发生变化(pushDataSource / undoDataSource / redoDataSource 成功时触发)
事件回调函数: (dataSourceId: Id, step: DataSourceStepValue) => void
/**
* 数据源历史记录条目。按 dataSource.id 分组保存到 historyState.dataSourceState。
* 变更内容统一由 \`diff\`(单项)表达,每项见 {@link StepDiffItem}:
* - 新增(opType 'add'):仅 \`newSchema\`(新 schema);
* - 更新(opType 'update'):\`oldSchema\` + \`newSchema\`,并可带 \`changeRecords\` 做局部更新;
* - 删除(opType 'remove'):仅 \`oldSchema\`(删除前 schema)。
*/
export interface DataSourceStepValue extends BaseStepValue<DataSourceSchema> {
/** 关联的数据源 id */
id: Id;
}/**
* 历史记录的「操作途径」——标记本次变更由哪条交互入口触发,仅用于历史面板展示 / 业务埋点,
* 不影响 undo/redo 行为。缺省(未传)时 UI 视为「未知」。
*
* - \`stage\`:画布(拖拽 / 缩放 / 排序等舞台直接操作)
* - \`tree\`:树形面板(图层 / 数据源 / 代码块等树形结构里的拖拽 / 菜单操作)
* - \`component-panel\`:组件面板(左侧组件列表点击 / 拖拽新增组件)
* - \`props\`:配置面板表单(属性表单字段编辑)
* - \`code\`:源码编辑器(配置面板「源码」面板里直接编辑 JSON/代码后保存)
* - \`stage-contextmenu\`:画布右键菜单(舞台上节点的右键上下文菜单)
* - \`tree-contextmenu\`:树面板右键菜单(图层 / 数据源 / 代码块等树形列表上的右键上下文菜单)
* - \`toolbar\`:工具栏菜单(顶部导航工具栏按钮)
* - \`shortcut\`:键盘快捷键
* - \`rollback\`:历史回滚(历史面板里对某条历史「回滚」,反向应用为一条新记录,类 git revert)
* - \`api\`:代码 / 接口调用(程序化触发)
* - \`ai\`:AI 生成 / 智能助手触发的变更
* - \`unknown\`:未知来源
*
* 通过 \`(string & {})\` 允许业务侧扩展自定义途径字符串,同时保留内置值的自动补全。
*/
export type HistoryOpSource =
| 'initial'
| 'stage'
| 'tree'
| 'component-panel'
| 'props'
| 'code'
| 'root-code'
| 'stage-contextmenu'
| 'tree-contextmenu'
| 'toolbar'
| 'shortcut'
| 'rollback'
| 'api'
| 'ai'
// 同步
| 'sync'
| 'unknown'
| (string & {});export type Id = string | number;TIP
oldSchema 为 nullnewSchema 为 nullundo / redo 返回 null(边界状态)时不会触发该事件详情: 调用 markSaved / markPageSaved / markCodeBlockSaved / markDataSourceSaved 标记「已保存」记录时触发
事件回调函数: (payload: { kind: 'all' | 'page' | 'code-block' | 'data-source'; id?: Id }) => void
TIP
markSaved 触发时 kind 为 all,无 idkind 对应类别,id 为目标页面 / 代码块 / 数据源 id详情: saveToIndexedDB 把历史记录写入本地 IndexedDB 成功时触发
事件回调函数: (snapshot: PersistedHistoryState) => void
/**
* 历史记录的可持久化快照。由 historyService.saveToIndexedDB 写入 IndexedDB,
* 再由 historyService.restoreFromIndexedDB 读出并重建各 UndoRedo 栈。
*/
export interface PersistedHistoryState {
/** 快照结构版本号,便于后续兼容升级。 */
version: number;
/** 保存时的活动页 id。 */
pageId?: Id;
/** 各页面历史栈的序列化快照,按 pageId 分组。 */
pageSteps: Record<Id, SerializedUndoRedo<StepValue>>;
/** 各代码块历史栈的序列化快照,按 codeBlockId 分组。 */
codeBlockState: Record<Id, SerializedUndoRedo<CodeBlockStepValue>>;
/** 各数据源历史栈的序列化快照,按 dataSourceId 分组。 */
dataSourceState: Record<Id, SerializedUndoRedo<DataSourceStepValue>>;
/** 保存时间戳(毫秒)。 */
savedAt: number;
}/**
* UndoRedo 栈的可序列化快照,用于持久化(如写入 IndexedDB)后再还原。
*/
export interface SerializedUndoRedo<T = any> {
/** 栈内全部元素(按时间正序,索引 0 为最早一步)。 */
elementList: T[];
/** 游标位置(已应用步骤数量)。 */
listCursor: number;
/** 栈容量上限。 */
listMaxSize: number;
}详情: restoreFromIndexedDB 从本地 IndexedDB 读取并重建历史记录成功时触发(找不到记录时不触发)
事件回调函数: (snapshot: PersistedHistoryState) => void
/**
* 历史记录的可持久化快照。由 historyService.saveToIndexedDB 写入 IndexedDB,
* 再由 historyService.restoreFromIndexedDB 读出并重建各 UndoRedo 栈。
*/
export interface PersistedHistoryState {
/** 快照结构版本号,便于后续兼容升级。 */
version: number;
/** 保存时的活动页 id。 */
pageId?: Id;
/** 各页面历史栈的序列化快照,按 pageId 分组。 */
pageSteps: Record<Id, SerializedUndoRedo<StepValue>>;
/** 各代码块历史栈的序列化快照,按 codeBlockId 分组。 */
codeBlockState: Record<Id, SerializedUndoRedo<CodeBlockStepValue>>;
/** 各数据源历史栈的序列化快照,按 dataSourceId 分组。 */
dataSourceState: Record<Id, SerializedUndoRedo<DataSourceStepValue>>;
/** 保存时间戳(毫秒)。 */
savedAt: number;
}