diff --git a/src/assets/scss/_utilities.scss b/src/assets/scss/_utilities.scss index 1cdaf84bab..2caa9558e5 100644 --- a/src/assets/scss/_utilities.scss +++ b/src/assets/scss/_utilities.scss @@ -9,3 +9,7 @@ .mw-300px { max-width: 300px; } + +.right-0 { + right: 0; +} diff --git a/src/library-authoring/LibraryBlock/LibraryBlock.tsx b/src/library-authoring/LibraryBlock/LibraryBlock.tsx new file mode 100644 index 0000000000..87defc0d83 --- /dev/null +++ b/src/library-authoring/LibraryBlock/LibraryBlock.tsx @@ -0,0 +1,93 @@ +import { useEffect, useRef, useState } from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { getConfig } from '@edx/frontend-platform'; + +import messages from './messages'; + +interface LibraryBlockProps { + onBlockNotification?: (event: { eventType: string; [key: string]: any }) => void; + usageKey: string; +} +/** + * React component that displays an XBlock in a sandboxed IFrame. + * + * The IFrame is resized responsively so that it fits the content height. + * + * We use an IFrame so that the XBlock code, including user-authored HTML, + * cannot access things like the user's cookies, nor can it make GET/POST + * requests as the user. However, it is allowed to call any XBlock handlers. + */ +const LibraryBlock = ({ onBlockNotification, usageKey }: LibraryBlockProps) => { + const iframeRef = useRef(null); + const [iFrameHeight, setIFrameHeight] = useState(600); + const lmsBaseUrl = getConfig().LMS_BASE_URL; + + const intl = useIntl(); + + /** + * Handle any messages we receive from the XBlock Runtime code in the IFrame. + * See wrap.ts to see the code that sends these messages. + */ + /* istanbul ignore next */ + const receivedWindowMessage = async (event) => { + if (!iframeRef.current || event.source !== iframeRef.current.contentWindow) { + return; // This is some other random message. + } + + const { method, replyKey, ...args } = event.data; + + if (method === 'update_frame_height') { + setIFrameHeight(args.height); + } else if (method?.indexOf('xblock:') === 0) { + // This is a notification from the XBlock's frontend via 'runtime.notify(event, args)' + if (onBlockNotification) { + onBlockNotification({ + eventType: method.substr(7), // Remove the 'xblock:' prefix that we added in wrap.ts + ...args, + }); + } + } + }; + + /** + * Prepare to receive messages from the IFrame. + */ + useEffect(() => { + // Messages are the only way that the code in the IFrame can communicate + // with the surrounding UI. + window.addEventListener('message', receivedWindowMessage); + + return () => { + window.removeEventListener('message', receivedWindowMessage); + }; + }, []); + + return ( +
+