UNPKG

nitropage

Version:

A free and open source, extensible visual page builder based on SolidStart.

163 lines (146 loc) 4.11 kB
import { Accessor, createEffect, startTransition, untrack } from "solid-js"; import { reconcile, SetStoreFunction } from "solid-js/store"; import { EditorUiState, State } from "../../../../types"; import { SettingsContext } from "../../../lib/context/admin"; import { createIframeSync, makeSyncedSetStore, Message, PostMessage, } from "../../../lib/primitives/iframeSync"; import { deserialize, serialize } from "../../../utils/object/serialize"; import { makeStateReconciler } from "./reconcile"; import { makeApplyShortcut, makeParentShortcutListener, makeViewportShortcutListener, } from "./shortcuts"; export const makeSetters = ( postMessage: PostMessage, setState_: SetStoreFunction<State>, setEditorUi_: SetStoreFunction<EditorUiState>, ) => { const [setState, onMessage1] = makeSyncedSetStore( "NP_SET_STATE", setState_, postMessage, ); const [setEditorUi, onMessage2] = makeSyncedSetStore( "NP_SET_EDITORUI", setEditorUi_, postMessage, ); const onMessage = (message: Message) => { if (onMessage1(message)) return true; return onMessage2(message); }; return [setState, setEditorUi, onMessage] as const; }; const FOCUS_ELEMENT_TYPE = "npFocusElement"; const RECONCILE_STATE_TYPE = "npReconcileState"; const RECONCILE_EDITOR_UI_TYPE = "npReconcileEditorUi"; export const iframeSyncId = "npEditor"; export const createParentSync = ({ iframeRef, state, setStateLocal, editorUi, setEditorUiLocal, settingsContext, }: { iframeRef: Accessor<HTMLIFrameElement>; state: State; editorUi: EditorUiState; setStateLocal: SetStoreFunction<State>; setEditorUiLocal: SetStoreFunction<EditorUiState>; settingsContext: (typeof SettingsContext)["defaultValue"]; }) => { const [connected, postMessage] = createIframeSync({ id: iframeSyncId, iframeRef, onMessage(message) { if (onSetterMessage(message)) return; if (onShortcutMessage(message)) return; }, }); const [setState, setEditorUi, onSetterMessage] = makeSetters( postMessage, setStateLocal, setEditorUiLocal, ); const onShortcutMessage = makeParentShortcutListener( makeApplyShortcut({ stateContext: [state, setState], editorUiContext: [editorUi, setEditorUi], settingsContext, }), ); const postReconcileState = (state: Partial<State>) => postMessage({ type: RECONCILE_STATE_TYPE, data: serialize(state) }); const focusElement = (id: string) => { postMessage({ type: FOCUS_ELEMENT_TYPE, data: id, }); }; createEffect(() => { if (!connected()) { return; } untrack(() => { postMessage({ type: RECONCILE_EDITOR_UI_TYPE, data: serialize(editorUi), }); postReconcileState(state); }); }); return [ connected, setState, setEditorUi, postReconcileState, focusElement, ] as const; }; export const createViewportSync = ({ state, setStateLocal, setEditorUiLocal, focusElementById, }: { state: State; setStateLocal: SetStoreFunction<State>; setEditorUiLocal: SetStoreFunction<EditorUiState>; focusElementById: (id: string) => void; }) => { const reconcileState = makeStateReconciler(state, setStateLocal); const [connected, postMessage] = createIframeSync({ id: iframeSyncId, onMessage(message) { if (onMessage(message)) return; switch (message.type) { case FOCUS_ELEMENT_TYPE: focusElementById(message.data); break; case RECONCILE_STATE_TYPE: startTransition(() => reconcileState(deserialize(message.data))); break; case RECONCILE_EDITOR_UI_TYPE: startTransition(() => setEditorUiLocal( reconcile(deserialize(message.data), { key: null, merge: true }), ), ); break; } }, }); const [setState, setEditorUi, onMessage] = makeSetters( postMessage, setStateLocal, setEditorUiLocal, ); makeViewportShortcutListener(postMessage); return [connected, setState, setEditorUi] as const; };