From 56b9a753ec5f4eee86a2aaa3cdeb47e348143d8b Mon Sep 17 00:00:00 2001 From: Peter Kulko Date: Mon, 25 Nov 2024 10:45:25 +0200 Subject: [PATCH 1/4] feat: Listen to xBlock interaction events --- cms/static/js/views/container.js | 11 ++ cms/static/js/views/pages/container.js | 137 ++++++++++++++++-- .../sass/course-unit-mfe-iframe-bundle.scss | 5 + 3 files changed, 142 insertions(+), 11 deletions(-) diff --git a/cms/static/js/views/container.js b/cms/static/js/views/container.js index 9370dfdc29d5..7bf3372c6148 100644 --- a/cms/static/js/views/container.js +++ b/cms/static/js/views/container.js @@ -70,6 +70,17 @@ function($, _, XBlockView, ModuleUtils, gettext, StringUtils, NotificationView) newParent = undefined; }, update: function(event, ui) { + try { + window.parent.postMessage( + { + type: 'refreshPositions', + message: 'Refresh positions of all xblocks', + payload: {} + }, document.referrer + ); + } catch (e) { + console.error(e); + } // When dragging from one ol to another, this method // will be called twice (once for each list). ui.sender will // be null if the change is related to the list the element diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index fb8fd2482d4e..495730cded65 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -391,11 +391,12 @@ function($, _, Backbone, gettext, BasePage, editXBlock: function(event, options) { event.preventDefault(); + var isAccessButton = event.currentTarget.className === 'access-button'; try { - if (this.options.isIframeEmbed) { - window.parent.postMessage( + if (this.options.isIframeEmbed && isAccessButton) { + return window.parent.postMessage( { - type: 'editXBlock', + type: 'manageXBlockAccess', payload: {} }, document.referrer ); @@ -416,9 +417,25 @@ function($, _, Backbone, gettext, BasePage, || (useNewVideoEditor === 'True' && blockType === 'video') || (useNewProblemEditor === 'True' && blockType === 'problem') ) { - var destinationUrl = primaryHeader.attr('authoring_MFE_base_url') - + '/' + blockType - + '/' + encodeURI(primaryHeader.attr('data-usage-id')); + var pathToNewXBlockEditor = `/${blockType}/${encodeURI(primaryHeader.attr('data-usage-id'))}`; + var destinationUrl = `${primaryHeader.attr('authoring_MFE_base_url')}${pathToNewXBlockEditor}`; + + try { + if (this.options.isIframeEmbed) { + return window.parent.postMessage( + { + type: 'newXBlockEditor', + message: 'Open the new XBlock editor', + payload: { + url: pathToNewXBlockEditor, + } + }, document.referrer + ); + } + } catch (e) { + console.error(e); + } + var upstreamRef = primaryHeader.attr('data-upstream-ref'); if(upstreamRef) { destinationUrl += '?upstreamLibRef=' + upstreamRef; @@ -548,6 +565,84 @@ function($, _, Backbone, gettext, BasePage, // Code in 'base.js' normally handles toggling these dropdowns but since this one is // not present yet during the domReady event, we have to handle displaying it ourselves. subMenu.classList.toggle('is-shown'); + + if (!subMenu.classList.contains('is-shown') && this.options.isIframeEmbed) { + try { + window.parent.postMessage( + { + type: 'toggleCourseXBlockDropdown', + message: 'Adjust the height of the dropdown menu', + payload: { courseXBlockDropdownHeight: 0 } + }, document.referrer + ); + } catch (error) { + console.error(error); + } + } + + // Calculate the viewport height and the dropdown menu height. + // Check if the dropdown would overflow beyond the iframe height based on the user's click position. + // If the dropdown overflows, adjust its position to display above the click point. + const courseUnitXBlockIframeHeight = window.innerHeight; + const courseXBlockDropdownHeight = subMenu.offsetHeight; + const clickYPosition = event.clientY; + + function sendMessageToParent(type, message, payload) { + try { + if (this.options.isIframeEmbed) { + window.parent.postMessage( + { + type, + message, + payload + }, + document.referrer + ); + } + } catch (e) { + console.error(e); + } + } + + if (courseUnitXBlockIframeHeight < courseXBlockDropdownHeight) { + // If the dropdown menu is taller than the iframe, adjust the height of the dropdown menu. + sendMessageToParent.call( + this, + 'toggleCourseXBlockDropdown', + 'Adjust the height of the dropdown menu', + { courseXBlockDropdownHeight }, + ); + } else if ((courseXBlockDropdownHeight + clickYPosition) > courseUnitXBlockIframeHeight) { + if (courseXBlockDropdownHeight > courseUnitXBlockIframeHeight / 2) { + // If the dropdown menu is taller than half the iframe, send a message to adjust its height. + sendMessageToParent.call( + this, + 'toggleCourseXBlockDropdown', + 'Adjust the height of the dropdown menu', + { courseXBlockDropdownHeight }, + ); + } else { + // Move the dropdown menu upward to prevent it from overflowing out of the viewport. + if (this.options.isIframeEmbed) { + subMenu.style.top = `-${courseXBlockDropdownHeight}px`; + } + } + } + + try { + if (this.options.isIframeEmbed) { + window.parent.postMessage( + { + type: 'currentXBlockId', + payload: { + id: this.findXBlockElement(event.target).data('locator') + } + }, document.referrer + ); + } + } catch (e) { + console.error(e); + } // if propagation is not stopped, the event will bubble up to the // body element, which will close the dropdown. event.stopPropagation(); @@ -590,7 +685,7 @@ function($, _, Backbone, gettext, BasePage, event.preventDefault(); try { if (this.options.isIframeEmbed) { - window.parent.postMessage( + return window.parent.postMessage( { type: 'copyXBlock', payload: {} @@ -647,7 +742,7 @@ function($, _, Backbone, gettext, BasePage, event.preventDefault(); try { if (this.options.isIframeEmbed) { - window.parent.postMessage( + return window.parent.postMessage( { type: 'duplicateXBlock', payload: {} @@ -704,7 +799,7 @@ function($, _, Backbone, gettext, BasePage, event.preventDefault(); try { if (this.options.isIframeEmbed) { - window.parent.postMessage( + return window.parent.postMessage( { type: 'deleteXBlock', payload: {} @@ -868,12 +963,32 @@ function($, _, Backbone, gettext, BasePage, || (useNewProblemEditor === 'True' && blockType.includes('problem'))) ){ var destinationUrl; + var pathToXBlockEditor; if (useVideoGalleryFlow === "True" && blockType.includes("video")) { - destinationUrl = this.$('.xblock-header-primary').attr("authoring_MFE_base_url") + '/course-videos/' + encodeURI(data.locator); + pathToXBlockEditor = `/course-videos/${encodeURI(data.locator)}`; + destinationUrl = `${this.$('.xblock-header-primary').attr("authoring_MFE_base_url")}${pathToXBlockEditor}`; } else { - destinationUrl = this.$('.xblock-header-primary').attr("authoring_MFE_base_url") + '/' + blockType[1] + '/' + encodeURI(data.locator); + pathToXBlockEditor = `/${blockType[1]}/${encodeURI(data.locator)}`; + destinationUrl = `${this.$('.xblock-header-primary').attr("authoring_MFE_base_url")}${pathToXBlockEditor}`; } + + try { + if (this.options.isIframeEmbed) { + return window.parent.postMessage( + { + type: 'newXBlockEditor', + message: 'Sends a message when the new XBlock editor is opened', + payload: { + url: pathToXBlockEditor, + } + }, document.referrer + ); + } + } catch (e) { + console.error(e); + } + window.location.href = destinationUrl; return; } diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index a71882f1c355..3f0e95ef1bc1 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -53,6 +53,7 @@ background-color: $primary; color: $white; border-color: $transparent; + color: $white; } &:focus { @@ -656,3 +657,7 @@ select { .wrapper-comp-setting.metadata-list-enum .action.setting-clear.active { margin-top: 0; } + +.wrapper-xblock .xblock-header-primary .header-actions .wrapper-nav-sub { + z-index: 15; +} From aeab185e2126a7684a9f448c00521e7ff1938ec9 Mon Sep 17 00:00:00 2001 From: Peter Kulko Date: Mon, 25 Nov 2024 11:09:54 +0200 Subject: [PATCH 2/4] refactor: some refactoring --- cms/static/js/views/pages/container.js | 11 ++++++++--- cms/static/sass/course-unit-mfe-iframe-bundle.scss | 2 +- cms/static/sass/partials/cms/theme/_variables-v1.scss | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index 495730cded65..0950ade286da 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -397,6 +397,7 @@ function($, _, Backbone, gettext, BasePage, return window.parent.postMessage( { type: 'manageXBlockAccess', + message: 'Open the manage access modal', payload: {} }, document.referrer ); @@ -634,6 +635,7 @@ function($, _, Backbone, gettext, BasePage, window.parent.postMessage( { type: 'currentXBlockId', + message: 'Send the current XBlock ID to the parent window', payload: { id: this.findXBlockElement(event.target).data('locator') } @@ -688,6 +690,7 @@ function($, _, Backbone, gettext, BasePage, return window.parent.postMessage( { type: 'copyXBlock', + message: 'Copy the XBlock', payload: {} }, document.referrer ); @@ -745,6 +748,7 @@ function($, _, Backbone, gettext, BasePage, return window.parent.postMessage( { type: 'duplicateXBlock', + message: 'Duplicate the XBlock', payload: {} }, document.referrer ); @@ -802,6 +806,7 @@ function($, _, Backbone, gettext, BasePage, return window.parent.postMessage( { type: 'deleteXBlock', + message: 'Delete the XBlock', payload: {} }, document.referrer ); @@ -964,13 +969,13 @@ function($, _, Backbone, gettext, BasePage, ){ var destinationUrl; var pathToXBlockEditor; - if (useVideoGalleryFlow === "True" && blockType.includes("video")) { + if (useVideoGalleryFlow === 'True' && blockType.includes('video')) { pathToXBlockEditor = `/course-videos/${encodeURI(data.locator)}`; - destinationUrl = `${this.$('.xblock-header-primary').attr("authoring_MFE_base_url")}${pathToXBlockEditor}`; + destinationUrl = `${this.$('.xblock-header-primary').attr('authoring_MFE_base_url')}${pathToXBlockEditor}`; } else { pathToXBlockEditor = `/${blockType[1]}/${encodeURI(data.locator)}`; - destinationUrl = `${this.$('.xblock-header-primary').attr("authoring_MFE_base_url")}${pathToXBlockEditor}`; + destinationUrl = `${this.$('.xblock-header-primary').attr('authoring_MFE_base_url')}${pathToXBlockEditor}`; } try { diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index 3f0e95ef1bc1..1dc7ac51c181 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -659,5 +659,5 @@ select { } .wrapper-xblock .xblock-header-primary .header-actions .wrapper-nav-sub { - z-index: 15; + z-index: $zindex-dropdown; } diff --git a/cms/static/sass/partials/cms/theme/_variables-v1.scss b/cms/static/sass/partials/cms/theme/_variables-v1.scss index a008210b25b2..c48d78ba8481 100644 --- a/cms/static/sass/partials/cms/theme/_variables-v1.scss +++ b/cms/static/sass/partials/cms/theme/_variables-v1.scss @@ -313,3 +313,5 @@ $light-background-color: #e1dddb !default; $border-color: #707070 !default; $base-font-size: 18px !default; $dark: #212529; + +$zindex-dropdown: 100; From 6ca10a7cac721c3eb29e3ec9a1fb9d407f1f2dc3 Mon Sep 17 00:00:00 2001 From: Peter Kulko Date: Wed, 27 Nov 2024 21:19:09 +0200 Subject: [PATCH 3/4] refactor: after review --- cms/static/js/views/pages/container.js | 118 ++++++++++--------------- 1 file changed, 45 insertions(+), 73 deletions(-) diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index 0950ade286da..304e3bc92ddf 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -391,14 +391,16 @@ function($, _, Backbone, gettext, BasePage, editXBlock: function(event, options) { event.preventDefault(); - var isAccessButton = event.currentTarget.className === 'access-button'; + const isAccessButton = event.currentTarget.className === 'access-button'; + const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions'); + const usageId = encodeURI(primaryHeader.attr('data-usage-id')); try { if (this.options.isIframeEmbed && isAccessButton) { return window.parent.postMessage( { type: 'manageXBlockAccess', message: 'Open the manage access modal', - payload: {} + payload: { usageId } }, document.referrer ); } @@ -418,8 +420,9 @@ function($, _, Backbone, gettext, BasePage, || (useNewVideoEditor === 'True' && blockType === 'video') || (useNewProblemEditor === 'True' && blockType === 'problem') ) { - var pathToNewXBlockEditor = `/${blockType}/${encodeURI(primaryHeader.attr('data-usage-id'))}`; - var destinationUrl = `${primaryHeader.attr('authoring_MFE_base_url')}${pathToNewXBlockEditor}`; + var destinationUrl = primaryHeader.attr('authoring_MFE_base_url') + + '/' + blockType + + '/' + encodeURI(primaryHeader.attr('data-usage-id')); try { if (this.options.isIframeEmbed) { @@ -428,7 +431,8 @@ function($, _, Backbone, gettext, BasePage, type: 'newXBlockEditor', message: 'Open the new XBlock editor', payload: { - url: pathToNewXBlockEditor, + blockType, + usageId: encodeURI(primaryHeader.attr('data-usage-id')), } }, document.referrer ); @@ -588,40 +592,35 @@ function($, _, Backbone, gettext, BasePage, const courseXBlockDropdownHeight = subMenu.offsetHeight; const clickYPosition = event.clientY; - function sendMessageToParent(type, message, payload) { - try { - if (this.options.isIframeEmbed) { - window.parent.postMessage( - { - type, - message, - payload - }, - document.referrer - ); - } - } catch (e) { - console.error(e); - } - } - if (courseUnitXBlockIframeHeight < courseXBlockDropdownHeight) { // If the dropdown menu is taller than the iframe, adjust the height of the dropdown menu. - sendMessageToParent.call( - this, - 'toggleCourseXBlockDropdown', - 'Adjust the height of the dropdown menu', - { courseXBlockDropdownHeight }, - ); + try { + window.parent.postMessage( + { + type: 'toggleCourseXBlockDropdown', + message: 'Adjust the height of the dropdown menu', + payload: { courseXBlockDropdownHeight }, + }, document.referrer + ); + } catch (error) { + console.error(error); + } } else if ((courseXBlockDropdownHeight + clickYPosition) > courseUnitXBlockIframeHeight) { if (courseXBlockDropdownHeight > courseUnitXBlockIframeHeight / 2) { // If the dropdown menu is taller than half the iframe, send a message to adjust its height. - sendMessageToParent.call( - this, - 'toggleCourseXBlockDropdown', - 'Adjust the height of the dropdown menu', - { courseXBlockDropdownHeight }, - ); + try { + window.parent.postMessage( + { + type: 'toggleCourseXBlockDropdown', + message: 'Adjust the height of the dropdown menu', + payload: { + courseXBlockDropdownHeight: courseXBlockDropdownHeight / 2, + }, + }, document.referrer + ); + } catch (error) { + console.error(error); + } } else { // Move the dropdown menu upward to prevent it from overflowing out of the viewport. if (this.options.isIframeEmbed) { @@ -630,21 +629,6 @@ function($, _, Backbone, gettext, BasePage, } } - try { - if (this.options.isIframeEmbed) { - window.parent.postMessage( - { - type: 'currentXBlockId', - message: 'Send the current XBlock ID to the parent window', - payload: { - id: this.findXBlockElement(event.target).data('locator') - } - }, document.referrer - ); - } - } catch (e) { - console.error(e); - } // if propagation is not stopped, the event will bubble up to the // body element, which will close the dropdown. event.stopPropagation(); @@ -685,13 +669,15 @@ function($, _, Backbone, gettext, BasePage, copyXBlock: function(event) { event.preventDefault(); + const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions'); + const usageId = encodeURI(primaryHeader.attr('data-usage-id')); try { if (this.options.isIframeEmbed) { return window.parent.postMessage( { type: 'copyXBlock', message: 'Copy the XBlock', - payload: {} + payload: { usageId } }, document.referrer ); } @@ -743,13 +729,16 @@ function($, _, Backbone, gettext, BasePage, duplicateXBlock: function(event) { event.preventDefault(); + const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions'); + const blockType = primaryHeader.attr('data-block-type'); + const usageId = encodeURI(primaryHeader.attr('data-usage-id')); try { if (this.options.isIframeEmbed) { return window.parent.postMessage( { type: 'duplicateXBlock', message: 'Duplicate the XBlock', - payload: {} + payload: { blockType, usageId } }, document.referrer ); } @@ -801,13 +790,15 @@ function($, _, Backbone, gettext, BasePage, deleteXBlock: function(event) { event.preventDefault(); + const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions'); + const usageId = encodeURI(primaryHeader.attr('data-usage-id')); try { if (this.options.isIframeEmbed) { return window.parent.postMessage( { type: 'deleteXBlock', message: 'Delete the XBlock', - payload: {} + payload: { usageId } }, document.referrer ); } @@ -968,30 +959,11 @@ function($, _, Backbone, gettext, BasePage, || (useNewProblemEditor === 'True' && blockType.includes('problem'))) ){ var destinationUrl; - var pathToXBlockEditor; if (useVideoGalleryFlow === 'True' && blockType.includes('video')) { - pathToXBlockEditor = `/course-videos/${encodeURI(data.locator)}`; - destinationUrl = `${this.$('.xblock-header-primary').attr('authoring_MFE_base_url')}${pathToXBlockEditor}`; + destinationUrl = this.$('.xblock-header-primary').attr("authoring_MFE_base_url") + '/course-videos/' + encodeURI(data.locator); } else { - pathToXBlockEditor = `/${blockType[1]}/${encodeURI(data.locator)}`; - destinationUrl = `${this.$('.xblock-header-primary').attr('authoring_MFE_base_url')}${pathToXBlockEditor}`; - } - - try { - if (this.options.isIframeEmbed) { - return window.parent.postMessage( - { - type: 'newXBlockEditor', - message: 'Sends a message when the new XBlock editor is opened', - payload: { - url: pathToXBlockEditor, - } - }, document.referrer - ); - } - } catch (e) { - console.error(e); + destinationUrl = this.$('.xblock-header-primary').attr("authoring_MFE_base_url") + '/' + blockType[1] + '/' + encodeURI(data.locator); } window.location.href = destinationUrl; From 023b0d09685099414cdd4aee1fe5c5f5a8e85a71 Mon Sep 17 00:00:00 2001 From: Peter Kulko Date: Wed, 11 Dec 2024 17:07:19 +0200 Subject: [PATCH 4/4] refactor: changed bg color --- cms/static/sass/course-unit-mfe-iframe-bundle.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index 1dc7ac51c181..7176300da114 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -328,6 +328,7 @@ .wrapper-content.wrapper { padding: $baseline / 4; + background-color: #f8f7f6; } .btn-default.action-edit.title-edit-button {