feat: minimal UI for the Problem Bank block

This commit is contained in:
Braden MacDonald
2024-10-19 17:03:13 -07:00
parent 08c8e9d214
commit 9e28ba92ec
10 changed files with 213 additions and 26 deletions

View File

@@ -436,7 +436,7 @@ def get_library_content_picker_url(course_locator) -> str:
content_picker_url = None
if libraries_v2_enabled():
mfe_base_url = get_course_authoring_url(course_locator)
content_picker_url = f'{mfe_base_url}/component-picker'
content_picker_url = f'{mfe_base_url}/component-picker?variant=published'
return content_picker_url

View File

@@ -300,8 +300,9 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
selected_groups_label = _('Access restricted to: {list_of_groups}').format(list_of_groups=selected_groups_label) # lint-amnesty, pylint: disable=line-too-long
course = modulestore().get_course(xblock.location.course_key)
can_edit = context.get('can_edit', True)
can_add = context.get('can_add', True)
# Is this a course or a library?
is_course = xblock.scope_ids.usage_id.context_key.is_course
is_course = xblock.context_key.is_course
tags_count_map = context.get('tags_count_map')
tags_count = 0
if tags_count_map:
@@ -320,7 +321,10 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'is_selected': context.get('is_selected', False),
'selectable': context.get('selectable', False),
'selected_groups_label': selected_groups_label,
'can_add': context.get('can_add', True),
'can_add': can_add,
# Generally speaking, "if you can add, you can delete". One exception is itembank (Problem Bank)
# which has its own separate "add" workflow but uses the normal delete workflow for its child blocks.
'can_delete': can_add or (root_xblock and root_xblock.scope_ids.block_type == "itembank" and can_edit),
'can_move': context.get('can_move', is_course),
'language': getattr(course, 'language', None),
'is_course': is_course,

View File

@@ -1648,6 +1648,9 @@ INSTALLED_APPS = [
'corsheaders',
'openedx.core.djangoapps.cors_csrf',
# Provides the 'django_markup' template library so we can use 'interpolate_html' in django templates
'xss_utils',
# History tables
'simple_history',

View File

@@ -0,0 +1,68 @@
/**
* Provides utilities to open and close the library content picker.
*
*/
define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal'],
function($, _, gettext, BaseModal) {
'use strict';
var SelectV2LibraryContent = BaseModal.extend({
options: $.extend({}, BaseModal.prototype.options, {
modalName: 'add-component-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,
}),
initialize: function() {
BaseModal.prototype.initialize.call(this);
// 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,
}
this.callback(requestData);
this.hide();
}
};
this.messageListener = window.addEventListener("message", handleMessage);
this.cleanupListener = () => { window.removeEventListener("message", handleMessage) };
},
hide: function() {
BaseModal.prototype.hide.call(this);
this.cleanupListener();
},
/**
* Adds the action buttons to the modal.
*/
addActionButtons: function() {
this.addActionButton('cancel', gettext('Cancel'));
},
/**
* Show a component picker modal from library.
* @param contentPickerUrl Url for component picker
* @param callback A function to call with the selected block(s)
*/
showComponentPicker: function(contentPickerUrl, callback) {
this.contentPickerUrl = contentPickerUrl;
this.callback = callback;
this.render();
this.show();
},
getContentHtml: function() {
return `<iframe src="${this.contentPickerUrl}" onload="this.contentWindow.focus()" frameborder="0" style="width: 100%; height: 100%;"/>`;
},
});
return SelectV2LibraryContent;
});

View File

@@ -8,7 +8,8 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/pages/base_page
'js/models/xblock_info', 'js/views/xblock_string_field_editor', 'js/views/xblock_access_editor',
'js/views/pages/container_subviews', 'js/views/unit_outline', 'js/views/utils/xblock_utils',
'common/js/components/views/feedback_notification', 'common/js/components/views/feedback_prompt',
'js/views/utils/tagging_drawer_utils', 'js/utils/module', 'js/views/modals/preview_v2_library_changes'
'js/views/utils/tagging_drawer_utils', 'js/utils/module', 'js/views/modals/preview_v2_library_changes',
'js/views/modals/select_v2_library_content'
],
function($, _, Backbone, gettext, BasePage,
ViewUtils, ContainerView, XBlockView,
@@ -16,7 +17,7 @@ function($, _, Backbone, gettext, BasePage,
XBlockInfo, XBlockStringFieldEditor, XBlockAccessEditor,
ContainerSubviews, UnitOutlineView, XBlockUtils,
NotificationView, PromptView, TaggingDrawerUtils, ModuleUtils,
PreviewLibraryChangesModal) {
PreviewLibraryChangesModal, SelectV2LibraryContent) {
'use strict';
var XBlockContainerPage = BasePage.extend({
@@ -30,6 +31,7 @@ function($, _, Backbone, gettext, BasePage,
'click .move-button': 'showMoveXBlockModal',
'click .delete-button': 'deleteXBlock',
'click .library-sync-button': 'showXBlockLibraryChangesPreview',
'click .problem-bank-v2-add-button': 'showSelectV2LibraryContent',
'click .show-actions-menu-button': 'showXBlockActionsMenu',
'click .new-component-button': 'scrollToNewComponentButtons',
'click .save-button': 'saveSelectedLibraryComponents',
@@ -255,6 +257,7 @@ function($, _, Backbone, gettext, BasePage,
} else {
// The thing in the clipboard can be pasted into this unit:
const detailsPopupEl = this.$(".clipboard-details-popup")[0];
if (!detailsPopupEl) return; // This happens on the Problem Bank container page - no paste button is there anyways
detailsPopupEl.querySelector(".detail-block-name").innerText = data.content.display_name;
detailsPopupEl.querySelector(".detail-block-type").innerText = data.content.block_type_display;
detailsPopupEl.querySelector(".detail-course-name").innerText = data.source_context_title;
@@ -435,6 +438,43 @@ function($, _, Backbone, gettext, BasePage,
});
},
showSelectV2LibraryContent: function(event, options) {
event.preventDefault();
const xblockElement = this.findXBlockElement(event.target);
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';
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); };
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);
placeholderElement.data('locator', addResult.locator);
return this.refreshXBlock(placeholderElement, true);
};
}
// Now we actually add the block:
ViewUtils.runOperationShowingMessage(gettext('Adding'), () => {
return $.postJSON(this.getURLRoot() + '/', createData, doneAddingBlock);
});
});
},
/**
* If the new "Actions" menu is enabled, most XBlock actions like
* Duplicate, Move, Delete, Manage Access, etc. are moved into this

View File

@@ -197,8 +197,7 @@ upstream_info = UpstreamLink.try_get_for_block(xblock)
</li>
% endif
% endif
% if can_add:
<!-- If we can add, we can delete. -->
% if can_delete:
<li class="nav-item">
<a class="delete-button" href="#" role="button">${_("Delete")}</a>
</li>