diff --git a/amd/build/package_search/components/pagination.min.js b/amd/build/package_search/components/pagination.min.js new file mode 100644 index 00000000..88e23b1a --- /dev/null +++ b/amd/build/package_search/components/pagination.min.js @@ -0,0 +1,3 @@ +define("qtype_questionpy/package_search/components/pagination",["exports","qtype_questionpy/package_search/component"],(function(_exports,_component){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_component=(obj=_component)&&obj.__esModule?obj:{default:obj};class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,".data:updated"),handler:this.updateCurrentPage}]}create(descriptor){this.category=descriptor.category,this.selectors={PREVIOUS_BUTTON:'[data-for="pagination-previous"]',NEXT_BUTTON:'[data-for="pagination-next"]',CURRENT:'[data-for="pagination-current"]'}}stateReady(){this.addEventListener(this.getElement(this.selectors.PREVIOUS_BUTTON),"click",this.previousPage),this.addEventListener(this.getElement(this.selectors.NEXT_BUTTON),"click",this.nextPage)}_getCurrentPage(){return this.getState()[this.category].page}_getLastPage(){const state=this.getState()[this.category];return 0===state.data.total?0:Math.floor((state.data.total-1)/this.reactive.options.limit)}updateCurrentPage(){const currentPage=this._getCurrentPage(),lastPage=this._getLastPage();this.getElement(this.selectors.CURRENT).innerHTML="".concat(currentPage+1," / ").concat(lastPage+1),0===currentPage?this.getElement(this.selectors.PREVIOUS_BUTTON).classList.add("disabled"):this.getElement(this.selectors.PREVIOUS_BUTTON).classList.remove("disabled"),currentPage===lastPage?this.getElement(this.selectors.NEXT_BUTTON).classList.add("disabled"):this.getElement(this.selectors.NEXT_BUTTON).classList.remove("disabled")}previousPage(){const currentPage=this._getCurrentPage();currentPage>0&&this.reactive.dispatch("changePage",this.category,currentPage-1)}nextPage(){const currentPage=this._getCurrentPage();currentPage.\n */\n\n/**\n * @module qtype_questionpy/package_search/components/pagination\n */\n\nimport Component from 'qtype_questionpy/package_search/component';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `${this.category}.data:updated`, handler: this.updateCurrentPage},\n ];\n }\n\n create(descriptor) {\n this.category = descriptor.category;\n this.selectors = {\n PREVIOUS_BUTTON: `[data-for=\"pagination-previous\"]`,\n NEXT_BUTTON: `[data-for=\"pagination-next\"]`,\n CURRENT: `[data-for=\"pagination-current\"]`,\n };\n }\n\n stateReady() {\n this.addEventListener(this.getElement(this.selectors.PREVIOUS_BUTTON), \"click\", this.previousPage);\n this.addEventListener(this.getElement(this.selectors.NEXT_BUTTON), \"click\", this.nextPage);\n }\n\n /**\n * Returns the current page number starting form zero.\n *\n * @returns {number}\n * @private\n */\n _getCurrentPage() {\n return this.getState()[this.category].page;\n }\n\n /**\n * Returns the last page number starting from zero.\n *\n * @returns {number}\n * @private\n */\n _getLastPage() {\n const state = this.getState()[this.category];\n if (state.data.total === 0) {\n return 0;\n }\n return Math.floor((state.data.total - 1) / this.reactive.options.limit);\n }\n\n /**\n * Updates the current page status and disables or enables the previous and next button.\n */\n updateCurrentPage() {\n const currentPage = this._getCurrentPage();\n const lastPage = this._getLastPage();\n\n this.getElement(this.selectors.CURRENT).innerHTML = `${currentPage + 1} / ${lastPage + 1}`;\n\n if (currentPage === 0) {\n this.getElement(this.selectors.PREVIOUS_BUTTON).classList.add(\"disabled\");\n } else {\n this.getElement(this.selectors.PREVIOUS_BUTTON).classList.remove(\"disabled\");\n }\n if (currentPage === lastPage) {\n this.getElement(this.selectors.NEXT_BUTTON).classList.add(\"disabled\");\n } else {\n this.getElement(this.selectors.NEXT_BUTTON).classList.remove(\"disabled\");\n }\n }\n\n /**\n * Dispatches mutation that retrieves packages from the previous page.\n */\n previousPage() {\n const currentPage = this._getCurrentPage();\n if (currentPage > 0) {\n this.reactive.dispatch(\"changePage\", this.category, currentPage - 1);\n }\n }\n\n /**\n * Dispatches mutation that retrieves packages from the next page.\n */\n nextPage() {\n const currentPage = this._getCurrentPage();\n const lastPage = this._getLastPage();\n if (currentPage < lastPage) {\n this.reactive.dispatch(\"changePage\", this.category, currentPage + 1);\n }\n }\n\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","updateCurrentPage","create","descriptor","selectors","PREVIOUS_BUTTON","NEXT_BUTTON","CURRENT","stateReady","addEventListener","getElement","previousPage","nextPage","_getCurrentPage","getState","page","_getLastPage","state","data","total","Math","floor","reactive","options","limit","currentPage","lastPage","innerHTML","classList","add","remove","dispatch"],"mappings":"mUAuB6BA,mBACzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,0BAAyBC,QAASF,KAAKG,oBAI/DC,OAAOC,iBACEJ,SAAWI,WAAWJ,cACtBK,UAAY,CACbC,mDACAC,2CACAC,2CAIRC,kBACSC,iBAAiBX,KAAKY,WAAWZ,KAAKM,UAAUC,iBAAkB,QAASP,KAAKa,mBAChFF,iBAAiBX,KAAKY,WAAWZ,KAAKM,UAAUE,aAAc,QAASR,KAAKc,UASrFC,yBACWf,KAAKgB,WAAWhB,KAAKC,UAAUgB,KAS1CC,qBACUC,MAAQnB,KAAKgB,WAAWhB,KAAKC,iBACV,IAArBkB,MAAMC,KAAKC,MACJ,EAEJC,KAAKC,OAAOJ,MAAMC,KAAKC,MAAQ,GAAKrB,KAAKwB,SAASC,QAAQC,OAMrEvB,0BACUwB,YAAc3B,KAAKe,kBACnBa,SAAW5B,KAAKkB,oBAEjBN,WAAWZ,KAAKM,UAAUG,SAASoB,oBAAeF,YAAc,gBAAOC,SAAW,GAEnE,IAAhBD,iBACKf,WAAWZ,KAAKM,UAAUC,iBAAiBuB,UAAUC,IAAI,iBAEzDnB,WAAWZ,KAAKM,UAAUC,iBAAiBuB,UAAUE,OAAO,YAEjEL,cAAgBC,cACXhB,WAAWZ,KAAKM,UAAUE,aAAasB,UAAUC,IAAI,iBAErDnB,WAAWZ,KAAKM,UAAUE,aAAasB,UAAUE,OAAO,YAOrEnB,qBACUc,YAAc3B,KAAKe,kBACrBY,YAAc,QACTH,SAASS,SAAS,aAAcjC,KAAKC,SAAU0B,YAAc,GAO1Eb,iBACUa,YAAc3B,KAAKe,kBAErBY,YADa3B,KAAKkB,qBAEbM,SAASS,SAAS,aAAcjC,KAAKC,SAAU0B,YAAc"} \ No newline at end of file diff --git a/amd/build/package_search/components/tab_content.min.js b/amd/build/package_search/components/tab_content.min.js index 1bfa8448..44e63a0b 100644 --- a/amd/build/package_search/components/tab_content.min.js +++ b/amd/build/package_search/components/tab_content.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/components/tab_content",["exports","core/templates","core/notification","qtype_questionpy/package_search/component"],(function(_exports,templates,_notification,_component){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,templates=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(templates),_notification=_interopRequireDefault(_notification),_component=_interopRequireDefault(_component);class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,":updated"),handler:this.render}]}async create(descriptor){this.category=descriptor.category,this.selectors={CONTENT:".qpy-tab-content"}}_getPackageTemplatesPromise(contexts){let promises=[];for(const context of contexts){const promise=templates.renderForPromise("qtype_questionpy/package/package_selection",context);promises.push(promise)}return Promise.all(promises)}async render(){try{const state=this.getState()[this.category],packageTemplates=await this._getPackageTemplatesPromise(state.data.packages),element=this.getElement(this.selectors.CONTENT);element.innerHTML="";for(const{html:html,js:js}of packageTemplates)templates.appendNodeContents(element,html,js)}catch(exception){await _notification.default.exception(exception)}}}return _exports.default=_default,_exports.default})); +define("qtype_questionpy/package_search/components/tab_content",["exports","core/templates","core/notification","qtype_questionpy/package_search/component","qtype_questionpy/package_search/components/pagination"],(function(_exports,templates,_notification,_component,_pagination){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,templates=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(templates),_notification=_interopRequireDefault(_notification),_component=_interopRequireDefault(_component),_pagination=_interopRequireDefault(_pagination);class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,":updated"),handler:this.render}]}async create(descriptor){this.category=descriptor.category,this.selectors={CONTENT:".qpy-tab-content",PAGINATION:'[data-for="pagination"]'},new _pagination.default({element:this.getElement(this.selectors.PAGINATION),name:"pagiation_".concat(this.category),reactive:descriptor.reactive,category:this.category})}_getPackageTemplatesPromise(contexts){let promises=[];for(const context of contexts){const promise=templates.renderForPromise("qtype_questionpy/package/package_selection",context);promises.push(promise)}return Promise.all(promises)}async render(){try{const state=this.getState()[this.category],packageTemplates=await this._getPackageTemplatesPromise(state.data.packages),element=this.getElement(this.selectors.CONTENT);element.innerHTML="";for(const{html:html,js:js}of packageTemplates)templates.appendNodeContents(element,html,js)}catch(exception){await _notification.default.exception(exception)}}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=tab_content.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/components/tab_content.min.js.map b/amd/build/package_search/components/tab_content.min.js.map index 67f35ff2..8df549c6 100644 --- a/amd/build/package_search/components/tab_content.min.js.map +++ b/amd/build/package_search/components/tab_content.min.js.map @@ -1 +1 @@ -{"version":3,"file":"tab_content.min.js","sources":["../../../src/package_search/components/tab_content.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/tab_content\n */\n\nimport * as templates from 'core/templates';\nimport Notification from 'core/notification';\nimport Component from 'qtype_questionpy/package_search/component';\n\nexport default class extends Component {\n\n getWatchers() {\n return [\n {watch: `${this.category}:updated`, handler: this.render},\n ];\n }\n\n async create(descriptor) {\n this.category = descriptor.category;\n this.selectors = {\n CONTENT: \".qpy-tab-content\",\n };\n }\n\n /**\n * Groups render promises for package templates.\n *\n * @param {Object[]} contexts\n * @returns {Promise}\n * @private\n */\n _getPackageTemplatesPromise(contexts) {\n let promises = [];\n for (const context of contexts) {\n const promise = templates.renderForPromise(\"qtype_questionpy/package/package_selection\", context);\n promises.push(promise);\n }\n return Promise.all(promises);\n }\n\n /**\n * Renders every package inside the current state.\n */\n async render() {\n try {\n const state = this.getState()[this.category];\n const packageTemplates = await this._getPackageTemplatesPromise(state.data.packages);\n const element = this.getElement(this.selectors.CONTENT);\n element.innerHTML = \"\";\n for (const {html, js} of packageTemplates) {\n templates.appendNodeContents(element, html, js);\n }\n } catch (exception) {\n await Notification.exception(exception);\n }\n }\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","render","descriptor","selectors","CONTENT","_getPackageTemplatesPromise","contexts","promises","context","promise","templates","renderForPromise","push","Promise","all","state","getState","packageTemplates","data","packages","element","getElement","innerHTML","html","js","appendNodeContents","exception","Notification"],"mappings":"o6CAyB6BA,mBAEzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,qBAAoBC,QAASF,KAAKG,sBAI7CC,iBACJH,SAAWG,WAAWH,cACtBI,UAAY,CACbC,QAAS,oBAWjBC,4BAA4BC,cACpBC,SAAW,OACV,MAAMC,WAAWF,SAAU,OACtBG,QAAUC,UAAUC,iBAAiB,6CAA8CH,SACzFD,SAASK,KAAKH,gBAEXI,QAAQC,IAAIP,mCAQTQ,MAAQjB,KAAKkB,WAAWlB,KAAKC,UAC7BkB,uBAAyBnB,KAAKO,4BAA4BU,MAAMG,KAAKC,UACrEC,QAAUtB,KAAKuB,WAAWvB,KAAKK,UAAUC,SAC/CgB,QAAQE,UAAY,OACf,MAAMC,KAACA,KAADC,GAAOA,MAAOP,iBACrBP,UAAUe,mBAAmBL,QAASG,KAAMC,IAElD,MAAOE,iBACCC,sBAAaD,UAAUA"} \ No newline at end of file +{"version":3,"file":"tab_content.min.js","sources":["../../../src/package_search/components/tab_content.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/tab_content\n */\n\nimport * as templates from 'core/templates';\nimport Notification from 'core/notification';\nimport Component from 'qtype_questionpy/package_search/component';\nimport Pagination from 'qtype_questionpy/package_search/components/pagination';\n\nexport default class extends Component {\n\n getWatchers() {\n return [\n {watch: `${this.category}:updated`, handler: this.render},\n ];\n }\n\n async create(descriptor) {\n this.category = descriptor.category;\n this.selectors = {\n CONTENT: \".qpy-tab-content\",\n PAGINATION: '[data-for=\"pagination\"]',\n };\n\n // Register pagination.\n new Pagination({\n element: this.getElement(this.selectors.PAGINATION),\n name: `pagiation_${this.category}`,\n reactive: descriptor.reactive,\n category: this.category,\n });\n }\n\n /**\n * Groups render promises for package templates.\n *\n * @param {Object[]} contexts\n * @returns {Promise}\n * @private\n */\n _getPackageTemplatesPromise(contexts) {\n let promises = [];\n for (const context of contexts) {\n const promise = templates.renderForPromise(\"qtype_questionpy/package/package_selection\", context);\n promises.push(promise);\n }\n return Promise.all(promises);\n }\n\n /**\n * Renders every package inside the current state.\n */\n async render() {\n try {\n const state = this.getState()[this.category];\n const packageTemplates = await this._getPackageTemplatesPromise(state.data.packages);\n const element = this.getElement(this.selectors.CONTENT);\n element.innerHTML = \"\";\n for (const {html, js} of packageTemplates) {\n templates.appendNodeContents(element, html, js);\n }\n } catch (exception) {\n await Notification.exception(exception);\n }\n }\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","render","descriptor","selectors","CONTENT","PAGINATION","Pagination","element","getElement","name","reactive","_getPackageTemplatesPromise","contexts","promises","context","promise","templates","renderForPromise","push","Promise","all","state","getState","packageTemplates","data","packages","innerHTML","html","js","appendNodeContents","exception","Notification"],"mappings":"whDA0B6BA,mBAEzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,qBAAoBC,QAASF,KAAKG,sBAI7CC,iBACJH,SAAWG,WAAWH,cACtBI,UAAY,CACbC,QAAS,mBACTC,WAAY,+BAIZC,oBAAW,CACXC,QAAST,KAAKU,WAAWV,KAAKK,UAAUE,YACxCI,yBAAmBX,KAAKC,UACxBW,SAAUR,WAAWQ,SACrBX,SAAUD,KAAKC,WAWvBY,4BAA4BC,cACpBC,SAAW,OACV,MAAMC,WAAWF,SAAU,OACtBG,QAAUC,UAAUC,iBAAiB,6CAA8CH,SACzFD,SAASK,KAAKH,gBAEXI,QAAQC,IAAIP,mCAQTQ,MAAQvB,KAAKwB,WAAWxB,KAAKC,UAC7BwB,uBAAyBzB,KAAKa,4BAA4BU,MAAMG,KAAKC,UACrElB,QAAUT,KAAKU,WAAWV,KAAKK,UAAUC,SAC/CG,QAAQmB,UAAY,OACf,MAAMC,KAACA,KAADC,GAAOA,MAAOL,iBACrBP,UAAUa,mBAAmBtB,QAASoB,KAAMC,IAElD,MAAOE,iBACCC,sBAAaD,UAAUA"} \ No newline at end of file diff --git a/amd/build/package_search/mutations.min.js b/amd/build/package_search/mutations.min.js index 3d123c93..d39fdb44 100644 --- a/amd/build/package_search/mutations.min.js +++ b/amd/build/package_search/mutations.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/mutations",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);return _exports.default=class{constructor(options){this.options=options}_setLoading(stateManager,loading){stateManager.setReadOnly(!1),stateManager.state.general.loading=loading,stateManager.setReadOnly(!0)}_getSearchPackagesInCategoriesPromise(args){let clonedArgs={...args},methods=[];for(var _len=arguments.length,categories=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)categories[_key-1]=arguments[_key];for(const category of categories){let method={methodname:"qtype_questionpy_search_packages",args:clonedArgs};method.args.category=category,methods.push(method),clonedArgs={...args}}return _ajax.default.call(methods)}async searchPackages(stateManager){let args=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;const state=stateManager.state;args=args||{};let mergedArgs={query:args.query||state.general.query,tags:[],sort:args.sort||state.general.sort,order:args.order||state.general.order,limit:this.options.limit,page:0,contextid:this.options.contextid};this._setLoading(stateManager,!0);try{let[all,recentlyused,favourites,mine]=await this._getSearchPackagesInCategoriesPromise(mergedArgs,"all","recentlyused","favourites","mine");stateManager.setReadOnly(!1),state.all.data=await all,state.recentlyused.data=await recentlyused,state.favourites.data=await favourites,state.mine.data=await mine,stateManager.setReadOnly(!0)}catch(exception){await _notification.default.exception(exception)}this._setLoading(stateManager,!1)}async searchPackagesByQuery(stateManager,query){await this.searchPackages(stateManager,{query:query})}},_exports.default})); +define("qtype_questionpy/package_search/mutations",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);return _exports.default=class{constructor(options){this.options=options}_setLoading(stateManager,loading){stateManager.setReadOnly(!1),stateManager.state.general.loading=loading,stateManager.setReadOnly(!0)}_getSearchPackagesInCategoriesPromise(args,categories){let clonedArgs={...args},methods=[];for(const category of categories){let method={methodname:"qtype_questionpy_search_packages",args:clonedArgs};method.args.category=category,methods.push(method),clonedArgs={...args}}return _ajax.default.call(methods)}async searchPackages(stateManager){let args=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,categories=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const state=stateManager.state;categories=categories||["all","recentlyused","favourites","mine"],args=args||{};let mergedArgs={query:"string"==typeof args.query?args.query:state.general.query,tags:[],sort:args.sort||state.general.sort,order:args.order||state.general.order,limit:this.options.limit,page:args.page||0,contextid:this.options.contextid};this._setLoading(stateManager,!0);try{let results=await this._getSearchPackagesInCategoriesPromise(mergedArgs,categories);stateManager.setReadOnly(!1);for(const[index,category]of categories.entries())state[category].data=await results[index],state[category].page=mergedArgs.page;state.general.query=mergedArgs.query,state.general.tags=mergedArgs.tags,state.general.sort=mergedArgs.sort,state.general.order=mergedArgs.order,stateManager.setReadOnly(!0)}catch(exception){await _notification.default.exception(exception)}this._setLoading(stateManager,!1)}async searchPackagesByQuery(stateManager,query){await this.searchPackages(stateManager,{query:query})}async changePage(stateManager,category,page){await this.searchPackages(stateManager,{page:page},[category])}},_exports.default})); //# sourceMappingURL=mutations.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/mutations.min.js.map b/amd/build/package_search/mutations.min.js.map index ef0029aa..294ad25d 100644 --- a/amd/build/package_search/mutations.min.js.map +++ b/amd/build/package_search/mutations.min.js.map @@ -1 +1 @@ -{"version":3,"file":"mutations.min.js","sources":["../../src/package_search/mutations.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/mutations\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport default class {\n\n /**\n * @param {{contextid: number, limit: number}} options\n */\n constructor(options) {\n this.options = options;\n }\n\n /**\n * Sets search current status.\n *\n * @param {StateManager} stateManager\n * @param {boolean} loading\n */\n _setLoading(stateManager, loading) {\n stateManager.setReadOnly(false);\n stateManager.state.general.loading = loading;\n stateManager.setReadOnly(true);\n }\n\n /**\n * Search through given categories with same arguments.\n *\n * @param {Object} args the arguments\n * @param {string} categories\n * @returns {any}\n * @private\n */\n _getSearchPackagesInCategoriesPromise(args, ...categories) {\n let clonedArgs = {...args};\n let methods = [];\n for (const category of categories) {\n let method = {\n methodname: \"qtype_questionpy_search_packages\",\n args: clonedArgs,\n };\n method.args.category = category;\n methods.push(method);\n clonedArgs = {...args};\n }\n\n return Ajax.call(methods);\n }\n\n /**\n * Used to search packages.\n *\n * Missing arguments are taken from the current state.\n *\n * @param {StateManager} stateManager\n * @param {Object|null} args\n */\n async searchPackages(stateManager, args = null) {\n const state = stateManager.state;\n\n // Missing arguments are taken from the current state.\n args = args || {};\n let mergedArgs = {\n query: args.query || state.general.query,\n tags: [], // TODO.\n sort: args.sort || state.general.sort,\n order: args.order || state.general.order,\n limit: this.options.limit,\n page: 0,\n contextid: this.options.contextid,\n };\n\n this._setLoading(stateManager, true);\n\n try {\n let [all, recentlyused, favourites, mine] = await this._getSearchPackagesInCategoriesPromise(mergedArgs,\n \"all\", \"recentlyused\", \"favourites\", \"mine\");\n\n stateManager.setReadOnly(false);\n state.all.data = await all;\n state.recentlyused.data = await recentlyused;\n state.favourites.data = await favourites;\n state.mine.data = await mine;\n stateManager.setReadOnly(true);\n } catch (exception) {\n await Notification.exception(exception);\n }\n\n this._setLoading(stateManager, false);\n }\n\n /**\n * Used to search for packages only by providing a query.\n *\n * @param {StateManager} stateManager\n * @param {string} query\n */\n async searchPackagesByQuery(stateManager, query) {\n await this.searchPackages(stateManager, {query: query});\n }\n}\n"],"names":["constructor","options","_setLoading","stateManager","loading","setReadOnly","state","general","_getSearchPackagesInCategoriesPromise","args","clonedArgs","methods","categories","category","method","methodname","push","Ajax","call","mergedArgs","query","tags","sort","order","limit","this","page","contextid","all","recentlyused","favourites","mine","data","exception","Notification","searchPackages"],"mappings":"+ZA6BIA,YAAYC,cACHA,QAAUA,QASnBC,YAAYC,aAAcC,SACtBD,aAAaE,aAAY,GACzBF,aAAaG,MAAMC,QAAQH,QAAUA,QACrCD,aAAaE,aAAY,GAW7BG,sCAAsCC,UAC9BC,WAAa,IAAID,MACjBE,QAAU,iCAF6BC,8DAAAA,uCAGtC,MAAMC,YAAYD,WAAY,KAC3BE,OAAS,CACTC,WAAY,mCACZN,KAAMC,YAEVI,OAAOL,KAAKI,SAAWA,SACvBF,QAAQK,KAAKF,QACbJ,WAAa,IAAID,aAGdQ,cAAKC,KAAKP,8BAWAR,kBAAcM,4DAAO,WAChCH,MAAQH,aAAaG,MAG3BG,KAAOA,MAAQ,OACXU,WAAa,CACbC,MAAOX,KAAKW,OAASd,MAAMC,QAAQa,MACnCC,KAAM,GACNC,KAAMb,KAAKa,MAAQhB,MAAMC,QAAQe,KACjCC,MAAOd,KAAKc,OAASjB,MAAMC,QAAQgB,MACnCC,MAAOC,KAAKxB,QAAQuB,MACpBE,KAAM,EACNC,UAAWF,KAAKxB,QAAQ0B,gBAGvBzB,YAAYC,cAAc,WAGtByB,IAAKC,aAAcC,WAAYC,YAAcN,KAAKjB,sCAAsCW,WACzF,MAAO,eAAgB,aAAc,QAEzChB,aAAaE,aAAY,GACzBC,MAAMsB,IAAII,WAAaJ,IACvBtB,MAAMuB,aAAaG,WAAaH,aAChCvB,MAAMwB,WAAWE,WAAaF,WAC9BxB,MAAMyB,KAAKC,WAAaD,KACxB5B,aAAaE,aAAY,GAC3B,MAAO4B,iBACCC,sBAAaD,UAAUA,gBAG5B/B,YAAYC,cAAc,+BASPA,aAAciB,aAChCK,KAAKU,eAAehC,aAAc,CAACiB,MAAOA"} \ No newline at end of file +{"version":3,"file":"mutations.min.js","sources":["../../src/package_search/mutations.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/mutations\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport default class {\n\n /**\n * @param {{contextid: number, limit: number}} options\n */\n constructor(options) {\n this.options = options;\n }\n\n /**\n * Sets search current status.\n *\n * @param {StateManager} stateManager\n * @param {boolean} loading\n */\n _setLoading(stateManager, loading) {\n stateManager.setReadOnly(false);\n stateManager.state.general.loading = loading;\n stateManager.setReadOnly(true);\n }\n\n /**\n * Search through given categories with same arguments.\n *\n * @param {Object} args the arguments\n * @param {string[]} categories\n * @returns {any}\n * @private\n */\n _getSearchPackagesInCategoriesPromise(args, categories) {\n let clonedArgs = {...args};\n let methods = [];\n for (const category of categories) {\n let method = {\n methodname: \"qtype_questionpy_search_packages\",\n args: clonedArgs,\n };\n method.args.category = category;\n methods.push(method);\n clonedArgs = {...args};\n }\n\n return Ajax.call(methods);\n }\n\n /**\n * Used to search packages.\n *\n * Missing arguments are taken from the current state.\n *\n * @param {StateManager} stateManager\n * @param {Object|null} args\n * @param {string[]|null} categories\n */\n async searchPackages(stateManager, args = null, categories = null) {\n const state = stateManager.state;\n\n // Search through every category if no categories are provided.\n categories = categories || [\"all\", \"recentlyused\", \"favourites\", \"mine\"];\n\n // Missing arguments are taken from the current state.\n args = args || {};\n let mergedArgs = {\n query: (typeof args.query === \"string\") ? args.query : state.general.query,\n tags: [], // TODO.\n sort: args.sort || state.general.sort,\n order: args.order || state.general.order,\n limit: this.options.limit,\n page: args.page || 0,\n contextid: this.options.contextid,\n };\n\n this._setLoading(stateManager, true);\n\n try {\n // Get search results for each category.\n let results = await this._getSearchPackagesInCategoriesPromise(mergedArgs, categories);\n\n stateManager.setReadOnly(false);\n // Update category specific data.\n for (const [index, category] of categories.entries()) {\n state[category].data = await results[index];\n state[category].page = mergedArgs.page;\n }\n // Update general data.\n state.general.query = mergedArgs.query;\n state.general.tags = mergedArgs.tags;\n state.general.sort = mergedArgs.sort;\n state.general.order = mergedArgs.order;\n stateManager.setReadOnly(true);\n } catch (exception) {\n await Notification.exception(exception);\n }\n\n this._setLoading(stateManager, false);\n }\n\n /**\n * Used to search for packages only by providing a query.\n *\n * @param {StateManager} stateManager\n * @param {string} query\n */\n async searchPackagesByQuery(stateManager, query) {\n await this.searchPackages(stateManager, {query: query});\n }\n\n /**\n * Used to change the current page of a tab.\n *\n * @param {StateManager} stateManager\n * @param {string} category\n * @param {number} page\n */\n async changePage(stateManager, category, page) {\n await this.searchPackages(stateManager, {page: page}, [category]);\n }\n}\n"],"names":["constructor","options","_setLoading","stateManager","loading","setReadOnly","state","general","_getSearchPackagesInCategoriesPromise","args","categories","clonedArgs","methods","category","method","methodname","push","Ajax","call","mergedArgs","query","tags","sort","order","limit","this","page","contextid","results","index","entries","data","exception","Notification","searchPackages"],"mappings":"+ZA6BIA,YAAYC,cACHA,QAAUA,QASnBC,YAAYC,aAAcC,SACtBD,aAAaE,aAAY,GACzBF,aAAaG,MAAMC,QAAQH,QAAUA,QACrCD,aAAaE,aAAY,GAW7BG,sCAAsCC,KAAMC,gBACpCC,WAAa,IAAIF,MACjBG,QAAU,OACT,MAAMC,YAAYH,WAAY,KAC3BI,OAAS,CACTC,WAAY,mCACZN,KAAME,YAEVG,OAAOL,KAAKI,SAAWA,SACvBD,QAAQI,KAAKF,QACbH,WAAa,IAAIF,aAGdQ,cAAKC,KAAKN,8BAYAT,kBAAcM,4DAAO,KAAMC,kEAAa,WACnDJ,MAAQH,aAAaG,MAG3BI,WAAaA,YAAc,CAAC,MAAO,eAAgB,aAAc,QAGjED,KAAOA,MAAQ,OACXU,WAAa,CACbC,MAA8B,iBAAfX,KAAKW,MAAsBX,KAAKW,MAAQd,MAAMC,QAAQa,MACrEC,KAAM,GACNC,KAAMb,KAAKa,MAAQhB,MAAMC,QAAQe,KACjCC,MAAOd,KAAKc,OAASjB,MAAMC,QAAQgB,MACnCC,MAAOC,KAAKxB,QAAQuB,MACpBE,KAAMjB,KAAKiB,MAAQ,EACnBC,UAAWF,KAAKxB,QAAQ0B,gBAGvBzB,YAAYC,cAAc,WAIvByB,cAAgBH,KAAKjB,sCAAsCW,WAAYT,YAE3EP,aAAaE,aAAY,OAEpB,MAAOwB,MAAOhB,YAAaH,WAAWoB,UACvCxB,MAAMO,UAAUkB,WAAaH,QAAQC,OACrCvB,MAAMO,UAAUa,KAAOP,WAAWO,KAGtCpB,MAAMC,QAAQa,MAAQD,WAAWC,MACjCd,MAAMC,QAAQc,KAAOF,WAAWE,KAChCf,MAAMC,QAAQe,KAAOH,WAAWG,KAChChB,MAAMC,QAAQgB,MAAQJ,WAAWI,MACjCpB,aAAaE,aAAY,GAC3B,MAAO2B,iBACCC,sBAAaD,UAAUA,gBAG5B9B,YAAYC,cAAc,+BASPA,aAAciB,aAChCK,KAAKS,eAAe/B,aAAc,CAACiB,MAAOA,yBAUnCjB,aAAcU,SAAUa,YAC/BD,KAAKS,eAAe/B,aAAc,CAACuB,KAAMA,MAAO,CAACb"} \ No newline at end of file diff --git a/amd/src/package_search/components/pagination.js b/amd/src/package_search/components/pagination.js new file mode 100644 index 00000000..4cdb319e --- /dev/null +++ b/amd/src/package_search/components/pagination.js @@ -0,0 +1,111 @@ +/* + * This file is part of the QuestionPy Moodle plugin - https://questionpy.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 qtype_questionpy/package_search/components/pagination + */ + +import Component from 'qtype_questionpy/package_search/component'; + +export default class extends Component { + getWatchers() { + return [ + {watch: `${this.category}.data:updated`, handler: this.updateCurrentPage}, + ]; + } + + create(descriptor) { + this.category = descriptor.category; + this.selectors = { + PREVIOUS_BUTTON: `[data-for="pagination-previous"]`, + NEXT_BUTTON: `[data-for="pagination-next"]`, + CURRENT: `[data-for="pagination-current"]`, + }; + } + + stateReady() { + this.addEventListener(this.getElement(this.selectors.PREVIOUS_BUTTON), "click", this.previousPage); + this.addEventListener(this.getElement(this.selectors.NEXT_BUTTON), "click", this.nextPage); + } + + /** + * Returns the current page number starting form zero. + * + * @returns {number} + * @private + */ + _getCurrentPage() { + return this.getState()[this.category].page; + } + + /** + * Returns the last page number starting from zero. + * + * @returns {number} + * @private + */ + _getLastPage() { + const state = this.getState()[this.category]; + if (state.data.total === 0) { + return 0; + } + return Math.floor((state.data.total - 1) / this.reactive.options.limit); + } + + /** + * Updates the current page status and disables or enables the previous and next button. + */ + updateCurrentPage() { + const currentPage = this._getCurrentPage(); + const lastPage = this._getLastPage(); + + this.getElement(this.selectors.CURRENT).innerHTML = `${currentPage + 1} / ${lastPage + 1}`; + + if (currentPage === 0) { + this.getElement(this.selectors.PREVIOUS_BUTTON).classList.add("disabled"); + } else { + this.getElement(this.selectors.PREVIOUS_BUTTON).classList.remove("disabled"); + } + if (currentPage === lastPage) { + this.getElement(this.selectors.NEXT_BUTTON).classList.add("disabled"); + } else { + this.getElement(this.selectors.NEXT_BUTTON).classList.remove("disabled"); + } + } + + /** + * Dispatches mutation that retrieves packages from the previous page. + */ + previousPage() { + const currentPage = this._getCurrentPage(); + if (currentPage > 0) { + this.reactive.dispatch("changePage", this.category, currentPage - 1); + } + } + + /** + * Dispatches mutation that retrieves packages from the next page. + */ + nextPage() { + const currentPage = this._getCurrentPage(); + const lastPage = this._getLastPage(); + if (currentPage < lastPage) { + this.reactive.dispatch("changePage", this.category, currentPage + 1); + } + } + +} diff --git a/amd/src/package_search/components/tab_content.js b/amd/src/package_search/components/tab_content.js index 71736259..a88a3ae5 100644 --- a/amd/src/package_search/components/tab_content.js +++ b/amd/src/package_search/components/tab_content.js @@ -22,6 +22,7 @@ import * as templates from 'core/templates'; import Notification from 'core/notification'; import Component from 'qtype_questionpy/package_search/component'; +import Pagination from 'qtype_questionpy/package_search/components/pagination'; export default class extends Component { @@ -35,7 +36,16 @@ export default class extends Component { this.category = descriptor.category; this.selectors = { CONTENT: ".qpy-tab-content", + PAGINATION: '[data-for="pagination"]', }; + + // Register pagination. + new Pagination({ + element: this.getElement(this.selectors.PAGINATION), + name: `pagiation_${this.category}`, + reactive: descriptor.reactive, + category: this.category, + }); } /** diff --git a/amd/src/package_search/mutations.js b/amd/src/package_search/mutations.js index 60d7e055..5d508128 100644 --- a/amd/src/package_search/mutations.js +++ b/amd/src/package_search/mutations.js @@ -47,11 +47,11 @@ export default class { * Search through given categories with same arguments. * * @param {Object} args the arguments - * @param {string} categories + * @param {string[]} categories * @returns {any} * @private */ - _getSearchPackagesInCategoriesPromise(args, ...categories) { + _getSearchPackagesInCategoriesPromise(args, categories) { let clonedArgs = {...args}; let methods = []; for (const category of categories) { @@ -74,33 +74,43 @@ export default class { * * @param {StateManager} stateManager * @param {Object|null} args + * @param {string[]|null} categories */ - async searchPackages(stateManager, args = null) { + async searchPackages(stateManager, args = null, categories = null) { const state = stateManager.state; + // Search through every category if no categories are provided. + categories = categories || ["all", "recentlyused", "favourites", "mine"]; + // Missing arguments are taken from the current state. args = args || {}; let mergedArgs = { - query: args.query || state.general.query, + query: (typeof args.query === "string") ? args.query : state.general.query, tags: [], // TODO. sort: args.sort || state.general.sort, order: args.order || state.general.order, limit: this.options.limit, - page: 0, + page: args.page || 0, contextid: this.options.contextid, }; this._setLoading(stateManager, true); try { - let [all, recentlyused, favourites, mine] = await this._getSearchPackagesInCategoriesPromise(mergedArgs, - "all", "recentlyused", "favourites", "mine"); + // Get search results for each category. + let results = await this._getSearchPackagesInCategoriesPromise(mergedArgs, categories); stateManager.setReadOnly(false); - state.all.data = await all; - state.recentlyused.data = await recentlyused; - state.favourites.data = await favourites; - state.mine.data = await mine; + // Update category specific data. + for (const [index, category] of categories.entries()) { + state[category].data = await results[index]; + state[category].page = mergedArgs.page; + } + // Update general data. + state.general.query = mergedArgs.query; + state.general.tags = mergedArgs.tags; + state.general.sort = mergedArgs.sort; + state.general.order = mergedArgs.order; stateManager.setReadOnly(true); } catch (exception) { await Notification.exception(exception); @@ -118,4 +128,15 @@ export default class { async searchPackagesByQuery(stateManager, query) { await this.searchPackages(stateManager, {query: query}); } + + /** + * Used to change the current page of a tab. + * + * @param {StateManager} stateManager + * @param {string} category + * @param {number} page + */ + async changePage(stateManager, category, page) { + await this.searchPackages(stateManager, {page: page}, [category]); + } } diff --git a/lang/en/qtype_questionpy.php b/lang/en/qtype_questionpy.php index fc9cbaee..35a1b2fb 100644 --- a/lang/en/qtype_questionpy.php +++ b/lang/en/qtype_questionpy.php @@ -74,6 +74,9 @@ $string['search_recentlyused_header'] = 'Recently Used ({$a})'; $string['search_favourites_header'] = 'Favourites ({$a})'; $string['search_mine_header'] = 'Mine ({$a})'; +$string['search_pagination_label_aria'] = 'Search results pages'; +$string['search_pagination_previous_aria'] = 'Previous'; +$string['search_pagination_next_aria'] = 'Next'; // Question management. $string['package_not_found'] = 'The requested package {$a->packagehash} does not exist.'; diff --git a/templates/package_search/area.mustache b/templates/package_search/area.mustache index c6d447bc..43e13476 100644 --- a/templates/package_search/area.mustache +++ b/templates/package_search/area.mustache @@ -51,15 +51,19 @@
+ {{> qtype_questionpy/package_search/pagination }}
+ {{> qtype_questionpy/package_search/pagination }}
+ {{> qtype_questionpy/package_search/pagination }}
+ {{> qtype_questionpy/package_search/pagination }}
diff --git a/templates/package_search/pagination.mustache b/templates/package_search/pagination.mustache new file mode 100644 index 00000000..52949037 --- /dev/null +++ b/templates/package_search/pagination.mustache @@ -0,0 +1,42 @@ +{{! + This file is part of the QuestionPy Moodle plugin - https://questionpy.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 qtype_questionpy/package_search/pagination + + Represents a pagination section of a package container. + + Classes required for JS: + * qpy-pagination + + Example context (json): + {} +}} + \ No newline at end of file