From 9fe035a1075d526fc72f4f1c617f2e682d92c327 Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Mon, 22 Sep 2025 15:51:16 +0200 Subject: [PATCH] MDL-86716 courseformat: Remove separator on Activity chooser --- .../local/activitychooser/exporter.min.js | 2 +- .../local/activitychooser/exporter.min.js.map | 2 +- .../amd/src/local/activitychooser/exporter.js | 1 + .../local/activitychooser/help.mustache | 43 ++++++++++--------- .../theme/boost/scss/moodle/modchooser.scss | 2 +- public/theme/boost/style/moodle.css | 2 +- public/theme/classic/style/moodle.css | 2 +- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/public/course/format/amd/build/local/activitychooser/exporter.min.js b/public/course/format/amd/build/local/activitychooser/exporter.min.js index fd41492f818..e30c5a87850 100644 --- a/public/course/format/amd/build/local/activitychooser/exporter.min.js +++ b/public/course/format/amd/build/local/activitychooser/exporter.min.js @@ -6,6 +6,6 @@ define("core_courseformat/local/activitychooser/exporter",["exports","core/str"] * @copyright 2025 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const activityCategories=["administration","assessment","collaboration","communication","content","interactivecontent"];let allStrings=null;loadNecessaryStrings();async function loadNecessaryStrings(){if(null!==allStrings)return allStrings;allStrings={};const stringToLoad=[{key:"all",component:"core"},{key:"yes",component:"core"},{key:"no",component:"core"},{key:"favourites",component:"core"},{key:"recommended",component:"core"},{key:"gradable",component:"core"},{key:"recommended_help",component:"core_course"},...activityCategories.map((key=>({key:"mod_purpose_"+key,component:"core_course"}))),...activityCategories.map((key=>({key:"mod_purpose_"+key+"_help",component:"core_course"})))],loadedStrings=await(0,_str.getStrings)(stringToLoad);return stringToLoad.forEach(((_ref,index)=>{let{key:key}=_ref;allStrings[key]=loadedStrings[index]})),allStrings}return _exports.default=class{getTabData(tabId,filteredModules,tabLabel){let tabHelp=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;const result={tabId:tabId,active:arguments.length>4&&void 0!==arguments[4]&&arguments[4],items:filteredModules,displayed:filteredModules.length>0,tabLabel:tabLabel};return tabHelp&&(result.tabHelp=tabHelp),result}normaliseModulesData(modulesData){if(modulesData instanceof Map)modulesData=Array.from(modulesData.values());else if(!Array.isArray(modulesData))throw new Error("Invalid modules data format. Expected an array or a Map.");return modulesData}async getModChooserTemplateData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteTab=await this.getFavouriteTabData(modulesData),tabs=[{...this.getTabData("all",modulesData,allStrings.all,null,!favouriteTab.displayed),hasSearchResults:!0},favouriteTab,{...this.getTabData("recommended",modulesData.filter((mod=>!0===mod.recommended)),allStrings.recommended,allStrings.recommended_help),separator:!0}];return activityCategories.forEach((category=>{const categoryModules=modulesData.filter((mod=>mod.purpose==category||mod.otherpurpose==category));0!==categoryModules.length&&tabs.push(this.getTabData(category,categoryModules,allStrings["mod_purpose_"+category],allStrings["mod_purpose_"+category+"_help"]))})),{modules:modulesData,tabs:tabs}}async getModuleHelpTemplateData(moduleData){const allStrings=await loadNecessaryStrings(),data={...moduleData,purposes:[]};for(const purposeField of["purpose","otherpurpose"])moduleData[purposeField]&&activityCategories.includes(moduleData[purposeField])&&data.purposes.push({purposename:moduleData[purposeField],purposelabel:allStrings["mod_purpose_".concat(moduleData[purposeField])]});return data.details=[{label:allStrings.gradable,value:moduleData.gradable?allStrings.yes:allStrings.no}],data}async getFavouriteTabData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteModules=modulesData.filter((mod=>!0===mod.favourite));return this.getTabData("favourites",favouriteModules,allStrings.favourites,null,favouriteModules.length>0)}getSearchResultData(searchQuery,resultsModulesData){return{searchresultsnumber:(resultsModulesData=this.normaliseModulesData(resultsModulesData)).length,searchresults:resultsModulesData,hasresults:resultsModulesData.length>0,searchquery:searchQuery}}countTabItems(tabData){var _tabData$items$length,_tabData$items;return null!==(_tabData$items$length=null===(_tabData$items=tabData.items)||void 0===_tabData$items?void 0:_tabData$items.length)&&void 0!==_tabData$items$length?_tabData$items$length:0}getFooterData(footerData){return{activeFooter:footerData.customfootertemplate}}},_exports.default})); +const activityCategories=["administration","assessment","collaboration","communication","content","interactivecontent"];let allStrings=null;loadNecessaryStrings();async function loadNecessaryStrings(){if(null!==allStrings)return allStrings;allStrings={};const stringToLoad=[{key:"all",component:"core"},{key:"yes",component:"core"},{key:"no",component:"core"},{key:"favourites",component:"core"},{key:"recommended",component:"core"},{key:"gradable",component:"core"},{key:"recommended_help",component:"core_course"},...activityCategories.map((key=>({key:"mod_purpose_"+key,component:"core_course"}))),...activityCategories.map((key=>({key:"mod_purpose_"+key+"_help",component:"core_course"})))],loadedStrings=await(0,_str.getStrings)(stringToLoad);return stringToLoad.forEach(((_ref,index)=>{let{key:key}=_ref;allStrings[key]=loadedStrings[index]})),allStrings}return _exports.default=class{getTabData(tabId,filteredModules,tabLabel){let tabHelp=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;const result={tabId:tabId,active:arguments.length>4&&void 0!==arguments[4]&&arguments[4],items:filteredModules,displayed:filteredModules.length>0,tabLabel:tabLabel};return tabHelp&&(result.tabHelp=tabHelp),result}normaliseModulesData(modulesData){if(modulesData instanceof Map)modulesData=Array.from(modulesData.values());else if(!Array.isArray(modulesData))throw new Error("Invalid modules data format. Expected an array or a Map.");return modulesData}async getModChooserTemplateData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteTab=await this.getFavouriteTabData(modulesData),tabs=[{...this.getTabData("all",modulesData,allStrings.all,null,!favouriteTab.displayed),hasSearchResults:!0},favouriteTab,{...this.getTabData("recommended",modulesData.filter((mod=>!0===mod.recommended)),allStrings.recommended,allStrings.recommended_help),separator:!0}];return activityCategories.forEach((category=>{const categoryModules=modulesData.filter((mod=>mod.purpose==category||mod.otherpurpose==category));0!==categoryModules.length&&tabs.push(this.getTabData(category,categoryModules,allStrings["mod_purpose_"+category],allStrings["mod_purpose_"+category+"_help"]))})),{modules:modulesData,tabs:tabs}}async getModuleHelpTemplateData(moduleData){const allStrings=await loadNecessaryStrings(),data={...moduleData,purposes:[]};for(const purposeField of["purpose","otherpurpose"])moduleData[purposeField]&&activityCategories.includes(moduleData[purposeField])&&data.purposes.push({purposename:moduleData[purposeField],purposelabel:allStrings["mod_purpose_".concat(moduleData[purposeField])]});return data.haspurposes=data.purposes.length>0,data.details=[{label:allStrings.gradable,value:moduleData.gradable?allStrings.yes:allStrings.no}],data}async getFavouriteTabData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteModules=modulesData.filter((mod=>!0===mod.favourite));return this.getTabData("favourites",favouriteModules,allStrings.favourites,null,favouriteModules.length>0)}getSearchResultData(searchQuery,resultsModulesData){return{searchresultsnumber:(resultsModulesData=this.normaliseModulesData(resultsModulesData)).length,searchresults:resultsModulesData,hasresults:resultsModulesData.length>0,searchquery:searchQuery}}countTabItems(tabData){var _tabData$items$length,_tabData$items;return null!==(_tabData$items$length=null===(_tabData$items=tabData.items)||void 0===_tabData$items?void 0:_tabData$items.length)&&void 0!==_tabData$items$length?_tabData$items$length:0}getFooterData(footerData){return{activeFooter:footerData.customfootertemplate}}},_exports.default})); //# sourceMappingURL=exporter.min.js.map \ No newline at end of file diff --git a/public/course/format/amd/build/local/activitychooser/exporter.min.js.map b/public/course/format/amd/build/local/activitychooser/exporter.min.js.map index 4033e6a2b99..8293c0774e1 100644 --- a/public/course/format/amd/build/local/activitychooser/exporter.min.js.map +++ b/public/course/format/amd/build/local/activitychooser/exporter.min.js.map @@ -1 +1 @@ -{"version":3,"file":"exporter.min.js","sources":["../../../src/local/activitychooser/exporter.js"],"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 .\n\n/**\n * Module to generate template data for the activity chooser.\n *\n * @module core_courseformat/local/activitychooser/exporter\n * @copyright 2025 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getStrings} from 'core/str';\n\nconst activityCategories = [\n 'administration',\n 'assessment',\n 'collaboration',\n 'communication',\n 'content',\n 'interactivecontent',\n];\n\nlet allStrings = null;\n\nloadNecessaryStrings();\n\nexport default class {\n /**\n * A tab data structure.\n *\n * @typedef {object} TabData\n * @property {String} tabId the tab ID\n * @property {Boolean} active whether the tab is active or not\n * @property {Array} items the filtered modules to be displayed in the tab\n * @property {Boolean} displayed whether the tab is displayed or not\n * @property {String} tabLabel the tab label\n * @property {String|null} tabHelp the help text for the tab (optional)\n */\n\n /**\n * @typedef {Object} ModuleHelpData\n * @property {String} name The name of the module.\n * @property {String} description The description of the module.\n * @property {Array} purposes The purposes of the module.\n * @property {Array} details Additional details about the module.\n */\n\n /**\n * @typedef {Object} ModulePurposeData\n * @property {String} purposename The name of the purpose.\n * @property {String} purposelabel The label of the purpose.\n */\n\n /**\n * Generate a tab data object for the activity chooser.\n *\n * @private\n * @param {String} tabId Tab ID.\n * @param {Array} filteredModules Filtered modules to be displayed in the tab.\n * @param {String} tabLabel Tab label.\n * @param {String|null} tabHelp Help text for the tab (optional).\n * @param {Boolean} active Whether the tab is active or not.\n * @return {TabData} Tab data object.\n */\n getTabData(tabId, filteredModules, tabLabel, tabHelp = null, active = false) {\n const result = {\n tabId: tabId,\n active: active,\n items: filteredModules,\n displayed: filteredModules.length > 0,\n tabLabel,\n };\n if (tabHelp) {\n result.tabHelp = tabHelp;\n }\n return result;\n }\n\n /**\n * Normalise the modules data to be used in the chooser.\n *\n * The modulesData can be a plain array or a Map. This method will convert it to a\n * plain array of objects.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Array} Normalised modules data.\n */\n normaliseModulesData(modulesData) {\n if (modulesData instanceof Map) {\n modulesData = Array.from(modulesData.values());\n } else if (!Array.isArray(modulesData)) {\n throw new Error('Invalid modules data format. Expected an array or a Map.');\n }\n return modulesData;\n }\n\n /**\n * Fetch the chooser template data for a specific section.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getModChooserTemplateData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n const favouriteTab = await this.getFavouriteTabData(modulesData);\n\n const tabs = [\n {\n ...this.getTabData(\n 'all',\n modulesData,\n allStrings.all,\n null,\n !favouriteTab.displayed,\n ),\n hasSearchResults: true, // The all tab will also show search results.\n },\n favouriteTab,\n {\n ...this.getTabData(\n 'recommended',\n modulesData.filter(mod => mod.recommended === true),\n allStrings.recommended,\n allStrings.recommended_help\n ),\n separator: true, // Add a separator before the purpose categories.\n },\n ];\n\n activityCategories.forEach((category) => {\n const categoryModules = modulesData.filter(mod => mod.purpose == category || mod.otherpurpose == category);\n if (categoryModules.length === 0) {\n return;\n }\n tabs.push(\n this.getTabData(\n category,\n categoryModules,\n allStrings['mod_purpose_' + category],\n allStrings['mod_purpose_' + category + '_help']\n )\n );\n });\n\n return {\n modules: modulesData,\n tabs,\n };\n }\n\n /**\n * Get the module help template data.\n *\n * @param {Object} moduleData Data of the module to get help for.\n * @return {Promise} Promise resolved with the module help data.\n */\n async getModuleHelpTemplateData(moduleData) {\n const allStrings = await loadNecessaryStrings();\n const data = {\n ...moduleData,\n purposes: [],\n };\n // Add purpose information from all related fields.\n for (const purposeField of ['purpose', 'otherpurpose']) {\n if (\n !moduleData[purposeField]\n || !activityCategories.includes(moduleData[purposeField])\n ) {\n continue;\n }\n data.purposes.push({\n purposename: moduleData[purposeField],\n purposelabel: allStrings[`mod_purpose_${moduleData[purposeField]}`],\n });\n }\n // The rest of details are displayed as a simpler list.\n data.details = [\n {\n label: allStrings['gradable'],\n value: moduleData.gradable ? allStrings['yes'] : allStrings['no'],\n },\n ];\n return data;\n }\n\n /**\n * Get the favourite tab data.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getFavouriteTabData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n\n // We need to deconstruct the modules data to ensure it is an array.\n const favouriteModules = modulesData.filter(\n mod => {\n return mod.favourite === true;\n }\n );\n\n return this.getTabData(\n 'favourites',\n favouriteModules,\n allStrings.favourites,\n null,\n favouriteModules.length > 0,\n );\n }\n\n /**\n * Get the search result template data.\n *\n * @param {String} searchQuery The search query string.\n * @param {Array|Map} resultsModulesData Modules data to be used in the chooser.\n * @return {Object} The template data.\n */\n getSearchResultData(searchQuery, resultsModulesData) {\n resultsModulesData = this.normaliseModulesData(resultsModulesData);\n return {\n 'searchresultsnumber': resultsModulesData.length,\n 'searchresults': resultsModulesData,\n 'hasresults': resultsModulesData.length > 0,\n 'searchquery': searchQuery,\n };\n }\n\n /**\n * Get the number of items in a tab.\n *\n * @param {TabData} tabData The tab data.\n * @return {Number} The number of items in the tab.\n */\n countTabItems(tabData) {\n return tabData.items?.length ?? 0;\n }\n\n /**\n * Get the activity chooser footer template data.\n * @param {Object} footerData The active footer data object.\n * @return {Object} The template data.\n */\n getFooterData(footerData) {\n return {\n 'activeFooter': footerData.customfootertemplate,\n };\n }\n}\n\n/**\n * Load the necessary strings for the activity chooser.\n *\n * @return {Promise} Promise resolved with the loaded strings.\n */\nasync function loadNecessaryStrings() {\n if (allStrings !== null) {\n return allStrings;\n }\n allStrings = {};\n\n const stringToLoad = [\n {key: 'all', component: 'core'},\n {key: 'yes', component: 'core'},\n {key: 'no', component: 'core'},\n {key: 'favourites', component: 'core'},\n {key: 'recommended', component: 'core'},\n {key: 'gradable', component: 'core'},\n {key: 'recommended_help', component: 'core_course'},\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key,\n component: 'core_course',\n })\n ),\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key + '_help',\n component: 'core_course',\n })\n ),\n ];\n\n const loadedStrings = await getStrings(stringToLoad);\n stringToLoad.forEach(({key}, index) => {\n allStrings[key] = loadedStrings[index];\n });\n return allStrings;\n}\n"],"names":["activityCategories","allStrings","loadNecessaryStrings","stringToLoad","key","component","map","loadedStrings","forEach","index","getTabData","tabId","filteredModules","tabLabel","tabHelp","result","active","items","displayed","length","normaliseModulesData","modulesData","Map","Array","from","values","isArray","Error","this","favouriteTab","getFavouriteTabData","tabs","all","hasSearchResults","filter","mod","recommended","recommended_help","separator","category","categoryModules","purpose","otherpurpose","push","modules","moduleData","data","purposes","purposeField","includes","purposename","purposelabel","details","label","value","gradable","favouriteModules","favourite","favourites","getSearchResultData","searchQuery","resultsModulesData","countTabItems","tabData","_tabData$items","getFooterData","footerData","customfootertemplate"],"mappings":";;;;;;;;MAyBMA,mBAAqB,CACvB,iBACA,aACA,gBACA,gBACA,UACA,0BAGAC,WAAa,KAEjBC,sCAwOeA,0BACQ,OAAfD,kBACOA,WAEXA,WAAa,SAEPE,aAAe,CACjB,CAACC,IAAK,MAAOC,UAAW,QACxB,CAACD,IAAK,MAAOC,UAAW,QACxB,CAACD,IAAK,KAAMC,UAAW,QACvB,CAACD,IAAK,aAAcC,UAAW,QAC/B,CAACD,IAAK,cAAeC,UAAW,QAChC,CAACD,IAAK,WAAYC,UAAW,QAC7B,CAACD,IAAK,mBAAoBC,UAAW,kBAClCL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IACtBC,UAAW,qBAGhBL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IAAM,QAC5BC,UAAW,mBAKjBE,oBAAsB,mBAAWJ,qBACvCA,aAAaK,SAAQ,MAAQC,aAAPL,IAACA,UACnBH,WAAWG,KAAOG,cAAcE,UAE7BR,yCAhOPS,WAAWC,MAAOC,gBAAiBC,cAAUC,+DAAU,WAC7CC,OAAS,CACXJ,MAAOA,MACPK,+DACAC,MAAOL,gBACPM,UAAWN,gBAAgBO,OAAS,EACpCN,SAAAA,iBAEAC,UACAC,OAAOD,QAAUA,SAEdC,OAYXK,qBAAqBC,gBACbA,uBAAuBC,IACvBD,YAAcE,MAAMC,KAAKH,YAAYI,eAClC,IAAKF,MAAMG,QAAQL,mBAChB,IAAIM,MAAM,mEAEbN,4CASqBA,aAC5BA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBACnB2B,mBAAqBD,KAAKE,oBAAoBT,aAE9CU,KAAO,CACT,IACOH,KAAKlB,WACJ,MACAW,YACApB,WAAW+B,IACX,MACCH,aAAaX,WAElBe,kBAAkB,GAEtBJ,aACA,IACOD,KAAKlB,WACJ,cACAW,YAAYa,QAAOC,MAA2B,IAApBA,IAAIC,cAC9BnC,WAAWmC,YACXnC,WAAWoC,kBAEfC,WAAW,WAInBtC,mBAAmBQ,SAAS+B,iBAClBC,gBAAkBnB,YAAYa,QAAOC,KAAOA,IAAIM,SAAWF,UAAYJ,IAAIO,cAAgBH,WAClE,IAA3BC,gBAAgBrB,QAGpBY,KAAKY,KACDf,KAAKlB,WACD6B,SACAC,gBACAvC,WAAW,eAAiBsC,UAC5BtC,WAAW,eAAiBsC,SAAW,cAK5C,CACHK,QAASvB,YACTU,KAAAA,sCAUwBc,kBACtB5C,iBAAmBC,uBACnB4C,KAAO,IACND,WACHE,SAAU,QAGT,MAAMC,eAAgB,CAAC,UAAW,gBAE9BH,WAAWG,eACRhD,mBAAmBiD,SAASJ,WAAWG,gBAI/CF,KAAKC,SAASJ,KAAK,CACfO,YAAaL,WAAWG,cACxBG,aAAclD,iCAA0B4C,WAAWG,yBAI3DF,KAAKM,QAAU,CACX,CACIC,MAAOpD,WAAU,SACjBqD,MAAOT,WAAWU,SAAWtD,WAAU,IAAUA,WAAU,KAG5D6C,+BASezB,aACtBA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBAGnBsD,iBAAmBnC,YAAYa,QACjCC,MAC6B,IAAlBA,IAAIsB,mBAIZ7B,KAAKlB,WACR,aACA8C,iBACAvD,WAAWyD,WACX,KACAF,iBAAiBrC,OAAS,GAWlCwC,oBAAoBC,YAAaC,0BAEtB,sBADPA,mBAAqBjC,KAAKR,qBAAqByC,qBAED1C,qBACzB0C,8BACHA,mBAAmB1C,OAAS,cAC3ByC,aAUvBE,cAAcC,8GACHA,QAAQ9C,uCAAR+C,eAAe7C,8DAAU,EAQpC8C,cAAcC,kBACH,cACaA,WAAWC"} \ No newline at end of file +{"version":3,"file":"exporter.min.js","sources":["../../../src/local/activitychooser/exporter.js"],"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 .\n\n/**\n * Module to generate template data for the activity chooser.\n *\n * @module core_courseformat/local/activitychooser/exporter\n * @copyright 2025 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getStrings} from 'core/str';\n\nconst activityCategories = [\n 'administration',\n 'assessment',\n 'collaboration',\n 'communication',\n 'content',\n 'interactivecontent',\n];\n\nlet allStrings = null;\n\nloadNecessaryStrings();\n\nexport default class {\n /**\n * A tab data structure.\n *\n * @typedef {object} TabData\n * @property {String} tabId the tab ID\n * @property {Boolean} active whether the tab is active or not\n * @property {Array} items the filtered modules to be displayed in the tab\n * @property {Boolean} displayed whether the tab is displayed or not\n * @property {String} tabLabel the tab label\n * @property {String|null} tabHelp the help text for the tab (optional)\n */\n\n /**\n * @typedef {Object} ModuleHelpData\n * @property {String} name The name of the module.\n * @property {String} description The description of the module.\n * @property {Array} purposes The purposes of the module.\n * @property {Array} details Additional details about the module.\n */\n\n /**\n * @typedef {Object} ModulePurposeData\n * @property {String} purposename The name of the purpose.\n * @property {String} purposelabel The label of the purpose.\n */\n\n /**\n * Generate a tab data object for the activity chooser.\n *\n * @private\n * @param {String} tabId Tab ID.\n * @param {Array} filteredModules Filtered modules to be displayed in the tab.\n * @param {String} tabLabel Tab label.\n * @param {String|null} tabHelp Help text for the tab (optional).\n * @param {Boolean} active Whether the tab is active or not.\n * @return {TabData} Tab data object.\n */\n getTabData(tabId, filteredModules, tabLabel, tabHelp = null, active = false) {\n const result = {\n tabId: tabId,\n active: active,\n items: filteredModules,\n displayed: filteredModules.length > 0,\n tabLabel,\n };\n if (tabHelp) {\n result.tabHelp = tabHelp;\n }\n return result;\n }\n\n /**\n * Normalise the modules data to be used in the chooser.\n *\n * The modulesData can be a plain array or a Map. This method will convert it to a\n * plain array of objects.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Array} Normalised modules data.\n */\n normaliseModulesData(modulesData) {\n if (modulesData instanceof Map) {\n modulesData = Array.from(modulesData.values());\n } else if (!Array.isArray(modulesData)) {\n throw new Error('Invalid modules data format. Expected an array or a Map.');\n }\n return modulesData;\n }\n\n /**\n * Fetch the chooser template data for a specific section.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getModChooserTemplateData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n const favouriteTab = await this.getFavouriteTabData(modulesData);\n\n const tabs = [\n {\n ...this.getTabData(\n 'all',\n modulesData,\n allStrings.all,\n null,\n !favouriteTab.displayed,\n ),\n hasSearchResults: true, // The all tab will also show search results.\n },\n favouriteTab,\n {\n ...this.getTabData(\n 'recommended',\n modulesData.filter(mod => mod.recommended === true),\n allStrings.recommended,\n allStrings.recommended_help\n ),\n separator: true, // Add a separator before the purpose categories.\n },\n ];\n\n activityCategories.forEach((category) => {\n const categoryModules = modulesData.filter(mod => mod.purpose == category || mod.otherpurpose == category);\n if (categoryModules.length === 0) {\n return;\n }\n tabs.push(\n this.getTabData(\n category,\n categoryModules,\n allStrings['mod_purpose_' + category],\n allStrings['mod_purpose_' + category + '_help']\n )\n );\n });\n\n return {\n modules: modulesData,\n tabs,\n };\n }\n\n /**\n * Get the module help template data.\n *\n * @param {Object} moduleData Data of the module to get help for.\n * @return {Promise} Promise resolved with the module help data.\n */\n async getModuleHelpTemplateData(moduleData) {\n const allStrings = await loadNecessaryStrings();\n const data = {\n ...moduleData,\n purposes: [],\n };\n // Add purpose information from all related fields.\n for (const purposeField of ['purpose', 'otherpurpose']) {\n if (\n !moduleData[purposeField]\n || !activityCategories.includes(moduleData[purposeField])\n ) {\n continue;\n }\n data.purposes.push({\n purposename: moduleData[purposeField],\n purposelabel: allStrings[`mod_purpose_${moduleData[purposeField]}`],\n });\n }\n data.haspurposes = data.purposes.length > 0;\n // The rest of details are displayed as a simpler list.\n data.details = [\n {\n label: allStrings['gradable'],\n value: moduleData.gradable ? allStrings['yes'] : allStrings['no'],\n },\n ];\n return data;\n }\n\n /**\n * Get the favourite tab data.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getFavouriteTabData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n\n // We need to deconstruct the modules data to ensure it is an array.\n const favouriteModules = modulesData.filter(\n mod => {\n return mod.favourite === true;\n }\n );\n\n return this.getTabData(\n 'favourites',\n favouriteModules,\n allStrings.favourites,\n null,\n favouriteModules.length > 0,\n );\n }\n\n /**\n * Get the search result template data.\n *\n * @param {String} searchQuery The search query string.\n * @param {Array|Map} resultsModulesData Modules data to be used in the chooser.\n * @return {Object} The template data.\n */\n getSearchResultData(searchQuery, resultsModulesData) {\n resultsModulesData = this.normaliseModulesData(resultsModulesData);\n return {\n 'searchresultsnumber': resultsModulesData.length,\n 'searchresults': resultsModulesData,\n 'hasresults': resultsModulesData.length > 0,\n 'searchquery': searchQuery,\n };\n }\n\n /**\n * Get the number of items in a tab.\n *\n * @param {TabData} tabData The tab data.\n * @return {Number} The number of items in the tab.\n */\n countTabItems(tabData) {\n return tabData.items?.length ?? 0;\n }\n\n /**\n * Get the activity chooser footer template data.\n * @param {Object} footerData The active footer data object.\n * @return {Object} The template data.\n */\n getFooterData(footerData) {\n return {\n 'activeFooter': footerData.customfootertemplate,\n };\n }\n}\n\n/**\n * Load the necessary strings for the activity chooser.\n *\n * @return {Promise} Promise resolved with the loaded strings.\n */\nasync function loadNecessaryStrings() {\n if (allStrings !== null) {\n return allStrings;\n }\n allStrings = {};\n\n const stringToLoad = [\n {key: 'all', component: 'core'},\n {key: 'yes', component: 'core'},\n {key: 'no', component: 'core'},\n {key: 'favourites', component: 'core'},\n {key: 'recommended', component: 'core'},\n {key: 'gradable', component: 'core'},\n {key: 'recommended_help', component: 'core_course'},\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key,\n component: 'core_course',\n })\n ),\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key + '_help',\n component: 'core_course',\n })\n ),\n ];\n\n const loadedStrings = await getStrings(stringToLoad);\n stringToLoad.forEach(({key}, index) => {\n allStrings[key] = loadedStrings[index];\n });\n return allStrings;\n}\n"],"names":["activityCategories","allStrings","loadNecessaryStrings","stringToLoad","key","component","map","loadedStrings","forEach","index","getTabData","tabId","filteredModules","tabLabel","tabHelp","result","active","items","displayed","length","normaliseModulesData","modulesData","Map","Array","from","values","isArray","Error","this","favouriteTab","getFavouriteTabData","tabs","all","hasSearchResults","filter","mod","recommended","recommended_help","separator","category","categoryModules","purpose","otherpurpose","push","modules","moduleData","data","purposes","purposeField","includes","purposename","purposelabel","haspurposes","details","label","value","gradable","favouriteModules","favourite","favourites","getSearchResultData","searchQuery","resultsModulesData","countTabItems","tabData","_tabData$items","getFooterData","footerData","customfootertemplate"],"mappings":";;;;;;;;MAyBMA,mBAAqB,CACvB,iBACA,aACA,gBACA,gBACA,UACA,0BAGAC,WAAa,KAEjBC,sCAyOeA,0BACQ,OAAfD,kBACOA,WAEXA,WAAa,SAEPE,aAAe,CACjB,CAACC,IAAK,MAAOC,UAAW,QACxB,CAACD,IAAK,MAAOC,UAAW,QACxB,CAACD,IAAK,KAAMC,UAAW,QACvB,CAACD,IAAK,aAAcC,UAAW,QAC/B,CAACD,IAAK,cAAeC,UAAW,QAChC,CAACD,IAAK,WAAYC,UAAW,QAC7B,CAACD,IAAK,mBAAoBC,UAAW,kBAClCL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IACtBC,UAAW,qBAGhBL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IAAM,QAC5BC,UAAW,mBAKjBE,oBAAsB,mBAAWJ,qBACvCA,aAAaK,SAAQ,MAAQC,aAAPL,IAACA,UACnBH,WAAWG,KAAOG,cAAcE,UAE7BR,yCAjOPS,WAAWC,MAAOC,gBAAiBC,cAAUC,+DAAU,WAC7CC,OAAS,CACXJ,MAAOA,MACPK,+DACAC,MAAOL,gBACPM,UAAWN,gBAAgBO,OAAS,EACpCN,SAAAA,iBAEAC,UACAC,OAAOD,QAAUA,SAEdC,OAYXK,qBAAqBC,gBACbA,uBAAuBC,IACvBD,YAAcE,MAAMC,KAAKH,YAAYI,eAClC,IAAKF,MAAMG,QAAQL,mBAChB,IAAIM,MAAM,mEAEbN,4CASqBA,aAC5BA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBACnB2B,mBAAqBD,KAAKE,oBAAoBT,aAE9CU,KAAO,CACT,IACOH,KAAKlB,WACJ,MACAW,YACApB,WAAW+B,IACX,MACCH,aAAaX,WAElBe,kBAAkB,GAEtBJ,aACA,IACOD,KAAKlB,WACJ,cACAW,YAAYa,QAAOC,MAA2B,IAApBA,IAAIC,cAC9BnC,WAAWmC,YACXnC,WAAWoC,kBAEfC,WAAW,WAInBtC,mBAAmBQ,SAAS+B,iBAClBC,gBAAkBnB,YAAYa,QAAOC,KAAOA,IAAIM,SAAWF,UAAYJ,IAAIO,cAAgBH,WAClE,IAA3BC,gBAAgBrB,QAGpBY,KAAKY,KACDf,KAAKlB,WACD6B,SACAC,gBACAvC,WAAW,eAAiBsC,UAC5BtC,WAAW,eAAiBsC,SAAW,cAK5C,CACHK,QAASvB,YACTU,KAAAA,sCAUwBc,kBACtB5C,iBAAmBC,uBACnB4C,KAAO,IACND,WACHE,SAAU,QAGT,MAAMC,eAAgB,CAAC,UAAW,gBAE9BH,WAAWG,eACRhD,mBAAmBiD,SAASJ,WAAWG,gBAI/CF,KAAKC,SAASJ,KAAK,CACfO,YAAaL,WAAWG,cACxBG,aAAclD,iCAA0B4C,WAAWG,yBAG3DF,KAAKM,YAAcN,KAAKC,SAAS5B,OAAS,EAE1C2B,KAAKO,QAAU,CACX,CACIC,MAAOrD,WAAU,SACjBsD,MAAOV,WAAWW,SAAWvD,WAAU,IAAUA,WAAU,KAG5D6C,+BASezB,aACtBA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBAGnBuD,iBAAmBpC,YAAYa,QACjCC,MAC6B,IAAlBA,IAAIuB,mBAIZ9B,KAAKlB,WACR,aACA+C,iBACAxD,WAAW0D,WACX,KACAF,iBAAiBtC,OAAS,GAWlCyC,oBAAoBC,YAAaC,0BAEtB,sBADPA,mBAAqBlC,KAAKR,qBAAqB0C,qBAED3C,qBACzB2C,8BACHA,mBAAmB3C,OAAS,cAC3B0C,aAUvBE,cAAcC,8GACHA,QAAQ/C,uCAARgD,eAAe9C,8DAAU,EAQpC+C,cAAcC,kBACH,cACaA,WAAWC"} \ No newline at end of file diff --git a/public/course/format/amd/src/local/activitychooser/exporter.js b/public/course/format/amd/src/local/activitychooser/exporter.js index a835063c3a4..befc5f3ceca 100644 --- a/public/course/format/amd/src/local/activitychooser/exporter.js +++ b/public/course/format/amd/src/local/activitychooser/exporter.js @@ -186,6 +186,7 @@ export default class { purposelabel: allStrings[`mod_purpose_${moduleData[purposeField]}`], }); } + data.haspurposes = data.purposes.length > 0; // The rest of details are displayed as a simpler list. data.details = [ { diff --git a/public/course/format/templates/local/activitychooser/help.mustache b/public/course/format/templates/local/activitychooser/help.mustache index 69f9034fa7f..fed53482101 100644 --- a/public/course/format/templates/local/activitychooser/help.mustache +++ b/public/course/format/templates/local/activitychooser/help.mustache @@ -31,7 +31,8 @@ "componentname": "mod_assign", "favourite": 1, "purpose": "collaboration", - "categories": "Collaboration, Communication" + "categories": "Collaboration, Communication", + "haspurposes": true } }}
- {{! Purposes panel. }} -
-

