import type { ActionState } from '../runtime/action-runner'; interface BridgeContext { loaded: boolean; } export const bridgeContext: BridgeContext = import.meta.hot?.data?.editorBridgeContext ?? { loaded: false, }; if (import.meta.hot && import.meta.hot.data) { import.meta.hot.data.editorBridgeContext = bridgeContext; } export type EventPayload = { pageName: string; } & Record; type EventHandler = (payload: EventPayload) => void; type EventMap = Map>; type Page = { name: string; title: string; actionIds?: string[]; }; type SectionProps = { id: string; pageName: string; content: string; domId: string; rootDomId: string; sort?: number; }; /** * 构筑一个无头的 editor 的 bridge。 * 所有操作编辑器的方法,都需要经由此 bridge 进行广播。 * 其内部所保存的 pages 与 sections 均为虚拟数据,与 editor 所需的实际数据有一定差异。 */ export class EditorBridge { #pages: Map = new Map(); #sections: Map = new Map(); #events: EventMap = new Map(); #watchHandlers: Set<(event: { type: string; payload: EventPayload }) => void> = new Set(); /** * 监听事件 * @param event 事件名称 * @param handler 处理函数 */ on(event: string, handler: EventHandler) { if (!this.#events.has(event)) { this.#events.set(event, new Set()); } this.#events.get(event)?.add(handler); return this; } /** * 移除事件监听 * @param event 事件名称 * @param handler 处理函数 */ off(event: string, handler?: EventHandler) { if (!handler) { this.#events.delete(event); } else if (this.#events.has(event)) { this.#events.get(event)?.delete(handler); } return this; } /** * 触发事件 * @param event 事件名称 * @param payload 事件负载对象 */ #emit(event: string, payload: EventPayload) { this.#events.get(event)?.forEach((handler) => handler(payload)); // 同时通知所有 watch 监听器 this.#watchHandlers.forEach((handler) => handler({ type: event, payload })); } /** * 创建页面 * @param pageName 页面名称 */ async createPage(name: string, { title, actionIds }: { title?: string; actionIds?: string[] } = {}) { const newPage: Page = { name, title: title ?? '未命名页面', actionIds: actionIds ?? [] }; this.#pages.set(name, newPage); // 发出 add_page 事件,其他地方可通过 editorBridge.on('add_page', (payload) => {}) 监听 this.#emit('add_page', { pageName: name, ...newPage, }); } /** * 更新页面属性 * * @param pageName * @param param1 * @returns */ async updatePageAttributes(pageName: string, { title }: { title?: string } = {}) { const page = this.#pages.get(pageName); if (!page) { return; } const actionIds = page.actionIds; this.#emit('upsert_page', { pageName, title, actionIds, }); } async removePage(pageName: string) { this.#pages.delete(pageName); // 发出 remove_page 事件,其他地方可通过 editorBridge.on('remove_page', (payload) => {}) 监听 this.#emit('remove_page', { pageName }); } async updateSection(action: ActionState) { this.#sections.set(action.id, action); // 发出 add_page_section 事件,其他地方可通过 editorBridge.on('add_page_section', (payload) => {}) 监听 this.#emit('update_section', { pageName: action.pageName, id: action.id, section: action }); } async upsertPageAction(pageName: string, pageTitle: string, actionId: string) { const page = this.#pages.get(pageName); const pageProps = page ? { ...page, actionIds: [...(page.actionIds ?? []), actionId], } : { name: pageName, title: pageTitle || '未命名页面', actionIds: [actionId], }; pageProps.actionIds = [...new Set(pageProps.actionIds)]; this.#pages.set(pageName, pageProps); // 发出 update_page 事件,其他地方可通过 editorBridge.on('update_page', (payload) => {}) 监听 this.#emit('upsert_page', { pageName, ...pageProps, }); } // /** // * 获取页面历史记录 // * @param pageName 页面名称 // * @returns 页面历史 // */ // async getPageHistory(pageName: string): Promise { // return this.#pages.get(pageName) || '{}'; // } /** * 监听所有事件 * @param handler 处理所有事件的函数 */ watch(handler: (event: { type: string; payload: EventPayload }) => void) { this.#watchHandlers.add(handler); return this; } /** * 移除 watch 监听 * @param handler 处理函数 */ unwatch(handler: (event: { type: string; payload: EventPayload }) => void) { this.#watchHandlers.delete(handler); return this; } } export let editorBridge: Promise = new Promise(() => { // noop for ssr }); if (!import.meta.env.SSR) { editorBridge = import.meta.hot?.data?.editorBridge ?? Promise.resolve() .then(() => { return new EditorBridge(); }) .then(async (editorBridge) => { bridgeContext.loaded = true; return editorBridge; }); if (import.meta.hot && import.meta.hot.data) { import.meta.hot.data.editorBridge = editorBridge; } }