UNPKG

redraw-drawio

Version:

react component for drawio

372 lines (366 loc) 11.2 kB
import { forwardRef, useRef, useState, useCallback, useImperativeHandle, useEffect } from 'react'; const getEmbedUrl = (urlParameters, addConfiguration)=>{ const url = new URL("/", "https://embed.diagrams.net"); const urlSearchParams = new URLSearchParams(); urlSearchParams.append("proto", "json"); if (addConfiguration) { urlSearchParams.append("configure", "1"); } if (urlParameters) { Object.keys(urlParameters).forEach((key)=>{ const value = urlParameters[key]; if (value) { if (typeof value === "boolean") { urlSearchParams.append(key, value ? "1" : "0"); } else { urlSearchParams.append(key, value.toString()); } } }); } url.search = urlSearchParams.toString(); return url.toString(); }; /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Type alias for a map of event handlers. * It maps each event name to its corresponding handler function, * which receives the event data typed to the specific event type. */ // export type EventHandler = { // [E in EmbedEvents as E['event']]?: (data: E) => void; // }; /** * Handles an incoming postMessage event by parsing the data * and invoking the appropriate event handler if available. * * @param event - The MessageEvent received from the postMessage listener. * @param handlers - A map of event handlers keyed by the event type. */ function handleEvent(event, handlers) { // Validate the event origin to ensure it comes from 'embed.diagrams.net'. if (!event.origin.includes("embed.diagrams.net")) { return; } // export function handleEvent(event: MessageEvent, handlers: EventHandler) { try { // Attempt to parse the event data as an EmbedEvent. const data = JSON.parse(event.data); // Check if there is a handler for the received event type. if (isEmbedEvent(data)) { const handler = handlers[data.event]; if (handler) { // Invoke the handler with the correctly typed data. handler(data); } } } catch { // An empty catch block should be avoided, at least log the error. console.error("Failed to handle event"); } } // Helper type guard function to discriminate the EmbedEvents type function isEmbedEvent(data) { return typeof data === "object" && data !== null && "event" in data; } /** * @description A hook to send actions to the draw.io editor * @param {RefObject<HTMLIFrameElement | null>} iframeRef * @returns {EmbedActions} */ const useActions = (iframeRef)=>{ function sendAction(action, data) { iframeRef.current?.contentWindow?.postMessage(JSON.stringify({ action, ...data }), "*"); } /** * Loads the given XML into the editor * @param {UniqueActionProps<ActionLoad>} data * @returns {void} */ const load = (data)=>{ sendAction("load", data); }; /** * Configure the editor * @param {UniqueActionProps<ActionConfigure>} data * @returns {void} */ const configure = (data)=>{ sendAction("configure", data); }; /** * Merge the contents of the given XML into the current file * @param {UniqueActionProps<ActionMerge>} data * @returns {void} */ const merge = (data)=>{ sendAction("merge", data); }; /** * Display a dialog in the editor window * @param {UniqueActionProps<ActionDialog>} data * @returns {void} */ const dialog = (data)=>{ sendAction("dialog", data); }; /** * Display a prompt in the editor window * @param {UniqueActionProps<ActionPrompt>} data * @returns {void} */ const prompt = (data)=>{ sendAction("prompt", data); }; /** * Show the template dialog * @param {UniqueActionProps<ActionTemplate>} data * @returns {void} */ const template = (data)=>{ sendAction("template", data); }; /** * Runs an array of layouts using the same format as Arrange > Layout > Apply. * @param {UniqueActionProps<ActionLayout>} data * @returns {void} */ const layout = (data)=>{ sendAction("layout", data); }; /** * Show a draft dialog * @param {UniqueActionProps<ActionDraft>} data * @returns {void} */ const draft = (data)=>{ sendAction("draft", data); }; /** * Display a message in the status bar * @param {UniqueActionProps<ActionStatus>} data * @returns {void} */ const status = (data)=>{ sendAction("status", data); }; /** * Display a spinner with a message or hide the current spinner if show is set to false * @param {UniqueActionProps<ActionSpinner>} data * @returns {void} */ const spinner = (data)=>{ sendAction("spinner", data); }; /** * Export the current diagram * @param {UniqueActionProps<ActionExport>} data * @returns {void} */ const exportDiagram = (data)=>{ sendAction("export", data); }; /** * @description A hook to send actions to the draw.io editor */ return { load, configure, merge, dialog, prompt, template, layout, draft, status, spinner, exportDiagram }; }; /** * Draw.io embed component * @param {DrawIoEmbedProps} props * @param {string} props.urlParameters * @param {string} props.configuration * @param {string} props.xml * @param {(data: any) => void} props.onSave * @param {(data: any) => void} props.onClose * @param {(data: any) => void} props.onLoad * @param {(data: any) => void} props.onConfigure * @param {(data: any) => void} props.onDraft * @param {(data: any) => void} props.onExport * @param {(data: any) => void} props.onMerge * @param {(data: any) => void} props.onPrompt * @param {(data: any) => void} props.onTemplate * @param {React.Ref<DrawIoEmbedRef>} ref * @returns {JSX.Element} */ const DrawIOEmbed = /*#__PURE__*/ forwardRef(function DrawIoEmbed(props, ref) { const { urlParameters, configuration, xml, onSave, onClose, onLoad, onConfigure, onDraft, onExport, onMerge, onPrompt, onTemplate } = props; const iframeRef = useRef(null); const action = useActions(iframeRef); const iframeUrl = getEmbedUrl(urlParameters, !!configuration); const [isInitialized, setIsInitialized] = useState(false); /** * @param {MessageEvent} evt * @returns {void} */ const messageHandler = useCallback((evt)=>{ /** * @param {MessageEvent} evt * @returns {void} */ handleEvent(evt, { /** * @returns {void} */ init: ()=>{ setIsInitialized(true); }, /** * @param {EventUnion<"load">} data * @returns {void} */ load: (data)=>{ if (onLoad) { onLoad(data); } }, /** * * @param {EventUnion<"configure">} data * @returns {void} */ configure: (data)=>{ if (configuration) { action.configure({ config: configuration }); } if (onConfigure) { onConfigure(data); } }, /** * @param {EventUnion<"save">} data * @returns {void} */ save: (data)=>{ action.exportDiagram({ format: "xmlsvg", exit: data.exit }); }, /** * @param {EventUnion<"exit">} data * @returns {void} */ exit: (data)=>{ if (onClose) { onClose(data); } }, /** * @param {EventUnion<"draft">} data * @returns {void} */ draft: (data)=>{ if (onDraft) { onDraft(data); } }, /** * @param {EventUnion<"export">} data * @returns {void} */ export: (data)=>{ if (onSave) { onSave({ event: "save", xml: data.data }); } /** * Check if exit is true and call onClose * @returns {void} */ if (onExport) { onExport(data); } if (data.message.exit && onClose) { onClose({ event: "exit", modified: true }); } }, /** * @param {EventUnion<"merge">} data * @returns {void} */ merge: (data)=>{ if (onMerge) { onMerge(data); } }, /** * @param {EventUnion<'prompt'>} data * @returns {void} */ prompt: (data)=>{ if (onPrompt) { onPrompt(data); } }, /** * @param {EventUnion<'template'>} data * @returns {void} */ template: (data)=>{ if (onTemplate) { onTemplate(data); } } }); }, [ action, configuration, onClose, onSave, onLoad, onConfigure, onDraft, onExport, onMerge, onPrompt, onTemplate ]); useImperativeHandle(/** * @param {React.Ref<DrawIoEmbedRef>} ref */ ref, ()=>({ /** * @param {string} xml * @returns {void} */ ...action }), [ action ]); useEffect(()=>{ if (isInitialized) { if (xml) { action.load({ xml }); } else { action.load({ xml: "" }); } } }, [ action, isInitialized, xml ]); // Initial load useEffect(()=>{ /** * Add event listener for messages from iframe */ window.addEventListener("message", messageHandler); /** * Remove event listener for messages from iframe */ return ()=>{ window.removeEventListener("message", messageHandler); }; }, [ messageHandler ]); return /*#__PURE__*/ React.createElement("iframe", { className: "diagrams-iframe", src: iframeUrl, ref: iframeRef, title: "Diagrams.net", style: { width: "100%", height: "100%", minWidth: "400px", minHeight: "400px", border: "none" } }); }); export { DrawIOEmbed, useActions }; //# sourceMappingURL=index.js.map