MDL-67584 core_course: Activity chooser favouriting frontend
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_course/activitychooser",["exports","core_course/local/activitychooser/dialogue","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/custom_interaction_events","core/templates","core/modal_factory","core/str","core/pending"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=k(c);d=j(d);e=j(e);f=k(f);g=k(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function l(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}var n=function(a){var b=new i.default;o(a);b.resolve()};a.init=n;var o=function(a){var f=["click",e.default.events.activate,e.default.events.keyboardActivate],g=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}();e.default.define(document,f);f.forEach(function(a){document.addEventListener(a,function(){var a=m(regeneratorRuntime.mark(function a(c){var e,f,h;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=12;break}e=c.target.closest(d.default.elements.sectionmodchooser);a.t0=p;a.next=5;return g();case 5:a.t1=a.sent;a.t2=e.dataset.sectionid;f=(0,a.t0)(a.t1,a.t2);a.next=10;return q(f);case 10:h=a.sent;b.displayChooser(e,h,f);case 12:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},p=function(a,b){var c=JSON.parse(JSON.stringify(a));c.content_items.forEach(function(a){a.link+="§ion="+b});return c.content_items},q=function(a){return s(r(a))},r=function(a){var b=[],c=a.filter(function(a){return!0===a.recommended}),d=!!b.length,e=!!(c.length&&!1===d);return{default:a,favourites:b,recommended:c,favouritesFirst:d,recommendedFirst:e,fallback:!1===d&&!1===e}},s=function(a){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:f.render("core_course/chooser",a),large:!0,templateContext:{classes:"modchooser"}})}});
|
||||
define ("core_course/activitychooser",["exports","core_course/local/activitychooser/dialogue","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/custom_interaction_events","core/templates","core/modal_factory","core/str","core/pending"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=k(c);d=j(d);e=j(e);f=k(f);g=k(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function l(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}var n=function(a){var b=new i.default;o(a);b.resolve()};a.init=n;var o=function(a){var f=["click",e.default.events.activate,e.default.events.keyboardActivate],g=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}();e.default.define(document,f);f.forEach(function(a){document.addEventListener(a,function(){var a=m(regeneratorRuntime.mark(function a(c){var e,f,h,i,j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=11;break}a.next=3;return g();case 3:e=a.sent;f=c.target.closest(d.default.elements.sectionmodchooser);h=u(e,f.dataset.sectionid);i=p(e,f.dataset.sectionid);a.next=9;return q(i);case 9:j=a.sent;b.displayChooser(f,j,i,h);case 11:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},p=function(a,b){var c=JSON.parse(JSON.stringify(a));c.content_items.forEach(function(a){a.link+="§ion="+b});return c.content_items},q=function(a){return s(r(a))},r=function(a){var b=a.filter(function(a){return!0===a.favourite}),c=a.filter(function(a){return!0===a.recommended}),d=!!b.length,e=!!(c.length&&!1===d);return{default:a,favourites:b,recommended:c,favouritesFirst:d,recommendedFirst:e,fallback:!1===d&&!1===e}},s=function(a){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:f.render("core_course/chooser",a),large:!0,templateContext:{classes:"modchooser"}})},t=function(a,b){a.classList.add("d-none");if(a.classList.contains("active")){a.classList.remove("active");var f=b.querySelector(d.default.regions.favouriteTab);f.classList.remove("active");var c=b.querySelector(d.default.regions.recommendedTabNav),e=b.querySelector(d.default.regions.defaultTabNav);if(!1===c.classList.contains("d-none")){c.classList.add("active");var g=b.querySelector(d.default.regions.recommendedTab);g.classList.add("active")}else{e.classList.add("active");var h=b.querySelector(d.default.regions.defaultTab);h.classList.add("active")}}},u=function(a,b){return function(){var c=m(regeneratorRuntime.mark(function c(e,g,h){var i,j,k,l,m,n,o,q,r,s,u;return regeneratorRuntime.wrap(function(c){while(1){switch(c.prev=c.next){case 0:i=h.querySelector(d.default.render.favourites);j=h.querySelectorAll("[data-internal=\"".concat(e,"\"] ").concat(d.default.actions.optionActions.manageFavourite));k=h.querySelector(d.default.regions.favouriteTabNav);l=a.content_items.find(function(a){var b=a.name;return b===e});m={};if(!l){c.next=27;break}if(!g){c.next=21;break}l.favourite=!0;m.content_items=a.content_items.filter(function(a){return!0===a.favourite});n=p(m,b);c.next=12;return f.renderForPromise("core_course/chooser_favourites",{favourites:n});case 12:o=c.sent;q=o.html;r=o.js;c.next=17;return f.replaceNodeContents(i,q,r);case 17:Array.from(j).forEach(function(a){a.classList.remove("text-muted");a.classList.add("text-primary");a.dataset.favourited="true";a.setAttribute("aria-pressed",!0);a.firstElementChild.classList.remove("fa-star-o");a.firstElementChild.classList.add("fa-star")});k.classList.remove("d-none");c.next=27;break;case 21:l.favourite=!1;s=i.querySelector("[data-internal=\"".concat(e,"\"]"));s.parentNode.removeChild(s);Array.from(j).forEach(function(a){a.classList.add("text-muted");a.classList.remove("text-primary");a.dataset.favourited="false";a.setAttribute("aria-pressed",!1);a.firstElementChild.classList.remove("fa-star");a.firstElementChild.classList.add("fa-star-o")});u=a.content_items.filter(function(a){return!0===a.favourite});if(0===u.length){t(k,h)}case 27:case"end":return c.stop();}}},c)}));return function(){return c.apply(this,arguments)}}()}});
|
||||
//# sourceMappingURL=activitychooser.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_course_content_items",args:{courseid:a}}])[0]};a.activityModules=c});
|
||||
define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.unfavouriteModule=a.favouriteModule=a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_course_content_items",args:{courseid:a}}])[0]};a.activityModules=c;var d=function(a,c){return b.default.call([{methodname:"core_course_add_content_item_to_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.favouriteModule=d;var e=function(a,c){return b.default.call([{methodname:"core_course_remove_content_item_from_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.unfavouriteModule=e});
|
||||
//# sourceMappingURL=repository.min.js.map
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args"],"mappings":"oLAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_course_content_items',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"}
|
||||
{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args","favouriteModule","modName","modID","componentname","contentitemid","unfavouriteModule"],"mappings":"0NAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C,oBAmBA,GAAMK,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAUC,CAAV,CAAoB,CAQ/C,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,iDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C,oBAoBA,GAAMG,CAAAA,CAAiB,CAAG,SAACJ,CAAD,CAAUC,CAAV,CAAoB,CAQjD,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,sDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_course_content_items',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is a users' favourite.\n *\n * @method favouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const favouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_add_content_item_to_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is no longer a users' favourite.\n *\n * @method unfavouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const unfavouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_remove_content_item_from_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"}
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),chooserOptions:b("region","chooser-options-container"),chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")}},actions:{optionActions:{showSummary:b("action","show-option-summary")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic"}};a.default=c;return a.default});
|
||||
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),chooserOptions:b("region","chooser-options-container"),chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")}},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic"}};a.default=c;return a.default});
|
||||
//# sourceMappingURL=selectors.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -78,11 +78,13 @@ const registerListenerEvents = (courseId) => {
|
||||
events.forEach((event) => {
|
||||
document.addEventListener(event, async(e) => {
|
||||
if (e.target.closest(selectors.elements.sectionmodchooser)) {
|
||||
const data = await fetchModuleData();
|
||||
const caller = e.target.closest(selectors.elements.sectionmodchooser);
|
||||
const builtModuleData = sectionIdMapper(await fetchModuleData(), caller.dataset.sectionid);
|
||||
const favouriteFunction = partiallyAppliedFavouriteManager(data, caller.dataset.sectionid);
|
||||
const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);
|
||||
const sectionModal = await modalBuilder(builtModuleData);
|
||||
|
||||
ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData);
|
||||
ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData, favouriteFunction);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -111,7 +113,7 @@ const sectionIdMapper = (webServiceData, id) => {
|
||||
*
|
||||
* @method modalBuilder
|
||||
* @param {Map} data our map of section ID's & modules to generate modals for
|
||||
* @return {Object} TODO
|
||||
* @return {Object} Our modal that we are going to show the user
|
||||
*/
|
||||
const modalBuilder = data => buildModal(templateDataBuilder(data));
|
||||
|
||||
@@ -124,7 +126,7 @@ const modalBuilder = data => buildModal(templateDataBuilder(data));
|
||||
*/
|
||||
const templateDataBuilder = (data) => {
|
||||
// Filter the incoming data to find favourite & recommended modules.
|
||||
const favourites = [];
|
||||
const favourites = data.filter(mod => mod.favourite === true);
|
||||
const recommended = data.filter(mod => mod.recommended === true);
|
||||
|
||||
// Given the results of the above filters lets figure out what tab to set active.
|
||||
@@ -164,3 +166,105 @@ const buildModal = data => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A small helper function to handle the case where there are no more favourites
|
||||
* and we need to mess a bit with the available tabs in the chooser
|
||||
*
|
||||
* @method nullFavouriteDomManager
|
||||
* @param {HTMLElement} favouriteTabNav Dom node of the favourite tab nav
|
||||
* @param {HTMLElement} modalBody Our current modals' body
|
||||
*/
|
||||
const nullFavouriteDomManager = (favouriteTabNav, modalBody) => {
|
||||
favouriteTabNav.classList.add('d-none');
|
||||
// Need to set active to an available tab.
|
||||
if (favouriteTabNav.classList.contains('active')) {
|
||||
favouriteTabNav.classList.remove('active');
|
||||
const favouriteTab = modalBody.querySelector(selectors.regions.favouriteTab);
|
||||
favouriteTab.classList.remove('active');
|
||||
const recommendedTabNav = modalBody.querySelector(selectors.regions.recommendedTabNav);
|
||||
const defaultTabNav = modalBody.querySelector(selectors.regions.defaultTabNav);
|
||||
if (recommendedTabNav.classList.contains('d-none') === false) {
|
||||
recommendedTabNav.classList.add('active');
|
||||
const recommendedTab = modalBody.querySelector(selectors.regions.recommendedTab);
|
||||
recommendedTab.classList.add('active');
|
||||
} else {
|
||||
defaultTabNav.classList.add('active');
|
||||
const defaultTab = modalBody.querySelector(selectors.regions.defaultTab);
|
||||
defaultTab.classList.add('active');
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Export a curried function where the builtModules has been applied.
|
||||
* We have our array of modules so we can rerender the favourites area and have all of the items sorted.
|
||||
*
|
||||
* @method partiallyAppliedFavouriteManager
|
||||
* @param {Array} moduleData This is our raw WS data that we need to manipulate
|
||||
* @param {Number} sectionId We need this to add the sectionID to the URL's in the faves area after rerender
|
||||
* @return {Function} partially applied function so we can manipulate DOM nodes easily & update our internal array
|
||||
*/
|
||||
const partiallyAppliedFavouriteManager = (moduleData, sectionId) => {
|
||||
/**
|
||||
* Curried function that is being returned.
|
||||
*
|
||||
* @param {String} internal Internal name of the module to manage
|
||||
* @param {Boolean} favourite Is the caller adding a favourite or removing one?
|
||||
* @param {HTMLElement} modalBody What we need to update whilst we are here
|
||||
*/
|
||||
return async(internal, favourite, modalBody) => {
|
||||
const favouriteArea = modalBody.querySelector(selectors.render.favourites);
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const favouriteButtons = modalBody.querySelectorAll(`[data-internal="${internal}"] ${selectors.actions.optionActions.manageFavourite}`);
|
||||
const favouriteTabNav = modalBody.querySelector(selectors.regions.favouriteTabNav);
|
||||
const result = moduleData.content_items.find(({name}) => name === internal);
|
||||
const newFaves = {};
|
||||
if (result) {
|
||||
if (favourite) {
|
||||
result.favourite = true;
|
||||
|
||||
newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);
|
||||
|
||||
const builtFaves = sectionIdMapper(newFaves, sectionId);
|
||||
|
||||
const {html, js} = await Templates.renderForPromise('core_course/chooser_favourites', {favourites: builtFaves});
|
||||
|
||||
await Templates.replaceNodeContents(favouriteArea, html, js);
|
||||
|
||||
Array.from(favouriteButtons).forEach((element) => {
|
||||
element.classList.remove('text-muted');
|
||||
element.classList.add('text-primary');
|
||||
element.dataset.favourited = 'true';
|
||||
element.setAttribute('aria-pressed', true);
|
||||
element.firstElementChild.classList.remove('fa-star-o');
|
||||
element.firstElementChild.classList.add('fa-star');
|
||||
});
|
||||
|
||||
favouriteTabNav.classList.remove('d-none');
|
||||
} else {
|
||||
result.favourite = false;
|
||||
|
||||
const nodeToRemove = favouriteArea.querySelector(`[data-internal="${internal}"]`);
|
||||
|
||||
nodeToRemove.parentNode.removeChild(nodeToRemove);
|
||||
|
||||
Array.from(favouriteButtons).forEach((element) => {
|
||||
element.classList.add('text-muted');
|
||||
element.classList.remove('text-primary');
|
||||
element.dataset.favourited = 'false';
|
||||
element.setAttribute('aria-pressed', false);
|
||||
element.firstElementChild.classList.remove('fa-star');
|
||||
element.firstElementChild.classList.add('fa-star-o');
|
||||
});
|
||||
const newFaves = moduleData.content_items.filter(mod => mod.favourite === true);
|
||||
|
||||
if (newFaves.length === 0) {
|
||||
nullFavouriteDomManager(favouriteTabNav, modalBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,6 +28,8 @@ import selectors from 'core_course/local/activitychooser/selectors';
|
||||
import * as Templates from 'core/templates';
|
||||
import {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';
|
||||
import {addIconToContainer} from 'core/loadingicon';
|
||||
import * as Repository from 'core_course/local/activitychooser/repository';
|
||||
import Notification from 'core/notification';
|
||||
|
||||
/**
|
||||
* Given an event from the main module 'page' navigate to it's help section via a carousel.
|
||||
@@ -69,14 +71,42 @@ const showModuleHelp = (carousel, moduleData) => {
|
||||
carousel.carousel('next');
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a user wants to change the favourite state of a module we either add or remove the status.
|
||||
* We also propergate this change across our map of modals.
|
||||
*
|
||||
* @method manageFavouriteState
|
||||
* @param {HTMLElement} modalBody The DOM node of the modal to manipulate
|
||||
* @param {HTMLElement} caller
|
||||
* @param {Function} partialFavourite Partially applied function we need to manage favourite status
|
||||
*/
|
||||
const manageFavouriteState = async(modalBody, caller, partialFavourite) => {
|
||||
const isFavourite = caller.dataset.favourited;
|
||||
const id = caller.dataset.id;
|
||||
const name = caller.dataset.name;
|
||||
const internal = caller.dataset.internal;
|
||||
// Switch on fave or not.
|
||||
if (isFavourite === 'true') {
|
||||
await Repository.unfavouriteModule(name, id);
|
||||
|
||||
partialFavourite(internal, false, modalBody);
|
||||
} else {
|
||||
await Repository.favouriteModule(name, id);
|
||||
|
||||
partialFavourite(internal, true, modalBody);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Register chooser related event listeners.
|
||||
*
|
||||
* @method registerListenerEvents
|
||||
* @param {Promise} modal Our modal that we are working with
|
||||
* @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}
|
||||
* @param {Function} partialFavourite Partially applied function we need to manage favourite status
|
||||
*/
|
||||
const registerListenerEvents = (modal, mappedModules) => {
|
||||
const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
|
||||
const bodyClickListener = e => {
|
||||
if (e.target.closest(selectors.actions.optionActions.showSummary)) {
|
||||
const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));
|
||||
@@ -87,6 +117,11 @@ const registerListenerEvents = (modal, mappedModules) => {
|
||||
showModuleHelp(carousel, moduleData);
|
||||
}
|
||||
|
||||
if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {
|
||||
const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);
|
||||
manageFavouriteState(modal.getBody()[0], caller, partialFavourite);
|
||||
}
|
||||
|
||||
// From the help screen go back to the module overview.
|
||||
if (e.target.matches(selectors.actions.closeOption)) {
|
||||
const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));
|
||||
@@ -273,18 +308,22 @@ const focusChooserOption = (currentChooserOption, previousChooserOption = false)
|
||||
if (previousChooserOption !== false) {
|
||||
const previousChooserOptionLink = previousChooserOption.querySelector(selectors.actions.addChooser);
|
||||
const previousChooserOptionHelp = previousChooserOption.querySelector(selectors.actions.optionActions.showSummary);
|
||||
const previousChooserOptionFavourite = previousChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);
|
||||
// Set tabindex to -1 to remove the previous chooser option element from the focus order.
|
||||
previousChooserOption.tabIndex = -1;
|
||||
previousChooserOptionLink.tabIndex = -1;
|
||||
previousChooserOptionHelp.tabIndex = -1;
|
||||
previousChooserOptionFavourite.tabIndex = -1;
|
||||
}
|
||||
|
||||
const currentChooserOptionLink = currentChooserOption.querySelector(selectors.actions.addChooser);
|
||||
const currentChooserOptionHelp = currentChooserOption.querySelector(selectors.actions.optionActions.showSummary);
|
||||
const currentChooserOptionFavourite = currentChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);
|
||||
// Set tabindex to 0 to add current chooser option element to the focus order.
|
||||
currentChooserOption.tabIndex = 0;
|
||||
currentChooserOptionLink.tabIndex = 0;
|
||||
currentChooserOptionHelp.tabIndex = 0;
|
||||
currentChooserOptionFavourite.tabIndex = 0;
|
||||
// Focus the current chooser option element.
|
||||
currentChooserOption.focus();
|
||||
};
|
||||
@@ -312,8 +351,9 @@ const clickErrorHandler = (item, fallback) => {
|
||||
* @param {HTMLElement} origin The calling button
|
||||
* @param {Object} modal Our created modal for the section
|
||||
* @param {Array} sectionModules An array of all of the built module information
|
||||
* @param {Function} partialFavourite Partially applied function we need to manage favourite status
|
||||
*/
|
||||
export const displayChooser = (origin, modal, sectionModules) => {
|
||||
export const displayChooser = (origin, modal, sectionModules, partialFavourite) => {
|
||||
|
||||
// Make a map so we can quickly fetch a specific module's object for either rendering or searching.
|
||||
const mappedModules = new Map();
|
||||
@@ -322,7 +362,7 @@ export const displayChooser = (origin, modal, sectionModules) => {
|
||||
});
|
||||
|
||||
// Register event listeners.
|
||||
registerListenerEvents(modal, mappedModules);
|
||||
registerListenerEvents(modal, mappedModules, partialFavourite);
|
||||
|
||||
// We want to focus on the action select when the dialog is closed.
|
||||
modal.getRoot().on(ModalEvents.hidden, () => {
|
||||
|
||||
@@ -38,3 +38,43 @@ export const activityModules = (courseid) => {
|
||||
};
|
||||
return ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a module name, module ID & the current course we want to specify that the module
|
||||
* is a users' favourite.
|
||||
*
|
||||
* @method favouriteModule
|
||||
* @param {String} modName Frankenstyle name of the component to add favourite
|
||||
* @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names
|
||||
* @return {object} jQuery promise
|
||||
*/
|
||||
export const favouriteModule = (modName, modID) => {
|
||||
const request = {
|
||||
methodname: 'core_course_add_content_item_to_user_favourites',
|
||||
args: {
|
||||
componentname: modName,
|
||||
contentitemid: modID,
|
||||
},
|
||||
};
|
||||
return ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a module name, module ID & the current course we want to specify that the module
|
||||
* is no longer a users' favourite.
|
||||
*
|
||||
* @method unfavouriteModule
|
||||
* @param {String} modName Frankenstyle name of the component to add favourite
|
||||
* @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names
|
||||
* @return {object} jQuery promise
|
||||
*/
|
||||
export const unfavouriteModule = (modName, modID) => {
|
||||
const request = {
|
||||
methodname: 'core_course_remove_content_item_from_user_favourites',
|
||||
args: {
|
||||
componentname: modName,
|
||||
contentitemid: modID,
|
||||
},
|
||||
};
|
||||
return ajax.call([request])[0];
|
||||
};
|
||||
|
||||
@@ -62,11 +62,15 @@ export default {
|
||||
actions: {
|
||||
optionActions: {
|
||||
showSummary: getDataSelector('action', 'show-option-summary'),
|
||||
manageFavourite: getDataSelector('action', 'manage-module-favourite'),
|
||||
},
|
||||
addChooser: getDataSelector('action', 'add-chooser-option'),
|
||||
closeOption: getDataSelector('action', 'close-chooser-option-summary'),
|
||||
hide: getDataSelector('action', 'hide')
|
||||
},
|
||||
render: {
|
||||
favourites: getDataSelector('render', 'favourites-area'),
|
||||
},
|
||||
elements: {
|
||||
section: '.section',
|
||||
sectionmodchooser: 'button.section-modchooser-link',
|
||||
|
||||
@@ -85,9 +85,7 @@
|
||||
<div class="tab-pane {{#favouritesFirst}}active{{/favouritesFirst}}" id="starred-{{uniqid}}" data-region="favourites" role="tabpanel" aria-labelledby="starred-tab-{{uniqid}}">
|
||||
<div class="modchoosercontainer" data-region="chooser-container" aria-label="{{#str}} activitymodules {{/str}}">
|
||||
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container" data-render="favourites-area">
|
||||
{{#favourites}}
|
||||
{{>core_course/chooser_item}}
|
||||
{{/favourites}}
|
||||
{{>core_course/chooser_favourites}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_course/chooser_favourites
|
||||
|
||||
Chooser favourite template partial.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"favourites": {
|
||||
"label": "Option name",
|
||||
"description": "Option description",
|
||||
"urls": {
|
||||
"addoption": "http://addoptionurl.com"
|
||||
},
|
||||
"icon": "<img class='icon' src='http://urltooptionicon'>"
|
||||
}
|
||||
}
|
||||
}}
|
||||
{{#favourites}}
|
||||
{{>core_course/chooser_item}}
|
||||
{{/favourites}}
|
||||
@@ -29,7 +29,7 @@
|
||||
"icon": "<img class='icon' src='http://urltooptionicon'>"
|
||||
}
|
||||
}}
|
||||
<div role="menuitem" tabindex="-1" aria-label="{{label}}" class="option d-block text-center py-3 px-2" data-region="chooser-option-container" data-modname="{{componentname}}_{{link}}">
|
||||
<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-3 px-2" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
|
||||
<div class="optioninfo w-100" data-region="chooser-option-info-container">
|
||||
<a class="d-block" href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" tabindex="-1" data-action="add-chooser-option">
|
||||
<span class="optionicon d-block">
|
||||
@@ -38,6 +38,30 @@
|
||||
<span class="optionname d-block">{{title}}</span>
|
||||
</a>
|
||||
<div class="optionactions btn-group" role="group" data-region="chooser-option-actions-container">
|
||||
{{^legacyitem}}
|
||||
<button class="btn btn-icon icon-no-margin icon-size-3 m-0 optionaction {{#favourite}}text-primary{{/favourite}}{{^favourite}}text-muted{{/favourite}}"
|
||||
data-action="manage-module-favourite"
|
||||
data-favourited="{{favourite}}"
|
||||
data-id="{{id}}"
|
||||
data-name="{{componentname}}"
|
||||
data-internal="{{name}}"
|
||||
{{^favourite}}
|
||||
aria-pressed="false"
|
||||
{{/favourite}}
|
||||
{{#favourite}}
|
||||
aria-pressed="true"
|
||||
{{/favourite}}
|
||||
aria-label="{{#str}} aria:modulefavourite, core_course, {{title}} {{/str}}"
|
||||
tabindex="-1"
|
||||
>
|
||||
{{#favourite}}
|
||||
{{#pix}} i/star, core {{/pix}}
|
||||
{{/favourite}}
|
||||
{{^favourite}}
|
||||
{{#pix}} i/star-o, core {{/pix}}
|
||||
{{/favourite}}
|
||||
</button>
|
||||
{{/legacyitem}}
|
||||
<button class="btn btn-icon icon-no-margin icon-size-3 m-0 optionaction" data-action="show-option-summary" tabindex="-1">
|
||||
<span aria-hidden="true">{{#pix}} docs, core {{/pix}}</span>
|
||||
<span class="sr-only">{{#str}} informationformodule, core_course, {{title}} {{/str}}</span>
|
||||
|
||||
@@ -68,3 +68,30 @@ Feature: Display and choose from the available activities in course
|
||||
Then I should see "Recommended" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Recommended" "link" in the "Add an activity or resource" "dialogue"
|
||||
And I should see "Book" in the "recommended" "core_course > Activity chooser tab"
|
||||
|
||||
Scenario: Favourite a module in the activity chooser
|
||||
Given I open the activity chooser
|
||||
And I should not see "Starred" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
|
||||
And I should see "Starred" in the "Add an activity or resource" "dialogue"
|
||||
When I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
|
||||
Then I should see "Assignment" in the "favourites" "core_course > Activity chooser tab"
|
||||
And I click on "Information about the Assignment activity" "button" in the "favourites" "core_course > Activity chooser tab"
|
||||
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback."
|
||||
|
||||
Scenario: Add a favourite module and check it exists when reopening the chooser
|
||||
Given I open the activity chooser
|
||||
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Star Forum module" "button" in the "Add an activity or resource" "dialogue"
|
||||
And I should see "Starred" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Close" "button" in the "Add an activity or resource" "dialogue"
|
||||
When I click on "Add an activity or resource" "button" in the "Topic 3" "section"
|
||||
And I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
|
||||
Then I should see "Forum" in the "favourites" "core_course > Activity chooser tab"
|
||||
|
||||
Scenario: Add a favourite and then remove it whilst checking the tabs work as expected
|
||||
Given I open the activity chooser
|
||||
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
|
||||
Then I should not see "Starred" in the "Add an activity or resource" "dialogue"
|
||||
|
||||
@@ -31,6 +31,7 @@ $string['aria:defaulttab'] = 'The default modules';
|
||||
$string['aria:favourite'] = 'Course is starred';
|
||||
$string['aria:favouritestab'] = 'Your starred modules';
|
||||
$string['aria:recommendedtab'] = 'The recommended modules';
|
||||
$string['aria:modulefavourite'] = 'Star {$a} module';
|
||||
$string['coursealreadyfinished'] = 'Course already finished';
|
||||
$string['coursenotyetstarted'] = 'The course has not yet started';
|
||||
$string['coursenotyetfinished'] = 'The course has not yet finished';
|
||||
|
||||
@@ -320,6 +320,7 @@ class icon_system_fontawesome extends icon_system_font {
|
||||
'core:i/show' => 'fa-eye-slash',
|
||||
'core:i/siteevent' => 'fa-globe',
|
||||
'core:i/star' => 'fa-star',
|
||||
'core:i/star-o' => 'fa-star-o',
|
||||
'core:i/star-rating' => 'fa-star',
|
||||
'core:i/stats' => 'fa-line-chart',
|
||||
'core:i/switch' => 'fa-exchange',
|
||||
|
||||
Reference in New Issue
Block a user