feat: multi-select when adding blocks to problem bank (#35705)
This implements basic multi-select for adding components to a problem bank, for the Libraries Relaunch Beta [FC-0062]. Part of: https://github.com/openedx/frontend-app-authoring/issues/1385
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
/**
|
||||
* Provides utilities to open and close the library content picker.
|
||||
* This is for adding a single, selected, non-randomized component (XBlock)
|
||||
* from the library into the course. It achieves the same effect as copy-pasting
|
||||
* the block from a library into the course. The block will remain synced with
|
||||
* the "upstream" library version.
|
||||
*
|
||||
* Compare cms/static/js/views/modals/select_v2_library_content.js which uses
|
||||
* a multi-select modal to add component(s) to a Problem Bank (for
|
||||
* randomization).
|
||||
*/
|
||||
define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal'],
|
||||
function($, _, gettext, BaseModal) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* Provides utilities to open and close the library content picker.
|
||||
* This is for adding multiple components to a Problem Bank (for randomization).
|
||||
*
|
||||
* Compare cms/static/js/views/components/add_library_content.js which uses
|
||||
* a single-select modal to add one component to a course (non-randomized).
|
||||
*/
|
||||
define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal'],
|
||||
function($, _, gettext, BaseModal) {
|
||||
@@ -8,26 +11,31 @@ function($, _, gettext, BaseModal) {
|
||||
|
||||
var SelectV2LibraryContent = BaseModal.extend({
|
||||
options: $.extend({}, BaseModal.prototype.options, {
|
||||
modalName: 'add-component-from-library',
|
||||
modalName: 'add-components-from-library',
|
||||
modalSize: 'lg',
|
||||
view: 'studio_view',
|
||||
viewSpecificClasses: 'modal-add-component-picker confirm',
|
||||
// Translators: "title" is the name of the current component being edited.
|
||||
titleFormat: gettext('Add library content'),
|
||||
addPrimaryActionButton: false,
|
||||
}),
|
||||
|
||||
events: {
|
||||
'click .action-add': 'addSelectedComponents',
|
||||
'click .action-cancel': 'cancel',
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
BaseModal.prototype.initialize.call(this);
|
||||
this.selections = [];
|
||||
// Add event listen to close picker when the iframe tells us to
|
||||
const handleMessage = (event) => {
|
||||
if (event.data?.type === 'pickerComponentSelected') {
|
||||
var requestData = {
|
||||
library_content_key: event.data.usageKey,
|
||||
category: event.data.category,
|
||||
if (event.data?.type === 'pickerSelectionChanged') {
|
||||
this.selections = event.data.selections;
|
||||
if (this.selections.length > 0) {
|
||||
this.enableActionButton('add');
|
||||
} else {
|
||||
this.disableActionButton('add');
|
||||
}
|
||||
this.callback(requestData);
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
this.messageListener = window.addEventListener("message", handleMessage);
|
||||
@@ -43,7 +51,19 @@ function($, _, gettext, BaseModal) {
|
||||
* Adds the action buttons to the modal.
|
||||
*/
|
||||
addActionButtons: function() {
|
||||
this.addActionButton('add', gettext('Add selected components'), true);
|
||||
this.addActionButton('cancel', gettext('Cancel'));
|
||||
this.disableActionButton('add');
|
||||
},
|
||||
|
||||
/** Handler when the user clicks the "Add Selected Components" primary button */
|
||||
addSelectedComponents: function(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation(); // Make sure parent modals don't see the click
|
||||
}
|
||||
this.hide();
|
||||
this.callback(this.selections);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -426,6 +426,7 @@ function($, _, Backbone, gettext, BasePage,
|
||||
});
|
||||
},
|
||||
|
||||
/** Show the modal for previewing changes before syncing a library-sourced XBlock. */
|
||||
showXBlockLibraryChangesPreview: function(event, options) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -438,6 +439,7 @@ function($, _, Backbone, gettext, BasePage,
|
||||
});
|
||||
},
|
||||
|
||||
/** Show the multi-select library content picker, for adding to a Problem Bank (itembank) Component */
|
||||
showSelectV2LibraryContent: function(event, options) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -445,32 +447,40 @@ function($, _, Backbone, gettext, BasePage,
|
||||
const modal = new SelectV2LibraryContent(options);
|
||||
const courseAuthoringMfeUrl = this.model.attributes.course_authoring_url;
|
||||
const itemBankBlockId = xblockElement.data("locator");
|
||||
const pickerUrl = courseAuthoringMfeUrl + '/component-picker?variant=published';
|
||||
const pickerUrl = courseAuthoringMfeUrl + '/component-picker/multiple?variant=published';
|
||||
|
||||
modal.showComponentPicker(pickerUrl, (selectedBlockData) => {
|
||||
const createData = {
|
||||
parent_locator: itemBankBlockId,
|
||||
// The user wants to add this block from the library to the Problem Bank:
|
||||
library_content_key: selectedBlockData.library_content_key,
|
||||
category: selectedBlockData.category,
|
||||
};
|
||||
let doneAddingBlock = () => { this.refreshXBlock(xblockElement, false); };
|
||||
modal.showComponentPicker(pickerUrl, (selectedBlocks) => {
|
||||
// selectedBlocks has type: {usageKey: string, blockType: string}[]
|
||||
let doneAddingAllBlocks = () => { this.refreshXBlock(xblockElement, false); };
|
||||
let doneAddingBlock = () => {};
|
||||
if (this.model.id === itemBankBlockId) {
|
||||
// We're on the detailed view, showing all the components inside the problem bank.
|
||||
// Create a placeholder that will become the new block(s)
|
||||
const $placeholderEl = $(this.createPlaceholderElement());
|
||||
const $insertSpot = xblockElement.find('.insert-new-lib-blocks-here');
|
||||
const placeholderElement = $placeholderEl.insertBefore($insertSpot);
|
||||
const scrollOffset = ViewUtils.getScrollOffset($placeholderEl);
|
||||
doneAddingBlock = (addResult) => {
|
||||
ViewUtils.setScrollOffset(placeholderElement, scrollOffset);
|
||||
const $placeholderEl = $(this.createPlaceholderElement());
|
||||
const placeholderElement = $placeholderEl.insertBefore($insertSpot);
|
||||
placeholderElement.data('locator', addResult.locator);
|
||||
return this.refreshXBlock(placeholderElement, true);
|
||||
};
|
||||
doneAddingAllBlocks = () => {};
|
||||
}
|
||||
// Note: adding all the XBlocks in parallel will cause a race condition 😢 so we have to add
|
||||
// them one at a time:
|
||||
let lastAdded = $.when();
|
||||
for (const { usageKey, blockType } of selectedBlocks) {
|
||||
const addData = {
|
||||
library_content_key: usageKey,
|
||||
category: blockType,
|
||||
parent_locator: itemBankBlockId,
|
||||
};
|
||||
lastAdded = lastAdded.then(() => (
|
||||
$.postJSON(this.getURLRoot() + '/', addData, doneAddingBlock)
|
||||
));
|
||||
}
|
||||
// Now we actually add the block:
|
||||
ViewUtils.runOperationShowingMessage(gettext('Adding'), () => {
|
||||
return $.postJSON(this.getURLRoot() + '/', createData, doneAddingBlock);
|
||||
return lastAdded.done(() => { doneAddingAllBlocks() });
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user