From 05b27f211840b2ce54c140d53f0f3f53e318aae7 Mon Sep 17 00:00:00 2001 From: Mathew May Date: Mon, 3 Feb 2020 09:35:11 +0800 Subject: [PATCH] MDL-67264 core_course: Activity chooser new feature Roll in the base for the new activity chooser It renders all modules into a modal Gives the user to add from either the base or help screens All checked by accessability tools with great coverage Adds minimal overhead to the course edit setup time Co-authored-by: Mathew May Co-authored-by: Mihail Geshoski --- course/amd/build/activitychooser.min.js | Bin 0 -> 2460 bytes course/amd/build/activitychooser.min.js.map | Bin 0 -> 7823 bytes .../local/activitychooser/dialogue.min.js | Bin 0 -> 4605 bytes .../local/activitychooser/dialogue.min.js.map | Bin 0 -> 19677 bytes .../local/activitychooser/repository.min.js | Bin 0 -> 418 bytes .../activitychooser/repository.min.js.map | Bin 0 -> 1795 bytes .../local/activitychooser/selectors.min.js | Bin 0 -> 1353 bytes .../activitychooser/selectors.min.js.map | Bin 0 -> 4554 bytes course/amd/src/activitychooser.js | 148 +++++++++ .../amd/src/local/activitychooser/dialogue.js | 287 ++++++++++++++++++ .../src/local/activitychooser/repository.js | 40 +++ .../src/local/activitychooser/selectors.js | 70 +++++ course/externallib.php | 82 +++++ course/renderer.php | 10 +- course/templates/chooser.mustache | 48 +++ course/templates/chooser_help.mustache | 54 ++++ course/templates/chooser_item.mustache | 49 +++ course/templates/modchooser.mustache | 38 --- course/tests/externallib_test.php | 35 +++ .../moodle-course-modchooser-debug.js | Bin 4791 -> 0 bytes .../moodle-course-modchooser-min.js | Bin 1724 -> 0 bytes .../moodle-course-modchooser.js | Bin 4791 -> 0 bytes course/yui/src/modchooser/build.json | 10 - course/yui/src/modchooser/js/modchooser.js | 164 ---------- .../yui/src/modchooser/meta/modchooser.json | 8 - lang/en/moodle.php | 2 + lib/db/services.php | 8 + theme/boost/scss/moodle/core.scss | 123 +++++++- theme/boost/scss/moodle/course.scss | 5 - theme/boost/style/moodle.css | 92 +++++- theme/classic/style/moodle.css | 92 +++++- version.php | 2 +- 32 files changed, 1109 insertions(+), 258 deletions(-) create mode 100644 course/amd/build/activitychooser.min.js create mode 100644 course/amd/build/activitychooser.min.js.map create mode 100644 course/amd/build/local/activitychooser/dialogue.min.js create mode 100644 course/amd/build/local/activitychooser/dialogue.min.js.map create mode 100644 course/amd/build/local/activitychooser/repository.min.js create mode 100644 course/amd/build/local/activitychooser/repository.min.js.map create mode 100644 course/amd/build/local/activitychooser/selectors.min.js create mode 100644 course/amd/build/local/activitychooser/selectors.min.js.map create mode 100644 course/amd/src/activitychooser.js create mode 100644 course/amd/src/local/activitychooser/dialogue.js create mode 100644 course/amd/src/local/activitychooser/repository.js create mode 100644 course/amd/src/local/activitychooser/selectors.js create mode 100644 course/templates/chooser.mustache create mode 100644 course/templates/chooser_help.mustache create mode 100644 course/templates/chooser_item.mustache delete mode 100644 course/templates/modchooser.mustache delete mode 100644 course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js delete mode 100644 course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js delete mode 100644 course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js delete mode 100644 course/yui/src/modchooser/build.json delete mode 100644 course/yui/src/modchooser/js/modchooser.js delete mode 100644 course/yui/src/modchooser/meta/modchooser.json diff --git a/course/amd/build/activitychooser.min.js b/course/amd/build/activitychooser.min.js new file mode 100644 index 0000000000000000000000000000000000000000..39ab5cf434d9fbfbc27ce1c763cb71be11012dcc GIT binary patch literal 2460 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 literal 0 HcmV?d00001 diff --git a/course/amd/build/local/activitychooser/dialogue.min.js b/course/amd/build/local/activitychooser/dialogue.min.js new file mode 100644 index 0000000000000000000000000000000000000000..e1d3ec57776c9d0bdcc1cc84e6c9f8b78e24cc0b GIT binary patch literal 4605 zcmb_gU2oeq6n*cna5Nwg0hLK!qY*`yW>|rC9op_G2m*?tEGDw3l2j7c(SP3|CE1E= zC+o64G!n&+`*H5Mht6f8YB`G|p^dx}x;0iFRa)@shzqA~m0OBZYb(uBu6U*At&Eb3 zNZvQvI2$F=^|w}Pp_qI(&S7Q!6VeoHl-|u!EC}rs7Vn2X5pNcIBIa zO7mRRb0u^gT_#0a!%khtJjoJ~b$Ms)#{d4tvo$A?nugMt}N_Wp7qYNTdnfhahfp|XCxJ@j0H(^yyYa7c#|X@ zEt!=uCo5IN{P2*|%?V^>q-#w*k+kIY(_KBfL6NHpz6e6XmO0z$?cw1tJeO{=`kl1Gs0JB$^UU{f#A$DCPO;0w zLr$%9WVQ3WIAnwLu4WhFl5zak;a}$Sz}w0(o(>Vox^5jk<)b%7>Tv|$@YB_>_LNVS zT>Ip&7cH>C!-Ee=hQBv4+3by%l`D+s6957j~ycJk?kRKlQn44cjeXyfTGI{n<8n8eNlx@KdB`(y=>Gfr_z9_J$@sF8CNv!$i18gg-bnF^}v zTADw;e*D0qDD}$<79S^b%Jcj^IN+;3)3xJZF+)(4Ny)rpiDEv*D3XP;GTxj`Rwcqg zk{c#*Se;AryEfyrM=Ut--jz2c0+!NY7WMUZaRZuBmxQ`f){nOL7G}&t@`L~f8JqwJ za$34YmCW&QZAaL93qUeL{t15bUU=G!MIf+lduyNnz_nin%G?pP!Q?OPV!_Q4LG6nh z5hb+HqO~y@&lWhKhsmGXsY2ly)2O@`vhh!z1A)CbvZxE1Sxhz>oLWU2%0(=J6F+nO z7)KtssEkBU^DTO6lAEz2K&GIM8&C-q6qR}5QczOMQd6`~Ma@81l8xahleLkJ%9@IY>TZ93P)sjT8 z?Ab&hPJJU@!+Ra5t*{dG-x19n={mq~NcmXC+odyUD8R%Im4(mS7<#tQ*09-xbEQzg z2HboK+HW{&1zIA3e91I7IR;TFYf9W zWJ+JzD8pt`a(+(eP!hEJ$icTDv>z#WAJo&H=nGIQ8Ur$6&N;fxnquX;6U!YQAs#L0 zbUhnV&4Wv_9Q?F^^N&q?&Mq6;;GAOHGmt2nU_r5oG!GAt2WdOXayz5mADAhaQ>whnqJ>{+(Ddf#AX>*a$u-IfRP{K598&4- zvznKXe0zdWy(kC^Rt4R2UP%vxFDk2`^U+BpRD!W?7(Xjjl>^u&UL=~N-{K~z0<-Qg z_k%3QtMq7pI~3%d5Vw>`2vIODB{U&zkZ&lDD)UmYF&GPC$4M@R&?0fqkk`2!n&y`c+L L;|f9-ym|Exc#wk| literal 0 HcmV?d00001 diff --git a/course/amd/build/local/activitychooser/dialogue.min.js.map b/course/amd/build/local/activitychooser/dialogue.min.js.map new file mode 100644 index 0000000000000000000000000000000000000000..d2c6bcdf0a1025bf38804e779907dd380643f9b6 GIT binary patch literal 19677 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 literal 0 HcmV?d00001 diff --git a/course/amd/build/local/activitychooser/repository.min.js b/course/amd/build/local/activitychooser/repository.min.js new file mode 100644 index 0000000000000000000000000000000000000000..da5b3c7c6946691241ac0efd08780e1c23a66f68 GIT binary patch literal 418 zcmZvY!D_=W5Jd0!3Ri_-gOPK#3HgDBmO@V3!=^^Kx*_n9_ z9*o5#6`B~8rpO+Pjx=-y=wJpDF8WC1kqbsg-UQ-{$aezIjyQOc-<$=U;4Jp}A==qR zHif*-MiD(GKCsb2RG;+;b&$<1@QcYIhs&%JN}=CU6FU_7I6xP%d|ORf1@h70e)a2t zOp~mtdi{*aM#d1?Ca_$Be7@R1InGfKyHLV3rFB*fz=?ikt*@o7N;%K&(%sl&I8tMw z$MQcH)nZVOPRbemi>!=2kjQ~sUygTtrnx5Wv)yVxy%?eAX^WzGPy7s9;})DVw*C43 QSU&;F-dK6^vIn>L0|HN)K>z>% literal 0 HcmV?d00001 diff --git a/course/amd/build/local/activitychooser/repository.min.js.map b/course/amd/build/local/activitychooser/repository.min.js.map new file mode 100644 index 0000000000000000000000000000000000000000..67b15907c103938be1a1570c163b0b81ae9d4650 GIT binary patch literal 1795 zcmah~+j84B5dD=Mz1YsMV%j`ai9La7$x)-5b&1DyJRXQ#iEv1Q0YE9L$N%14kc{<^ zX*nc_%kIJ13l`6v9U7~2(doSlI#yREM%(G#ce>rMeyxeaOvf?{W$e^WxkH?4ZPA1V zOKp|Y=Fokzorj=P$h=l=CC56cGMWlHF(F%233cU@{J}Fa%jlP*OLfAUykxc>D3@ia z3hMVddL-~wluzsbm6dY?9woVWvVP}RE9wRQW^((17ocdn)wY<=gM9f=6>AOW*dGj zLjScgtZfj%>W$k=gFQIt3SwD+!9-bS)Vgv6?m!kvs14*gQJVw9@MKjaXuzci4oz-( za~@nzR&b348kxbYT4ySTk&3af2$E>>j7>3tbq&NS17@*kSinFNvUHS20idFF15H}N zyLOWXF#v5mom1(Uya8RZ;u$eMKqiS;tK0pB^{+-IpbF1E)g{@J5*%5$S6K#Ygv#Qk z$^yR=xdd*9%TLqQ62#;VZpC~qCd<3`Bu&XjtGL4&OXX#z$WrpHk%c=Dc@Ht}&!bPI zBQA%d;qs1bfWdG%=`R*An9f1KOw57w5SSdfp%86M$3e7x~-G6)Ge zsWSU&_Z`DxY|i0~~)FUJx5XdGqFL0se!Jlv8x7fuC3#e5}v*Q8{2y_*lyLo7{T2Be8mo z*&DS@9l#%NfBzH4lKix%-vK`4zTdSvt2i<2MXk~>;$45imjN@}xNuu$-PGk-oz$}t z>?i|GomdP+$BBZh>Ev-8qSK^vIAMMvk5AqoSqlSnaSBwYyq8r$fg*Cm3Fxi8U}29Z zBv2qlrAf%P`ax4D$%8*#=c^sG5>0)q4Wx1)b9kOq`5Mg&xwfvDaLcJ(kHBfzcv5@@ zTLwg{Mlk3qQ^2!cKVj@%;OVA9bAXa;RF=}vTaQ1y^)Y#te9)Bih6tR)X}j+koL|5* zDfuw7u)*&Y(*L}j=B3yA-K+iBq{HJD-D3;?SgQCNnn!lw2^~sS8*(|&`)uCF zO7e060RsjRODT}!)CvPj7O`#X>$30Cb+G;4jj|09Zv>e6`-b1nmqyCWO{%@VmBQPn z4%Cw{(OuUx+?+r7QXAOQNX(ar%H7I7btCG`y+SLDymoRHV(^*RX(}IR&*1n@o2Y7T zH0iURr5Zqrf5f_-**rjabCv2`1`#9c0bS7YF0MyD=r#Gr=#T~?a8GFzN{e6jw>FrVf+Qo5_xZjYydo5tq&rQlWdHE6&WJhBN+ zhkXc6RG_G7`?uqeM)DQ^i~g2_9c6{H;2!X~sZCV0qjj`&9!T&xWbq4F{GcYpcjQoZ z&S^DJ(l=9DOZ7@FzJ1h13RBYIdM!LqG_=cwhXcXsR*D=ddCI)``U{uKD<>`vXyV>$Ly? literal 0 HcmV?d00001 diff --git a/course/amd/build/local/activitychooser/selectors.min.js.map b/course/amd/build/local/activitychooser/selectors.min.js.map new file mode 100644 index 0000000000000000000000000000000000000000..98e336764ff50fe3ec47f68a58063ac928f27916 GIT binary patch literal 4554 zcmb_g3v=2y5dJGWOlQ)x2Ku<0xl12Y*^q=3LIULSXgaB43vgpwM7H6$O#b`rN-~C! z(&pxv#K_X_x1V;U)rz~yRH!tRNoD7)Q%Pm6e34dmzEo+V`~b)u|*rHo7B`HF(DRcD!PS@N3GWT;R77BQ%*}%VL$S?j&0s zG94r!lZ=OnP}n9a1|Up^vefS9am>}+N{b}JRv=Peg&LOnxso|D$L3f>6a0#0kVi<| zsUYu;epzCUqckrZl^TLAET>~REA8kjEu-oihd)XFIs4& z^h6qFB$?ANHZwU1ee!S2CzCK4VR$N|&+Mh!VUEY}Lr+1EJ*Ur@=TWmkkFIVWQ8RS$ z=_$`iUB*VP9v-`nU~KMcsefFj%e;NZbM{!%C*d;>dsvsWoAlmZdOx6EZPRm(t+i7p zM(i^jX(VK=+5YVKj1d;LA60l`6?R{f!d|=MaK^Y+zy~qF3H9~6p2eEQSB1qP3N_D; zOU6CYT9d!QPGY!wjgP&vzSCl?MUu_Iqw+5;{{CM$tRKhilX6zr>0RvqjAUW75ukkY z;*A+gC=47BUXMulZBB7KCj2J( zX5Gq(@mw$=Bckn>uA^zK%Xf_YP?H+DZBb5}g0-%Ip(ZM0SFfJXZI4_h#Hs0}i%XCe z3)5@0>)bXN%RRmFr?djUyNYNvXrW5?Y*5<54i7{2a9u52s*SCaDSX=au5l^+t+sR> z^uh_YSyQ`VT~FFr9Au>Txqa)F(bE*yRsOIl?J22r+NoQ*R->$|Sl=FM-%ebb^>6N3 z?v^pc9`A5z!y@qpZf@4Z&Sj^LioJmSiv{51F59Oq-ooC+4#Z?}K->6tN*I)z%wgeS zTBbycXGXmagK?O`FpLD?_k^nqh)N^*Q6TNFO$0dFJwqt2?41b zW;3qD4$Nf^K2Jc2AWSnAUgsHtXTX!7E)`(r42N@qq0T%Bgo11=AQLK1$vD029iG6R zz@vjlaFkz1p%3lQ7fC9BBTec_$0C4h14Jsj#G-Fmz^+7OPC45F5#qQ4dv3zp!iYt5 zKq^h=InRi^0&{R|BjY(l9ElaWwMS-OPBMTn(d@@^g7X<89L{hSMiE>Kz%y)^M~=3H zE`ih5V844Z0Cso|r>xgwhlBHXD4mTZj*6);SYbSgLYyi3R`Dd8BY6$cY4*H*)M0L` z-5Q+Z9ALLKIBfR&u-okcgCo`(w7iox>%q}U@2J~v)&TnGBXovF*x=)#j*}7)2t2`~ zw4C=j1{crKJPKgUrviiQi*SnkIQVix?0ynIZJI|i8R@y&WrWmPLr7%iK#COK+jTda z&1$10*9#BV0L4G8e?&Hj^5Vs10xzH;hFD?%M<-j`iz)|ZV#)h>5Q8*69Vs5rYls^` z4Y@B65e(cXT%K550DXL^#FdW=A{DlvS}UY9g-;Xi-|&%ETGBCNnySFits z4oCmb@M{k5W8JQ$GRk#z^wFr;nG%IZ{6K(_7-sd+B&vW)t zx`{y=laHu`!u34N6J5|iCgC~{F`BozP;-8bQ^#$?F$zVgXgI|J*JS_?3Pm%9S%b%L z*T=n{jP8M|uSW-TF=uDk ze9M%jP1^Laqo$xte+kIsob*=!SIBB}MWOu!OFu9~aJE)|3Yb;d z07_;5QxHl12XOc6p0;KE|Kpj|+(~|5y>K`lp--95GAOEU0AITPY&1|HF3GPf!v7e` z0(NsqS7KmqXdBrU*SHmml#MoOk-&$GC_df|n|j0Mfx(xD~L;1;f*j_?g7xF6zMj-5s3u0wpTq`+`U&EXK&#}A^qcFKy(YAMZ0>8$ePj0U0Dt{H@4;WXpDjv$}OFjw* g;k356^cO-D6*_h3M*ym=@dtqNK#jwMet)_D7Z;zT;s5{u literal 0 HcmV?d00001 diff --git a/course/amd/src/activitychooser.js b/course/amd/src/activitychooser.js new file mode 100644 index 00000000000..6f6613bc9d2 --- /dev/null +++ b/course/amd/src/activitychooser.js @@ -0,0 +1,148 @@ +// 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 . + +/** + * A type of dialogue used as for choosing modules in a course. + * + * @module core_course/activitychooser + * @package core_course + * @copyright 2020 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import * as ChooserDialogue from 'core_course/local/activitychooser/dialogue'; +import * as Repository from 'core_course/local/activitychooser/repository'; +import selectors from 'core_course/local/activitychooser/selectors'; +import CustomEvents from 'core/custom_interaction_events'; +import * as Templates from 'core/templates'; +import * as ModalFactory from 'core/modal_factory'; +import {get_string as getString} from 'core/str'; +import Pending from 'core/pending'; + +/** + * Set up the activity chooser. + * + * @method init + * @param {Number} courseId Course ID to use later on in fetchModules() + */ +export const init = courseId => { + const pendingPromise = new Pending(); + + registerListenerEvents(courseId); + + pendingPromise.resolve(); +}; + +/** + * Once a selection has been made make the modal & module information and pass it along + * + * @method registerListenerEvents + * @param {Number} courseId + */ +const registerListenerEvents = (courseId) => { + const events = [ + 'click', + CustomEvents.events.activate, + CustomEvents.events.keyboardActivate + ]; + + const fetchModuleData = (() => { + let innerPromise = null; + + return () => { + if (!innerPromise) { + innerPromise = new Promise((resolve) => { + resolve(Repository.activityModules(courseId)); + }); + } + + return innerPromise; + }; + })(); + + CustomEvents.define(document, events); + + // Display module chooser event listeners. + events.forEach((event) => { + document.addEventListener(event, async(e) => { + if (e.target.closest(selectors.elements.sectionmodchooser)) { + const caller = e.target.closest(selectors.elements.sectionmodchooser); + const builtModuleData = sectionIdMapper(await fetchModuleData(), caller.dataset.sectionid); + const sectionModal = await modalBuilder(builtModuleData); + + ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData); + } + }); + }); +}; + +/** + * Given the web service data and an ID we want to make a deep copy + * of the WS data then add on the section ID to the addoption URL + * + * @method sectionIdMapper + * @param {Object} webServiceData Our original data from the Web service call + * @param {Array} id The ID of the section we need to append to the links + * @return {Array} [modules] with URL's built + */ +const sectionIdMapper = (webServiceData, id) => { + // We need to take a fresh deep copy of the original data as an object is a reference type. + const newData = JSON.parse(JSON.stringify(webServiceData)); + newData.allmodules.forEach((module) => { + module.urls.addoption += '§ion=' + id; + }); + return newData.allmodules; +}; + +/** + * Build a modal for each section ID and store it into a map for quick access + * + * @method modalBuilder + * @param {Map} data our map of section ID's & modules to generate modals for + * @return {Object} TODO + */ +const modalBuilder = data => buildModal(templateDataBuilder(data)); + +/** + * Given an array of modules we want to figure out where & how to place them into our template object + * + * @method templateDataBuilder + * @param {Array} data our modules to manipulate into a Templatable object + * @return {Object} Our built object ready to render out + */ +const templateDataBuilder = (data) => { + return { + 'default': data, + }; +}; + +/** + * Given an object we want to prebuild a modal ready to store into a map + * + * @method buildModal + * @param {Object} data The template data which contains arrays of modules + * @return {Object} The modal for the calling section with everything already set up + */ +const buildModal = data => { + return ModalFactory.create({ + type: ModalFactory.types.DEFAULT, + title: getString('addresourceoractivity'), + body: Templates.render('core_course/chooser', data), + large: true, + templateContext: { + classes: 'modchooser' + } + }); +}; diff --git a/course/amd/src/local/activitychooser/dialogue.js b/course/amd/src/local/activitychooser/dialogue.js new file mode 100644 index 00000000000..4be43417dd4 --- /dev/null +++ b/course/amd/src/local/activitychooser/dialogue.js @@ -0,0 +1,287 @@ +// 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 . + +/** + * A type of dialogue used as for choosing options. + * + * @module core_course/local/chooser/dialogue + * @package core + * @copyright 2019 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import $ from 'jquery'; +import * as ModalEvents from 'core/modal_events'; +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'; + +/** + * Given an event from the main module 'page' navigate to it's help section via a carousel. + * + * @method showModuleHelp + * @param {jQuery} carousel Our initialized carousel to manipulate + * @param {Object} moduleData Data of the module to carousel to + */ +const showModuleHelp = (carousel, moduleData) => { + const help = carousel.find(selectors.regions.help)[0]; + help.innerHTML = ''; + + // Add a spinner. + const spinnerPromise = addIconToContainer(help); + + // Used later... + let transitionPromiseResolver = null; + const transitionPromise = new Promise(resolve => { + transitionPromiseResolver = resolve; + }); + + // Build up the html & js ready to place into the help section. + const contentPromise = Templates.renderForPromise('core_course/chooser_help', moduleData); + + // Wait for the content to be ready, and for the transition to be complet. + Promise.all([contentPromise, spinnerPromise, transitionPromise]) + .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js)) + .then(() => { + help.querySelector(selectors.regions.chooserSummary.description).focus(); + return help; + }) + .catch(Notification.exception); + + // Move to the next slide, and resolve the transition promise when it's done. + carousel.one('slid.bs.carousel', () => { + transitionPromiseResolver(); + }); + // Trigger the transition between 'pages'. + carousel.carousel('next'); +}; + +/** + * 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} + */ +const registerListenerEvents = (modal, mappedModules) => { + const bodyClickListener = e => { + if (e.target.closest(selectors.actions.optionActions.showSummary)) { + const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel)); + + const module = e.target.closest(selectors.regions.chooserOption.container); + const moduleName = module.dataset.modname; + const moduleData = mappedModules.get(moduleName); + showModuleHelp(carousel, moduleData); + } + + // 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)); + + // Trigger the transition between 'pages'. + carousel.carousel('prev'); + carousel.on('slid.bs.carousel', () => { + const allModules = modal.getBody()[0].querySelector(selectors.regions.modules); + const caller = allModules.querySelector(selectors.regions.getModuleSelector(e.target.dataset.modname)); + caller.focus(); + }); + } + }; + + modal.getBodyPromise() + + // The return value of getBodyPromise is a jquery object containing the body NodeElement. + .then(body => body[0]) + + // Set up the carousel. + .then(body => { + $(body.querySelector(selectors.regions.carousel)) + .carousel({ + interval: false, + pause: true, + keyboard: false + }); + + return body; + }) + + // Add the listener for clicks on the body. + .then(body => { + body.addEventListener('click', bodyClickListener); + return body; + }) + + // Register event listeners related to the keyboard navigation controls. + .then(body => { + initKeyboardNavigation(body, mappedModules); + return body; + }) + .catch(); + +}; + +/** + * Initialise the keyboard navigation controls for the chooser. + * + * @method initKeyboardNavigation + * @param {NodeElement} body 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} + */ +const initKeyboardNavigation = (body, mappedModules) => { + + const chooserOptions = body.querySelectorAll(selectors.regions.chooserOption.container); + + Array.from(chooserOptions).forEach((element) => { + return element.addEventListener('keyup', (e) => { + const chooserOptions = document.querySelector(selectors.regions.chooserOptions); + + // Check for enter/ space triggers for showing the help. + if (e.keyCode === enter || e.keyCode === space) { + if (e.target.matches(selectors.actions.optionActions.showSummary)) { + e.preventDefault(); + const module = e.target.closest(selectors.regions.chooserOption.container); + const moduleName = module.dataset.modname; + const moduleData = mappedModules.get(moduleName); + const carousel = $(body.querySelector(selectors.regions.carousel)); + carousel.carousel({ + interval: false, + pause: true, + keyboard: false + }); + showModuleHelp(carousel, moduleData); + } + } + + // Next. + if (e.keyCode === arrowRight) { + e.preventDefault(); + const currentOption = e.target.closest(selectors.regions.chooserOption.container); + const nextOption = currentOption.nextElementSibling; + const firstOption = chooserOptions.firstElementChild; + const toFocusOption = clickErrorHandler(nextOption, firstOption); + focusChooserOption(toFocusOption, currentOption); + } + + // Previous. + if (e.keyCode === arrowLeft) { + e.preventDefault(); + const currentOption = e.target.closest(selectors.regions.chooserOption.container); + const previousOption = currentOption.previousElementSibling; + const lastOption = chooserOptions.lastElementChild; + const toFocusOption = clickErrorHandler(previousOption, lastOption); + focusChooserOption(toFocusOption, currentOption); + } + + if (e.keyCode === home) { + e.preventDefault(); + const currentOption = e.target.closest(selectors.regions.chooserOption.container); + const firstOption = chooserOptions.firstElementChild; + focusChooserOption(firstOption, currentOption); + } + + if (e.keyCode === end) { + e.preventDefault(); + const currentOption = e.target.closest(selectors.regions.chooserOption.container); + const lastOption = chooserOptions.lastElementChild; + focusChooserOption(lastOption, currentOption); + } + }); + }); +}; + +/** + * Focus on a chooser option element and remove the previous chooser element from the focus order + * + * @method focusChooserOption + * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus + * @param {HTMLElement} previousChooserOption The previous focused option element + */ +const focusChooserOption = (currentChooserOption, previousChooserOption = false) => { + if (previousChooserOption !== false) { + const previousChooserOptionLink = previousChooserOption.querySelector(selectors.actions.addChooser); + const previousChooserOptionHelp = previousChooserOption.querySelector(selectors.actions.optionActions.showSummary); + // Set tabindex to -1 to remove the previous chooser option element from the focus order. + previousChooserOption.tabIndex = -1; + previousChooserOptionLink.tabIndex = -1; + previousChooserOptionHelp.tabIndex = -1; + } + + const currentChooserOptionLink = currentChooserOption.querySelector(selectors.actions.addChooser); + const currentChooserOptionHelp = currentChooserOption.querySelector(selectors.actions.optionActions.showSummary); + // Set tabindex to 0 to add current chooser option element to the focus order. + currentChooserOption.tabIndex = 0; + currentChooserOptionLink.tabIndex = 0; + currentChooserOptionHelp.tabIndex = 0; + // Focus the current chooser option element. + currentChooserOption.focus(); +}; + +/** + * Small error handling function to make sure the navigated to object exists + * + * @method clickErrorHandler + * @param {HTMLElement} item What we want to check exists + * @param {HTMLElement} fallback If we dont match anything fallback the focus + * @return {String} + */ +const clickErrorHandler = (item, fallback) => { + if (item !== null) { + return item; + } else { + return fallback; + } +}; + +/** + * Display the module chooser. + * + * @method displayChooser + * @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 + */ +export const displayChooser = (origin, modal, sectionModules) => { + + // Make a map so we can quickly fetch a specific module's object for either rendering or searching. + const mappedModules = new Map(); + sectionModules.forEach((module) => { + mappedModules.set(module.modulename, module); + }); + + // Register event listeners. + registerListenerEvents(modal, mappedModules); + + // We want to focus on the action select when the dialog is closed. + modal.getRoot().on(ModalEvents.hidden, () => { + modal.destroy(); + }); + + // We want to focus on the first chooser option element as soon as the modal is opened. + modal.getRoot().on(ModalEvents.shown, () => { + modal.getModal()[0].tabIndex = -1; + + modal.getBodyPromise() + .then(body => { + const firstChooserOption = body[0].querySelector(selectors.regions.chooserOption.container); + focusChooserOption(firstChooserOption); + + return; + }) + .catch(Notification.exception); + }); + + modal.show(); +}; diff --git a/course/amd/src/local/activitychooser/repository.js b/course/amd/src/local/activitychooser/repository.js new file mode 100644 index 00000000000..1e6f1a50187 --- /dev/null +++ b/course/amd/src/local/activitychooser/repository.js @@ -0,0 +1,40 @@ +// 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 . + +/** + * + * @module core_course/repository + * @package core_course + * @copyright 2019 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +import ajax from 'core/ajax'; + +/** + * Fetch all the information on modules we'll need in the activity chooser. + * + * @method activityModules + * @param {Number} courseid What course to fetch the modules for + * @return {object} jQuery promise + */ +export const activityModules = (courseid) => { + const request = { + methodname: 'core_course_get_activity_picker_info', + args: { + courseid: courseid, + }, + }; + return ajax.call([request])[0]; +}; diff --git a/course/amd/src/local/activitychooser/selectors.js b/course/amd/src/local/activitychooser/selectors.js new file mode 100644 index 00000000000..adeb07f2961 --- /dev/null +++ b/course/amd/src/local/activitychooser/selectors.js @@ -0,0 +1,70 @@ +// 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 . + +/** + * Define all of the selectors we will be using on the grading interface. + * + * @module core_course/local/chooser/selectors + * @package core_course + * @copyright 2019 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * A small helper function to build queryable data selectors. + * @method getDataSelector + * @param {String} name + * @param {String} value + * @return {string} + */ +const getDataSelector = (name, value) => { + return `[data-${name}="${value}"]`; +}; + +export default { + regions: { + chooser: getDataSelector('region', 'chooser-container'), + chooserOptions: getDataSelector('region', 'chooser-options-container'), + chooserOption: { + container: getDataSelector('region', 'chooser-option-container'), + actions: getDataSelector('region', 'chooser-option-actions-container'), + info: getDataSelector('region', 'chooser-option-info-container'), + }, + chooserSummary: { + container: getDataSelector('region', 'chooser-option-summary-container'), + content: getDataSelector('region', 'chooser-option-summary-content-container'), + description: getDataSelector('region', 'summary-description'), + actions: getDataSelector('region', 'chooser-option-summary-actions-container'), + }, + carousel: getDataSelector('region', 'carousel'), + help: getDataSelector('region', 'help'), + modules: getDataSelector('region', 'modules'), + getModuleSelector: modname => `[role="menuitem"][data-modname="${modname}"]` + }, + actions: { + optionActions: { + showSummary: getDataSelector('action', 'show-option-summary'), + }, + addChooser: getDataSelector('action', 'add-chooser-option'), + closeOption: getDataSelector('action', 'close-chooser-option-summary'), + hide: getDataSelector('action', 'hide') + }, + elements: { + section: '.section', + sectionmodchooser: 'button.section-modchooser-link', + sitemenu: '.block_site_main_menu', + sitetopic: 'div.sitetopic', + }, +}; diff --git a/course/externallib.php b/course/externallib.php index b39acfe3a0f..c92ad974e13 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -4140,4 +4140,86 @@ class core_course_external extends external_api { ); return new external_single_structure($userfields); } + + /** + * Returns description of method result value + * + * @return external_description + */ + public static function fetch_modules_activity_chooser_returns() { + return new external_single_structure([ + 'allmodules' => new external_multiple_structure( + new external_single_structure([ + 'label' => new external_value(PARAM_TEXT, 'Human readable module name', VALUE_OPTIONAL), + 'modulename' => new external_value(PARAM_TEXT, 'Module name', VALUE_OPTIONAL), + 'description' => new external_value(PARAM_RAW, 'Help panel information', VALUE_OPTIONAL), + 'urls' => new external_single_structure([ + 'addoption' => new external_value(PARAM_URL, 'The edit link for the module', VALUE_OPTIONAL), + ]), + 'icon' => new external_single_structure([ + 'attributes' => new external_multiple_structure( + new external_single_structure([ + 'name' => new external_value(PARAM_RAW, 'HTML attr', VALUE_OPTIONAL), + 'value' => new external_value(PARAM_RAW, 'Value of the HTML attr', VALUE_OPTIONAL), + ]) + ), + 'extraclasses' => new external_value(PARAM_RAW, 'Anything extra the module defines', VALUE_OPTIONAL), + ]), + ]) + ), + 'warnings' => new external_warnings() + ]); + } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + */ + public static function fetch_modules_activity_chooser_parameters() { + return new external_function_parameters([ + 'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED), + ]); + } + + /** + * Given a course ID fetch all accessible modules for that course + * + * @param int $courseid The course we want to fetch the modules for + * @return array Contains array of modules and their metadata + * @throws moodle_exception + */ + public static function fetch_modules_activity_chooser(int $courseid) { + global $DB, $OUTPUT; + [ + 'courseid' => $courseid, + ] = self::validate_parameters(self::fetch_modules_activity_chooser_parameters(), [ + 'courseid' => $courseid, + ]); + $warnings = array(); + + // Validate the course context. + $coursecontext = context_course::instance($courseid); + self::validate_context($coursecontext); + // Check to see if user can add menus and there are modules to add. + if (!has_capability('moodle/course:manageactivities', $coursecontext) + || !($modnames = get_module_types_names()) || empty($modnames)) { + return ''; + } + + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + // Retrieve all modules with associated metadata. + $modules = get_module_metadata($course, $modnames, null); + $related = [ + 'context' => $coursecontext + ]; + // Export the module chooser data. + $modchooserdata = new \core_course\external\course_module_chooser_exporter($modules, $related); + + $result = []; + $result['allmodules'] = $modchooserdata->export($OUTPUT)->options; + $result['warnings'] = $warnings; + return $result; + } + } diff --git a/course/renderer.php b/course/renderer.php index b02df933065..83a329aaf30 100644 --- a/course/renderer.php +++ b/course/renderer.php @@ -142,11 +142,8 @@ class core_course_renderer extends plugin_renderer_base { */ public function course_modchooser($modules, $course) { debugging('course_modchooser() is deprecated. Please use course_activitychooser() instead.', DEBUG_DEVELOPER); - if (!$this->page->requires->should_create_one_time_item_now('core_course_modchooser')) { - return ''; - } - $modchooser = new \core_course\output\modchooser($course, $modules); - return $this->render($modchooser); + + return $this->course_activitychooser($course->id); } /** @@ -161,7 +158,7 @@ class core_course_renderer extends plugin_renderer_base { return ''; } - $this->page->requires->js_call_amd('core_course/modchooser', 'init', [$courseid]); + $this->page->requires->js_call_amd('core_course/activitychooser', 'init', [$courseid]); return ''; } @@ -345,7 +342,6 @@ class core_course_renderer extends plugin_renderer_base { 'class' => 'section-modchooser-link btn btn-link', 'data-action' => 'open-chooser', 'data-sectionid' => $section, - 'disabled' => true ) ); $modchooser.= html_writer::end_tag('div'); diff --git a/course/templates/chooser.mustache b/course/templates/chooser.mustache new file mode 100644 index 00000000000..c503c9bb06e --- /dev/null +++ b/course/templates/chooser.mustache @@ -0,0 +1,48 @@ +{{! + 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 . +}} +{{! + @template core_course/chooser + + Chooser dialog template. + + Example context (json): + { + "title": "Chooser title", + "options": { + "label": "Option name", + "description": "Option description", + "urls": { + "addoption": "http://addoptionurl.com" + }, + "icon": "" + } + } +}} + diff --git a/course/templates/chooser_help.mustache b/course/templates/chooser_help.mustache new file mode 100644 index 00000000000..b63502b8434 --- /dev/null +++ b/course/templates/chooser_help.mustache @@ -0,0 +1,54 @@ +{{! + 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 . +}} +{{! + @template core_course/chooser_help + + Chooser help / more information template. + + Example context (json): + { + "label": "Option name", + "description": "Option description", + "urls": { + "addoption": "http://addoptionurl.com" + }, + "icon": "" + } +}} +
+
+
+
+ {{#icon}} + {{>core/pix_icon}} + {{/icon}} + {{label}} +
+
+
+ {{{description}}} +
+
+
+ + + {{#str}} add {{/str}} + +
+
diff --git a/course/templates/chooser_item.mustache b/course/templates/chooser_item.mustache new file mode 100644 index 00000000000..f8507561689 --- /dev/null +++ b/course/templates/chooser_item.mustache @@ -0,0 +1,49 @@ +{{! + 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 . +}} +{{! + @template core_course/chooser_item + + Chooser item template. + + Example context (json): + { + "label": "Option name", + "description": "Option description", + "urls": { + "addoption": "http://addoptionurl.com" + }, + "icon": "" + } +}} + diff --git a/course/templates/modchooser.mustache b/course/templates/modchooser.mustache deleted file mode 100644 index 88cf99c4f68..00000000000 --- a/course/templates/modchooser.mustache +++ /dev/null @@ -1,38 +0,0 @@ -{{! - 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 . -}} -{{! - Course module chooser. -}} -{{> core/chooser }} -{{#js}} -require([ - 'core/yui', - 'core/str' -], function(Y, Str) { - Str.get_strings([ - { key: 'addresourceoractivity', component: 'moodle' }, - { key: 'close', component: 'editor' }, - ]).then(function(add, close) { - Y.use('moodle-course-modchooser', function() { - M.course.init_chooser({ - courseid: {{courseid}}, - closeButtonTitle: close - }); - }); - }); -}); -{{/js}} diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index 67611e59786..b2f1671c7ff 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -3049,4 +3049,39 @@ class core_course_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals(2, count($users['users'])); $this->assertEquals($expectedusers, $users); } + + /** + * Test fetch_modules_activity_chooser + */ + public function test_fetch_modules_activity_chooser() { + global $OUTPUT; + + $this->resetAfterTest(true); + + // Log in as Admin. + $this->setAdminUser(); + + $course1 = self::getDataGenerator()->create_course(); + + // Fetch course modules. + $result = core_course_external::fetch_modules_activity_chooser($course1->id); + $result = external_api::clean_returnvalue(core_course_external::fetch_modules_activity_chooser_returns(), $result); + // Check for 0 warnings. + $this->assertEquals(0, count($result['warnings'])); + // Check we have the right number of standard modules. + $this->assertEquals(21, count($result['allmodules'])); + + $coursecontext = context_course::instance($course1->id); + $modnames = get_module_types_names(); + $modules = get_module_metadata($course1, $modnames, null); + $related = [ + 'context' => $coursecontext + ]; + // Export the module chooser data. + $modchooserdata = new \core_course\external\course_module_chooser_exporter($modules, $related); + $formatteddata = $modchooserdata->export($OUTPUT)->options; + + // Check if the webservice returns exactly what the exporter defines. + $this->assertEquals($formatteddata, $result['allmodules']); + } } diff --git a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js deleted file mode 100644 index ad86243e7b5c086e900fbe2a1f3c36f8d405cc02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4791 zcmd5=S#R4$5PsLMn5sZYr!t+q6-r%Lb=1a1Z3mVd)F=vJO|GPEcxActQAhv1GkbAG z*>0M?R1a~;ot^WWi&vK?3CprLDyk~W`F>ipvf=whm8I*dYPgI>ux!gziK>M7Y6NGK z)9D^O3(%M`o8!0V=d){*GBbDHu(#!h;|)|q+jgAv??1V+f-Fj`!vm2)4*wBq$FMV^eMknyv)qG z|A!zGQ8#G?MJg1O3;tb12hq1N~-PL0f}*Bo}s+EVx=%8HiFS%o6dJ zSHQ>$-j8>KK$=phtVSm6hh5+?-wLLZ6;}&Oaw{<`Hd-+TLmP|#h$X}!7Z+xCj>(tU z+#>iU5q!HPwd1t$JV`T$D!g=+R3(q2KHhZl2Aa^VHD?)@yLXgOIgfh8HIuApkoW=9 z&FM?9!}L~dy|DORI8|czpOKX_0>c9N`uhh5aNDJ*li^ z_a!St%JLlRp5zFw%u{==fmV3O#UzY_&smeY*R0`*ld~|TQm2&l!DeJs4#0O=XDA#M z^ti|D%ozOE!vOz&A*PkY@;30`pf-9g^DN z83KpE9dwiqMVl=^U|M5VQ?RMTgRb$`9mvzbJLJ5VAR1zwIoL^bi`YvzOV_clrZ(k5 z00C&jwDeZP9-wzt7LDJP@0o)96P4#v8D|)eL|opaTHfS za;!x>4tVX7YYA0}>g6KE!PbHX$ zJF-bKQb92` zzglw~n2kd&$RF>?O*DH?l#)&SuoW~Mk~!|lf3`(!*JlDC{j>$+z)n+E_3LO4*Nx~K zi@ifyKDJZH%h7;dsMd^4gVlZ F{symDmmL5A diff --git a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js deleted file mode 100644 index 785cf357db22d42350999e3d24bd401ed7176d7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1724 zcmbVNO>Y}F5WV+Ts4So?5t?;w#j0DxRfX0EOM$Hf2#P|Wmt%Sv!2(EPk8*u~^*AZs}Bo=$(wvW;-fzVU7Dl z_pg_;o7wz*&g8xI<)>9N2&=6!ZnXthbqAJkDb>> z#%c$lv?@kfdKWQNCHf%MZfBn|+?Xg-SXWdFBX1@k;R}M(nGrX1K7i~M(MDOnu0Yg1 z6{9=O#u=oT`4>WdR5&SyMaq$+l@C5_70NZ>s=xm^5QH9C#wR!)XHNEN7EH3&$R{aD zC_s7#89PPN>d%EajiW&ejEh**%_I>d@v+*idLpZVNfv|=k&B}vgmQqF@m7Eqe8=Hl z5sv_C6A{9Syi!?c1>}wBDjZ!uNI~ww=jRLyCy(sIMaK%cRMsZu*HxYih{@pG@qp#c<%pP{tBcuWQHt8s3?s!B@Jxb3~l1hs;A_F!B<@;8gV9cAWF?79w3os4p1sr zLZoSY(SP0>vRxQP7jGtDBaHsIX#$U5>55A!7(G7l2wc$-8>iN~pEDz#pxxBs0>#h2- z0khs(%NsY=jXcF|0Q=A9GZSuwba?pn9#Qe|0vlHm;>0X>+E0OFkAEm?cVShDz^4(Xmm1cVY diff --git a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js deleted file mode 100644 index ad86243e7b5c086e900fbe2a1f3c36f8d405cc02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4791 zcmd5=S#R4$5PsLMn5sZYr!t+q6-r%Lb=1a1Z3mVd)F=vJO|GPEcxActQAhv1GkbAG z*>0M?R1a~;ot^WWi&vK?3CprLDyk~W`F>ipvf=whm8I*dYPgI>ux!gziK>M7Y6NGK z)9D^O3(%M`o8!0V=d){*GBbDHu(#!h;|)|q+jgAv??1V+f-Fj`!vm2)4*wBq$FMV^eMknyv)qG z|A!zGQ8#G?MJg1O3;tb12hq1N~-PL0f}*Bo}s+EVx=%8HiFS%o6dJ zSHQ>$-j8>KK$=phtVSm6hh5+?-wLLZ6;}&Oaw{<`Hd-+TLmP|#h$X}!7Z+xCj>(tU z+#>iU5q!HPwd1t$JV`T$D!g=+R3(q2KHhZl2Aa^VHD?)@yLXgOIgfh8HIuApkoW=9 z&FM?9!}L~dy|DORI8|czpOKX_0>c9N`uhh5aNDJ*li^ z_a!St%JLlRp5zFw%u{==fmV3O#UzY_&smeY*R0`*ld~|TQm2&l!DeJs4#0O=XDA#M z^ti|D%ozOE!vOz&A*PkY@;30`pf-9g^DN z83KpE9dwiqMVl=^U|M5VQ?RMTgRb$`9mvzbJLJ5VAR1zwIoL^bi`YvzOV_clrZ(k5 z00C&jwDeZP9-wzt7LDJP@0o)96P4#v8D|)eL|opaTHfS za;!x>4tVX7YYA0}>g6KE!PbHX$ zJF-bKQb92` zzglw~n2kd&$RF>?O*DH?l#)&SuoW~Mk~!|lf3`(!*JlDC{j>$+z)n+E_3LO4*Nx~K zi@ifyKDJZH%h7;dsMd^4gVlZ F{symDmmL5A diff --git a/course/yui/src/modchooser/build.json b/course/yui/src/modchooser/build.json deleted file mode 100644 index 569ab7b0b60..00000000000 --- a/course/yui/src/modchooser/build.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "moodle-course-modchooser", - "builds": { - "moodle-course-modchooser": { - "jsfiles": [ - "modchooser.js" - ] - } - } -} diff --git a/course/yui/src/modchooser/js/modchooser.js b/course/yui/src/modchooser/js/modchooser.js deleted file mode 100644 index 989adf700f3..00000000000 --- a/course/yui/src/modchooser/js/modchooser.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * The activity chooser dialogue for courses. - * - * @module moodle-course-modchooser - */ - -var CSS = { - PAGECONTENT: 'body', - SECTION: null, - SECTIONMODCHOOSER: 'button.section-modchooser-link', - SITEMENU: '.block_site_main_menu', - SITETOPIC: 'div.sitetopic' -}; - -var MODCHOOSERNAME = 'course-modchooser'; - -/** - * The activity chooser dialogue for courses. - * - * @constructor - * @class M.course.modchooser - * @extends M.core.chooserdialogue - */ -var MODCHOOSER = function() { - MODCHOOSER.superclass.constructor.apply(this, arguments); -}; - -Y.extend(MODCHOOSER, M.core.chooserdialogue, { - /** - * The current section ID. - * - * @property sectionid - * @private - * @type Number - * @default null - */ - sectionid: null, - - /** - * Set up the activity chooser. - * - * @method initializer - */ - initializer: function() { - var sectionclass = M.course.format.get_sectionwrapperclass(); - if (sectionclass) { - CSS.SECTION = '.' + sectionclass; - } - var dialogue = Y.one('.chooserdialoguebody'); - var header = Y.one('.choosertitle'); - var params = {}; - this.setup_chooser_dialogue(dialogue, header, params); - - // Initialize existing sections and register for dynamically created sections - this.setup_for_section(); - M.course.coursebase.register_module(this); - }, - - /** - * Update any section areas within the scope of the specified - * selector with AJAX equivalents - * - * @method setup_for_section - * @param baseselector The selector to limit scope to - */ - setup_for_section: function(baseselector) { - if (!baseselector) { - baseselector = CSS.PAGECONTENT; - } - - // Setup for site topics - Y.one(baseselector).all(CSS.SITETOPIC).each(function(section) { - this._setup_for_section(section); - }, this); - - // Setup for standard course topics - if (CSS.SECTION) { - Y.one(baseselector).all(CSS.SECTION).each(function(section) { - this._setup_for_section(section); - }, this); - } - - // Setup for the block site menu - Y.one(baseselector).all(CSS.SITEMENU).each(function(section) { - this._setup_for_section(section); - }, this); - }, - - /** - * Update any section areas within the scope of the specified - * selector with AJAX equivalents - * - * @method _setup_for_section - * @private - * @param baseselector The selector to limit scope to - */ - _setup_for_section: function(section) { - var chooserspan = section.one(CSS.SECTIONMODCHOOSER); - if (!chooserspan) { - return; - } - var chooserlink = Y.Node.create(""); - chooserspan.get('children').each(function(node) { - chooserlink.appendChild(node); - }); - chooserspan.insertBefore(chooserlink); - chooserlink.on('click', this.display_mod_chooser, this); - }, - /** - * Display the module chooser - * - * @method display_mod_chooser - * @param {EventFacade} e Triggering Event - */ - display_mod_chooser: function(e) { - // Set the section for this version of the dialogue - if (e.target.ancestor(CSS.SITETOPIC)) { - // The site topic has a sectionid of 1 - this.sectionid = 1; - } else if (e.target.ancestor(CSS.SECTION)) { - var section = e.target.ancestor(CSS.SECTION); - this.sectionid = section.get('id').replace('section-', ''); - } else if (e.target.ancestor(CSS.SITEMENU)) { - // The block site menu has a sectionid of 0 - this.sectionid = 0; - } - this.display_chooser(e); - }, - - /** - * Helper function to set the value of a hidden radio button when a - * selection is made. - * - * @method option_selected - * @param {String} thisoption The selected option value - * @private - */ - option_selected: function(thisoption) { - // Add the sectionid to the URL. - this.hiddenRadioValue.setAttrs({ - name: 'jump', - value: thisoption.get('value') + '§ion=' + this.sectionid - }); - } -}, -{ - NAME: MODCHOOSERNAME, - ATTRS: { - /** - * The maximum height (in pixels) of the activity chooser. - * - * @attribute maxheight - * @type Number - * @default 800 - */ - maxheight: { - value: 800 - } - } -}); -M.course = M.course || {}; -M.course.init_chooser = function(config) { - return new MODCHOOSER(config); -}; diff --git a/course/yui/src/modchooser/meta/modchooser.json b/course/yui/src/modchooser/meta/modchooser.json deleted file mode 100644 index 2ef461b2662..00000000000 --- a/course/yui/src/modchooser/meta/modchooser.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "moodle-course-modchooser": { - "requires": [ - "moodle-core-chooserdialogue", - "moodle-course-coursebase" - ] - } -} diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 6c17f58459d..5010450bcb5 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -43,6 +43,7 @@ $string['activityselect'] = 'Select this activity to be moved elsewhere'; $string['activitysince'] = 'Activity since {$a}'; $string['activitytypetitle'] = '{$a->activity} - {$a->type}'; $string['activityweighted'] = 'Activity per user'; +$string['actionsfor'] = 'Actions for {$a}'; $string['add'] = 'Add'; $string['addactivity'] = 'Add an activity...'; $string['addactivitytosection'] = 'Add an activity to section \'{$a}\''; @@ -58,6 +59,7 @@ $string['addedtogroupnot'] = 'Not added to group "{$a}"'; $string['addedtogroupnotenrolled'] = 'Not added to group "{$a}", because not enrolled in course'; $string['addfilehere'] = 'Add file(s) here'; $string['addinganew'] = 'Adding a new {$a}'; +$string['addnew'] = 'Add a new {$a}'; $string['addinganewto'] = 'Adding a new {$a->what} to {$a->to}'; $string['addingdatatoexisting'] = 'Adding data to existing'; $string['additionalnames'] = 'Additional names'; diff --git a/lib/db/services.php b/lib/db/services.php index de2a2003916..50422b27bbc 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -629,6 +629,14 @@ $functions = array( 'type' => 'read', 'ajax' => true, ), + 'core_course_get_activity_picker_info' => array( + 'classname' => 'core_course_external', + 'methodname' => 'fetch_modules_activity_chooser', + 'classpath' => 'course/externallib.php', + 'description' => 'Fetch all the module information for the activity picker', + 'type' => 'read', + 'ajax' => true, + ), 'core_enrol_get_course_enrolment_methods' => array( 'classname' => 'core_enrol_external', 'methodname' => 'get_course_enrolment_methods', diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index 2c3ad22db47..8fd7052ab0c 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -1489,11 +1489,6 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { padding-top: 1px; } -.chooserdialogue-course-modchooser .modicon .icon { - width: 24px; - height: 24px; - font-size: 24px; -} @include media-breakpoint-down(xs) { .jsenabled .choosercontainer #chooseform .alloptions { @@ -1506,6 +1501,124 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { } } +/** + * Module chooser dialogue (moodle-core-chooserdialogue) + * + * This CSS belong to the chooser dialogue which should work both with, and + * without javascript enabled + */ +.modchooser .modal-body { + padding: 0; + height: 590px; + overflow-y: auto; + + .loading-icon { + opacity: 1; + .icon { + display: block; + font-size: 3em; + height: 1em; + width: 1em; + margin: 5em auto; + } + } +} + +.modchoosercontainer.noscroll { + overflow-y: hidden; +} + +.modchoosercontainer .optionscontainer { + overflow-x: hidden; + .option { + // Six items per line. + flex-basis: 16%; + .optionactions { + .optionaction { + cursor: pointer; + margin: 0.2rem; + color: $gray-600; + i { + margin: 0; + } + } + } + .optioninfo { + a { + color: $gray-700; + &:hover { + text-decoration: none; + } + .optionname { + margin-top: 0.5em; + } + .optionicon { + .icon { + margin: 0; + padding: 0; + width: 32px; + height: 32px; + font-size: 32px; + } + } + } + } + } +} + +.modchooser .modal-body .optionsummary { + background-color: $white; + overflow-x: hidden; + overflow-y: auto; + line-height: 2em; + height: 590px; + + .content { + overflow-y: auto; + .heading { + .icon { + height: 32px; + width: 32px; + font-size: 32px; + padding: 0; + } + } + } + + .actions { + border-top: 1px solid $gray-300; + background: $white; + } +} + +@include media-breakpoint-down(lg) { + .modchoosercontainer .optionscontainer .option { + // Five items per line. + flex-basis: 20%; + } +} + +@include media-breakpoint-down(xs) { + .path-course-view .modal-dialog.modal-lg, + .path-course-view .modal-content, + .modchooser .modal-body, + .modchooser .modal-body .carousel, + .modchooser .modal-body .carousel-inner, + .modchooser .modal-body .carousel-item, + .modchooser .modal-body .optionsummary, + .modchoosercontainer, + .optionscontainer { + height: 100%; + } + .path-course-view .modal-dialog.modal-lg { + margin: 0; + } + .modchoosercontainer .optionscontainer .option { + // Four items per line. + flex-basis: 25%; + } +} + /* Form element: listing */ .formlistingradio { padding-bottom: 25px; diff --git a/theme/boost/scss/moodle/course.scss b/theme/boost/scss/moodle/course.scss index d068d112def..57c0b1922e6 100644 --- a/theme/boost/scss/moodle/course.scss +++ b/theme/boost/scss/moodle/course.scss @@ -1,10 +1,5 @@ /* course.less */ /* COURSE CONTENT */ -.section-modchooser-link img { - margin-right: 0.5rem; - width: 16px; - height: 16px; -} .section_add_menus { text-align: right; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index bc138fa075c..330360e41f1 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -10637,11 +10637,6 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { margin-top: -1px; padding-top: 1px; } -.chooserdialogue-course-modchooser .modicon .icon { - width: 24px; - height: 24px; - font-size: 24px; } - @media (max-width: 575.98px) { .jsenabled .choosercontainer #chooseform .alloptions { max-width: 100%; } @@ -10649,6 +10644,88 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { .jsenabled .choosercontainer #chooseform .typesummary { position: static; } } +/** + * Module chooser dialogue (moodle-core-chooserdialogue) + * + * This CSS belong to the chooser dialogue which should work both with, and + * without javascript enabled + */ +.modchooser .modal-body { + padding: 0; + height: 590px; + overflow-y: auto; } + .modchooser .modal-body .loading-icon { + opacity: 1; } + .modchooser .modal-body .loading-icon .icon { + display: block; + font-size: 3em; + height: 1em; + width: 1em; + margin: 5em auto; } + +.modchoosercontainer.noscroll { + overflow-y: hidden; } + +.modchoosercontainer .optionscontainer { + overflow-x: hidden; } + .modchoosercontainer .optionscontainer .option { + flex-basis: 16%; } + .modchoosercontainer .optionscontainer .option .optionactions .optionaction { + cursor: pointer; + margin: 0.2rem; + color: #868e96; } + .modchoosercontainer .optionscontainer .option .optionactions .optionaction i { + margin: 0; } + .modchoosercontainer .optionscontainer .option .optioninfo a { + color: #495057; } + .modchoosercontainer .optionscontainer .option .optioninfo a:hover { + text-decoration: none; } + .modchoosercontainer .optionscontainer .option .optioninfo a .optionname { + margin-top: 0.5em; } + .modchoosercontainer .optionscontainer .option .optioninfo a .optionicon .icon { + margin: 0; + padding: 0; + width: 32px; + height: 32px; + font-size: 32px; } + +.modchooser .modal-body .optionsummary { + background-color: #fff; + overflow-x: hidden; + overflow-y: auto; + line-height: 2em; + height: 590px; } + .modchooser .modal-body .optionsummary .content { + overflow-y: auto; } + .modchooser .modal-body .optionsummary .content .heading .icon { + height: 32px; + width: 32px; + font-size: 32px; + padding: 0; } + .modchooser .modal-body .optionsummary .actions { + border-top: 1px solid #dee2e6; + background: #fff; } + +@media (max-width: 1199.98px) { + .modchoosercontainer .optionscontainer .option { + flex-basis: 20%; } } + +@media (max-width: 575.98px) { + .path-course-view .modal-dialog.modal-lg, + .path-course-view .modal-content, + .modchooser .modal-body, + .modchooser .modal-body .carousel, + .modchooser .modal-body .carousel-inner, + .modchooser .modal-body .carousel-item, + .modchooser .modal-body .optionsummary, + .modchoosercontainer, + .optionscontainer { + height: 100%; } + .path-course-view .modal-dialog.modal-lg { + margin: 0; } + .modchoosercontainer .optionscontainer .option { + flex-basis: 25%; } } + /* Form element: listing */ .formlistingradio { padding-bottom: 25px; @@ -12421,11 +12498,6 @@ table.calendartable caption { /* course.less */ /* COURSE CONTENT */ -.section-modchooser-link img { - margin-right: 0.5rem; - width: 16px; - height: 16px; } - .section_add_menus { text-align: right; clear: both; } diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index fd31a4ed147..4802c1437a9 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -10844,11 +10844,6 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { margin-top: -1px; padding-top: 1px; } -.chooserdialogue-course-modchooser .modicon .icon { - width: 24px; - height: 24px; - font-size: 24px; } - @media (max-width: 575.98px) { .jsenabled .choosercontainer #chooseform .alloptions { max-width: 100%; } @@ -10856,6 +10851,88 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { .jsenabled .choosercontainer #chooseform .typesummary { position: static; } } +/** + * Module chooser dialogue (moodle-core-chooserdialogue) + * + * This CSS belong to the chooser dialogue which should work both with, and + * without javascript enabled + */ +.modchooser .modal-body { + padding: 0; + height: 590px; + overflow-y: auto; } + .modchooser .modal-body .loading-icon { + opacity: 1; } + .modchooser .modal-body .loading-icon .icon { + display: block; + font-size: 3em; + height: 1em; + width: 1em; + margin: 5em auto; } + +.modchoosercontainer.noscroll { + overflow-y: hidden; } + +.modchoosercontainer .optionscontainer { + overflow-x: hidden; } + .modchoosercontainer .optionscontainer .option { + flex-basis: 16%; } + .modchoosercontainer .optionscontainer .option .optionactions .optionaction { + cursor: pointer; + margin: 0.2rem; + color: #868e96; } + .modchoosercontainer .optionscontainer .option .optionactions .optionaction i { + margin: 0; } + .modchoosercontainer .optionscontainer .option .optioninfo a { + color: #495057; } + .modchoosercontainer .optionscontainer .option .optioninfo a:hover { + text-decoration: none; } + .modchoosercontainer .optionscontainer .option .optioninfo a .optionname { + margin-top: 0.5em; } + .modchoosercontainer .optionscontainer .option .optioninfo a .optionicon .icon { + margin: 0; + padding: 0; + width: 32px; + height: 32px; + font-size: 32px; } + +.modchooser .modal-body .optionsummary { + background-color: #fff; + overflow-x: hidden; + overflow-y: auto; + line-height: 2em; + height: 590px; } + .modchooser .modal-body .optionsummary .content { + overflow-y: auto; } + .modchooser .modal-body .optionsummary .content .heading .icon { + height: 32px; + width: 32px; + font-size: 32px; + padding: 0; } + .modchooser .modal-body .optionsummary .actions { + border-top: 1px solid #dee2e6; + background: #fff; } + +@media (max-width: 1199.98px) { + .modchoosercontainer .optionscontainer .option { + flex-basis: 20%; } } + +@media (max-width: 575.98px) { + .path-course-view .modal-dialog.modal-lg, + .path-course-view .modal-content, + .modchooser .modal-body, + .modchooser .modal-body .carousel, + .modchooser .modal-body .carousel-inner, + .modchooser .modal-body .carousel-item, + .modchooser .modal-body .optionsummary, + .modchoosercontainer, + .optionscontainer { + height: 100%; } + .path-course-view .modal-dialog.modal-lg { + margin: 0; } + .modchoosercontainer .optionscontainer .option { + flex-basis: 25%; } } + /* Form element: listing */ .formlistingradio { padding-bottom: 25px; @@ -12633,11 +12710,6 @@ table.calendartable caption { /* course.less */ /* COURSE CONTENT */ -.section-modchooser-link img { - margin-right: 0.5rem; - width: 16px; - height: 16px; } - .section_add_menus { text-align: right; clear: both; } diff --git a/version.php b/version.php index 8f043162503..fa4b3f0e69d 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2020020700.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2020020700.02; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '3.9dev (Build: 20200207)'; // Human-friendly version name -- 2.17.1