diff --git a/src/course-unit/CourseUnit.jsx b/src/course-unit/CourseUnit.jsx
index 67088658d2..a01d9817f7 100644
--- a/src/course-unit/CourseUnit.jsx
+++ b/src/course-unit/CourseUnit.jsx
@@ -53,7 +53,6 @@ const CourseUnit = ({ courseId }) => {
handleXBlockDragAndDrop,
canPasteComponent,
} = useCourseUnit({ courseId, blockId });
-
const initialXBlocksData = useMemo(() => courseVerticalChildren.children ?? [], [courseVerticalChildren.children]);
const [unitXBlocks, setUnitXBlocks] = useState(initialXBlocksData);
diff --git a/src/course-unit/course-xblock/CourseXBlock.jsx b/src/course-unit/course-xblock/CourseXBlock.jsx
index f7734522bc..94bb4615e4 100644
--- a/src/course-unit/course-xblock/CourseXBlock.jsx
+++ b/src/course-unit/course-xblock/CourseXBlock.jsx
@@ -21,6 +21,7 @@ import {
} from '../data/selectors';
import {
copyToClipboard,
+ fetchXBlockModalDataQuery,
fetchXBlockIframeHtmlAndResourcesQuery,
} from '../data/thunk';
import { COMPONENT_TYPES } from '../constants';
@@ -44,6 +45,7 @@ const CourseXBlock = ({
const intl = useIntl();
const xblockIframeHtmlAndResources = useSelector(getXBlockIframeHtmlAndResources);
const xblockInstanceHtmlAndResources = find(xblockIframeHtmlAndResources, { xblockId: id });
+ const xblockModalData = useSelector(state => state.courseUnit.xblockModalData);
const visibilityMessage = userPartitionInfo.selectedGroupsLabel
? intl.formatMessage(messages.visibilityMessage, { selectedGroupsLabel: userPartitionInfo.selectedGroupsLabel })
@@ -73,6 +75,7 @@ const CourseXBlock = ({
navigate(`/course/${courseId}/editor/${type}/${id}`);
break;
default:
+ dispatch(fetchXBlockModalDataQuery(id));
}
};
@@ -88,83 +91,86 @@ const CourseXBlock = ({
}, []);
return (
-
-
-
-
-
-
+
+
+
+
+
-
- unitXBlockActions.handleDuplicate(id)}>
- {intl.formatMessage(messages.blockLabelButtonDuplicate)}
-
-
- {intl.formatMessage(messages.blockLabelButtonMove)}
-
- {canEdit && (
- dispatch(copyToClipboard(id))}>
- {intl.formatMessage(messages.blockLabelButtonCopyToClipboard)}
+
+
+
+ unitXBlockActions.handleDuplicate(id)}>
+ {intl.formatMessage(messages.blockLabelButtonDuplicate)}
- )}
-
- {intl.formatMessage(messages.blockLabelButtonManageAccess)}
-
-
- {intl.formatMessage(messages.blockLabelButtonDelete)}
-
-
-
-
-
-
- )}
- />
-
- {renderError ? : (
- <>
-
- {xblockInstanceHtmlAndResources && (
-
+ {intl.formatMessage(messages.blockLabelButtonMove)}
+
+ {canEdit && (
+ dispatch(copyToClipboard(id))}>
+ {intl.formatMessage(messages.blockLabelButtonCopyToClipboard)}
+
+ )}
+
+ {intl.formatMessage(messages.blockLabelButtonManageAccess)}
+
+
+ {intl.formatMessage(messages.blockLabelButtonDelete)}
+
+
+
+
+
- )}
- >
- )}
-
-
-
+
+ )}
+ />
+
+ {renderError ? : (
+ <>
+
+ {xblockInstanceHtmlAndResources && (
+
+ )}
+ >
+ )}
+
+
+
+ >
);
};
diff --git a/src/course-unit/course-xblock/CourseXblock.scss b/src/course-unit/course-xblock/CourseXblock.scss
index 6e5c4b516b..22e9bc4e34 100644
--- a/src/course-unit/course-xblock/CourseXblock.scss
+++ b/src/course-unit/course-xblock/CourseXblock.scss
@@ -36,3 +36,12 @@
margin-bottom: 0;
}
}
+
+.xblock-iframe-modal-wrapper {
+ height: 100%;
+ position: absolute;
+ z-index: 99999999999;
+ top: 0;
+ left: 0;
+ width: 100%;
+}
diff --git a/src/course-unit/course-xblock/xblock-content/XBlockContent.jsx b/src/course-unit/course-xblock/xblock-content/XBlockContent.jsx
index 1e6a620878..3741c859bc 100644
--- a/src/course-unit/course-xblock/xblock-content/XBlockContent.jsx
+++ b/src/course-unit/course-xblock/xblock-content/XBlockContent.jsx
@@ -19,7 +19,7 @@ ensureConfig(['STUDIO_BASE_URL', 'SECURE_ORIGIN_XBLOCK_BOOTSTRAP_HTML_URL'], 'st
* requests as the user. However, it is allowed to call any XBlock handlers.
*/
const XBlockContent = ({
- view, type, getHandlerUrl, onBlockNotification,
+ view, type, getHandlerUrl, onBlockNotification, variant,
}) => {
const iframeRef = useRef(null);
const [html, setHtml] = useState(null);
@@ -35,6 +35,7 @@ const XBlockContent = ({
view.resources,
getConfig().STUDIO_BASE_URL,
type,
+ variant,
);
// Load the XBlock HTML into the IFrame:
diff --git a/src/course-unit/course-xblock/xblock-content/iframe-wrapper/iframe-wrapper.js b/src/course-unit/course-xblock/xblock-content/iframe-wrapper/iframe-wrapper.js
index 3eb53cd4f4..f20489b719 100644
--- a/src/course-unit/course-xblock/xblock-content/iframe-wrapper/iframe-wrapper.js
+++ b/src/course-unit/course-xblock/xblock-content/iframe-wrapper/iframe-wrapper.js
@@ -18,7 +18,7 @@ import {
* JS and CSS dependencies properly.
* @param type The XBlock's type (openassessment, discussion, video, etc.)
*/
-export default function wrapBlockHtmlForIFrame(html, sourceResources, studioBaseUrl, type) {
+export default function wrapBlockHtmlForIFrame(html, sourceResources, studioBaseUrl, type, variant) {
const resources = normalizeResources(sourceResources);
/* Extract CSS resources. */
@@ -36,7 +36,7 @@ export default function wrapBlockHtmlForIFrame(html, sourceResources, studioBase
jsTags += scripts.map(script => ``).join('\n');
let legacyIncludes = '';
- if (html.indexOf('wrapper-xblock-message') !== -1) {
+ if (html.indexOf('wrapper-xblock-message') !== -1 || html.indexOf('xmodule_edit') !== -1) {
legacyIncludes += `
@@ -251,6 +251,53 @@ export default function wrapBlockHtmlForIFrame(html, sourceResources, studioBase
modifiedHtml = modifiedHtml.replace('src="/static/studio', `src="${studioBaseUrl}/static/studio`);
modifiedHtml = modifiedHtml.replace('data-target="/preview/xblock', `data-target="${studioBaseUrl}/preview/xblock`);
+ const hasCustomButtons = html.includes('editor-with-buttons');
+ const hasCustomTabs = html.includes('editor-with-tabs');
+ const hasPlugins = html.includes('wrapper-comp-plugins');
+ console.log('TYPE:', type);
+ console.log('VARIANT:', variant);
+ const getDataEditor = html.includes('wrapper-comp-editor');
+
+ const editingModalHeader = `
+ `;
+
+ const defaultModalTitle = `
+ `;
+
+ const getActionBar = `
+ `;
+
if (
type === COMPONENT_TYPES.discussion
|| type === COMPONENT_TYPES.dragAndDrop
@@ -328,5 +375,41 @@ export default function wrapBlockHtmlForIFrame(html, sourceResources, studioBase
`;
}
+ if (variant === 'edit-modal') {
+ result = `
+
+
+
+
+
+
+ ${legacyIncludes}
+ ${cssTags}
+
+
+
+
+
+
+
+ ${!hasCustomTabs && getDataEditor ? editingModalHeader : defaultModalTitle}
+
+ ${html}
+
+ ${!hasCustomButtons ? getActionBar : ''}
+
+
+
+ ${jsTags}
+
+
+
+ `;
+ }
+
return result;
}
diff --git a/src/course-unit/data/api.js b/src/course-unit/data/api.js
index 80c59dab42..afce81a683 100644
--- a/src/course-unit/data/api.js
+++ b/src/course-unit/data/api.js
@@ -13,6 +13,7 @@ export const getCourseSectionVerticalApiUrl = (itemId) => `${getStudioBaseUrl()}
export const getCourseVerticalChildrenApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container/vertical/${itemId}/children`;
export const getClipboardUrl = () => `${getStudioBaseUrl()}/api/content-staging/v1/clipboard/`;
export const getXBlockContainerPreview = (itemId) => `${getStudioBaseUrl()}/xblock/${itemId}/container_preview`;
+export const getXBlockModalDataApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/${itemId}/studio_view`;
export const postXBlockBaseApiUrl = () => `${getStudioBaseUrl()}/xblock/`;
@@ -199,3 +200,10 @@ export async function getXBlockIframeData(itemId) {
return camelCaseObject(data);
}
+
+export async function getXBlockModalData(itemId) {
+ const { data } = await getAuthenticatedHttpClient()
+ .get(getXBlockModalDataApiUrl(itemId));
+
+ return camelCaseObject(data);
+}
diff --git a/src/course-unit/data/slice.js b/src/course-unit/data/slice.js
index a538b4f0d3..7e7e673c4f 100644
--- a/src/course-unit/data/slice.js
+++ b/src/course-unit/data/slice.js
@@ -22,6 +22,7 @@ const slice = createSlice({
clipboardData: null,
staticFileNotices: {},
xblockIframeHtmlAndResources: [],
+ xblockModalData: [],
},
reducers: {
fetchCourseItemSuccess: (state, { payload }) => {
@@ -119,6 +120,9 @@ const slice = createSlice({
fetchXBlockIframeResources: (state, { payload }) => {
state.xblockIframeHtmlAndResources.push(payload);
},
+ fetchXBlockModalData: (state, { payload }) => {
+ state.xblockModalData = payload;
+ },
},
});
@@ -143,6 +147,7 @@ export const {
fetchStaticFileNoticesSuccess,
reorderXBlockList,
fetchXBlockIframeResources,
+ fetchXBlockModalData,
} = slice.actions;
export const {
diff --git a/src/course-unit/data/thunk.js b/src/course-unit/data/thunk.js
index 285d4e115d..c65f3bcca5 100644
--- a/src/course-unit/data/thunk.js
+++ b/src/course-unit/data/thunk.js
@@ -23,6 +23,7 @@ import {
duplicateUnitItem,
setXBlockOrderList,
getXBlockIframeData,
+ getXBlockModalData,
} from './api';
import {
updateLoadingCourseUnitStatus,
@@ -42,7 +43,7 @@ import {
duplicateXBlock,
fetchStaticFileNoticesSuccess,
reorderXBlockList,
- fetchXBlockIframeResources,
+ fetchXBlockIframeResources, fetchXBlockModalData,
} from './slice';
import { getNotificationMessage } from './utils';
@@ -322,3 +323,20 @@ export function fetchXBlockIframeHtmlAndResourcesQuery(xblockId) {
}
};
}
+
+export function fetchXBlockModalDataQuery(xblockId) {
+ return async (dispatch) => {
+ dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
+ dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.adding));
+
+ try {
+ const xblockModalData = await getXBlockModalData(xblockId);
+ dispatch(fetchXBlockModalData({ xblockId, ...xblockModalData }));
+ dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
+ } catch (error) {
+ dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
+ } finally {
+ dispatch(hideProcessingNotification());
+ }
+ };
+}