- {{#str}} supports, course{{/str}} -

- -
+ {{#haspurposes}} + {{! Purposes panel. }} +
+

+ {{#str}} supports, course{{/str}} +

+ +
+ {{/haspurposes}} {{#details}}
{{! Detail name and value. }} diff --git a/public/theme/boost/scss/moodle/modchooser.scss b/public/theme/boost/scss/moodle/modchooser.scss index 91827ae5f72..199458ed08e 100644 --- a/public/theme/boost/scss/moodle/modchooser.scss +++ b/public/theme/boost/scss/moodle/modchooser.scss @@ -230,7 +230,7 @@ .activitychooser-option-details { min-width: 15rem; - .modchooser-detail:not(:last-child) { + .modchooser-detail:not(:last-child):not(:only-child) { margin-bottom: map-get($spacers, 4); padding-bottom: map-get($spacers, 4); border-bottom: $border-width $border-style $border-color; diff --git a/public/theme/boost/style/moodle.css b/public/theme/boost/style/moodle.css index f79aa824ee8..06703d984db 100644 --- a/public/theme/boost/style/moodle.css +++ b/public/theme/boost/style/moodle.css @@ -31878,7 +31878,7 @@ table.calendartable caption { .modchooser .optionsummary .activitychooser-option-details { min-width: 15rem; } -.modchooser .optionsummary .activitychooser-option-details .modchooser-detail:not(:last-child) { +.modchooser .optionsummary .activitychooser-option-details .modchooser-detail:not(:last-child):not(:only-child) { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid #dee2e6; diff --git a/public/theme/classic/style/moodle.css b/public/theme/classic/style/moodle.css index 0ab297d187b..277fae216c4 100644 --- a/public/theme/classic/style/moodle.css +++ b/public/theme/classic/style/moodle.css @@ -31878,7 +31878,7 @@ table.calendartable caption { .modchooser .optionsummary .activitychooser-option-details { min-width: 15rem; } -.modchooser .optionsummary .activitychooser-option-details .modchooser-detail:not(:last-child) { +.modchooser .optionsummary .activitychooser-option-details .modchooser-detail:not(:last-child):not(:only-child) { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid #dee2e6;