From 8a09616bc2a82e403870c699c65c59a0129ad3fe Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Tue, 5 Nov 2019 13:41:58 +0800 Subject: [PATCH] MDL-66893 mod_forum: add grader status to grader UI --- .../amd/build/local/grades/grader.min.js | 2 +- .../amd/build/local/grades/grader.min.js.map | 2 +- .../grades/local/grader/selectors.min.js | 2 +- .../grades/local/grader/selectors.min.js.map | 2 +- mod/forum/amd/src/local/grades/grader.js | 16 +++++++- .../local/grades/local/grader/selectors.js | 1 + mod/forum/lang/en/forum.php | 1 + .../grades/local/grader/grading.mustache | 3 ++ .../local/grades/local/grader/status.mustache | 37 +++++++++++++++++++ .../local/grader/status_placeholder.mustache | 33 +++++++++++++++++ 10 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 mod/forum/templates/local/grades/local/grader/status.mustache create mode 100644 mod/forum/templates/local/grades/local/grader/status_placeholder.mustache diff --git a/mod/forum/amd/build/local/grades/grader.min.js b/mod/forum/amd/build/local/grades/grader.min.js index ee4215d489cd8..4662f042aec36 100644 --- a/mod/forum/amd/build/local/grades/grader.min.js +++ b/mod/forum/amd/build/local/grades/grader.min.js @@ -1,2 +1,2 @@ -define ("mod_forum/local/grades/grader",["exports","core/templates","./local/grader/selectors","./local/grader/user_picker","mod_forum/local/layout/fullscreen","./local/grader/gradingpanel","core/toast","core/str","core_grades/grades/grader/gradingpanel/normalise","core/loadingicon"],function(a,b,c,d,e,f,g,h,i,j){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"getGradingPanelFunctions",{enumerable:!0,get:function get(){return f.default}});a.launch=void 0;b=k(b);c=k(c);d=k(d);f=k(f);function k(a){return a&&a.__esModule?a:{default:a}}function l(a){for(var b=1;b.\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/local/grades/grader\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Templates from 'core/templates';\nimport Selectors from './local/grader/selectors';\nimport getUserPicker from './local/grader/user_picker';\nimport {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';\nimport getGradingPanelFunctions from './local/grader/gradingpanel';\nimport {add as addToast} from 'core/toast';\nimport {get_string as getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\n\nconst templateNames = {\n grader: {\n app: 'mod_forum/local/grades/grader',\n gradingPanel: {\n error: 'mod_forum/local/grades/local/grader/gradingpanel/error',\n },\n },\n};\n\n/**\n * Helper function that replaces the user picker placeholder with what we get back from the user picker class.\n *\n * @param {HTMLElement} root\n * @param {String} html\n */\nconst displayUserPicker = (root, html) => {\n const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);\n Templates.replaceNodeContents(pickerRegion, html, '');\n};\n\n/**\n * To be removed, this is now done as a part of Templates.renderForPromise()\n *\n * @param {String} html\n * @param {String} js\n * @return {[*, *]}\n */\nconst fetchContentFromRender = (html, js) => {\n return [html, js];\n};\n\n/**\n * Here we build the function that is passed to the user picker that'll handle updating the user content area\n * of the grading interface.\n *\n * @param {HTMLElement} root\n * @param {Function} getContentForUser\n * @param {Function} getGradeForUser\n * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {\n return async(user) => {\n const spinner = addIconToContainerWithPromise(root);\n const [\n [html, js],\n userGrade,\n ] = await Promise.all([\n getContentForUser(user.id).then(fetchContentFromRender),\n getGradeForUser(user.id),\n ]);\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);\n\n const [\n gradingPanelHtml,\n gradingPanelJS\n ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);\n const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);\n const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);\n Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);\n panelContainer.scrollTop = 0;\n spinner.resolve();\n };\n};\n\n/**\n * Add click handlers to the buttons in the header of the grading interface.\n *\n * @param {HTMLElement} graderLayout\n * @param {Object} userPicker\n * @param {Function} saveGradeFunction\n */\nconst registerEventListeners = (graderLayout, userPicker, saveGradeFunction) => {\n const graderContainer = graderLayout.getContainer();\n graderContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.buttons.toggleFullscreen)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n graderLayout.toggleFullscreen();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.closeGrader)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n\n graderLayout.close();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.saveGrade)) {\n saveGradeFunction(userPicker.currentUser);\n }\n });\n};\n\n/**\n * Get the function used to save a user grade.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Function} setGradeForUser The function that will be called.\n * @return {Function}\n */\nconst getSaveUserGradeFunction = (root, setGradeForUser) => {\n return async(user) => {\n try {\n root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';\n const result = await setGradeForUser(user.id, root.querySelector(Selectors.regions.gradingPanel));\n if (result.success) {\n addToast(await getString('grades:gradesavedfor', 'mod_forum', user));\n }\n if (result.failed) {\n displayGradingError(root, user, result.error);\n }\n\n return result;\n } catch (err) {\n displayGradingError(root, user, err);\n\n return failedUpdate(err);\n }\n };\n};\n\n/**\n * Display a grading error, typically from a failed save.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Object} user The user who was errored\n * @param {Object} err The details of the error\n */\nconst displayGradingError = async(root, user, err) => {\n const [\n {html, js},\n errorString\n ] = await Promise.all([\n Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),\n await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),\n ]);\n\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);\n addToast(errorString);\n};\n\n/**\n * Launch the grader interface with the specified parameters.\n *\n * @param {Function} getListOfUsers A function to get the list of users\n * @param {Function} getContentForUser A function to get the content for a specific user\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Function} setGradeForUser A function to set the grade for a specific user\n */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null, moduleName, courseName, courseUrl\n} = {}) => {\n\n // We need all of these functions to be executed in series, if one step runs before another the interface\n // will not work.\n const [\n graderLayout,\n {html, js},\n userList,\n ] = await Promise.all([\n createFullScreenWindow({fullscreen: false, showLoader: false}),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true}\n }),\n getListOfUsers(),\n ]);\n const graderContainer = graderLayout.getContainer();\n\n const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);\n\n Templates.replaceNodeContents(graderContainer, html, js);\n const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser);\n\n // Fetch the userpicker for display.\n const userPicker = await getUserPicker(\n userList,\n updateUserContent,\n saveGradeFunction,\n {\n initialUserId,\n },\n );\n\n // Register all event listeners.\n registerEventListeners(graderLayout, userPicker, saveGradeFunction);\n\n // Display the newly created user picker.\n displayUserPicker(graderContainer, userPicker.rootNode);\n};\n\nexport {getGradingPanelFunctions};\n"],"file":"grader.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/grades/grader.js"],"names":["templateNames","grader","app","gradingPanel","error","status","displayUserPicker","root","html","pickerRegion","querySelector","Selectors","regions","Templates","replaceNodeContents","fetchContentFromRender","js","getUpdateUserContentFunction","getContentForUser","getGradeForUser","user","spinner","Promise","all","id","then","userGrade","moduleReplace","render","templatename","grade","gradingPanelHtml","gradingPanelJS","panelContainer","gradingPanelContainer","panel","scrollTop","resolve","registerEventListeners","graderLayout","userPicker","saveGradeFunction","graderContainer","getContainer","addEventListener","e","target","closest","buttons","toggleFullscreen","stopImmediatePropagation","preventDefault","closeGrader","close","saveGrade","currentUser","getSaveUserGradeFunction","setGradeForUser","gradingPanelErrors","innerHTML","result","success","addToast","failed","displayGradingError","err","renderForPromise","message","errorString","launch","getListOfUsers","initialUserId","moduleName","courseName","courseUrl","fullscreen","showLoader","drawer","show","userList","updateUserContent","userIds","map","statusContainer","renderContext","index","indexOf","total","length","catch","rootNode"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/local/grades/grader\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Templates from 'core/templates';\nimport Selectors from './local/grader/selectors';\nimport getUserPicker from './local/grader/user_picker';\nimport {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';\nimport getGradingPanelFunctions from './local/grader/gradingpanel';\nimport {add as addToast} from 'core/toast';\nimport {get_string as getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\n\nconst templateNames = {\n grader: {\n app: 'mod_forum/local/grades/grader',\n gradingPanel: {\n error: 'mod_forum/local/grades/local/grader/gradingpanel/error',\n },\n status: 'mod_forum/local/grades/local/grader/status'\n },\n};\n\n/**\n * Helper function that replaces the user picker placeholder with what we get back from the user picker class.\n *\n * @param {HTMLElement} root\n * @param {String} html\n */\nconst displayUserPicker = (root, html) => {\n const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);\n Templates.replaceNodeContents(pickerRegion, html, '');\n};\n\n/**\n * To be removed, this is now done as a part of Templates.renderForPromise()\n *\n * @param {String} html\n * @param {String} js\n * @return {[*, *]}\n */\nconst fetchContentFromRender = (html, js) => {\n return [html, js];\n};\n\n/**\n * Here we build the function that is passed to the user picker that'll handle updating the user content area\n * of the grading interface.\n *\n * @param {HTMLElement} root\n * @param {Function} getContentForUser\n * @param {Function} getGradeForUser\n * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {\n return async(user) => {\n const spinner = addIconToContainerWithPromise(root);\n const [\n [html, js],\n userGrade,\n ] = await Promise.all([\n getContentForUser(user.id).then(fetchContentFromRender),\n getGradeForUser(user.id),\n ]);\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);\n\n const [\n gradingPanelHtml,\n gradingPanelJS\n ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);\n const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);\n const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);\n Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);\n panelContainer.scrollTop = 0;\n spinner.resolve();\n };\n};\n\n/**\n * Add click handlers to the buttons in the header of the grading interface.\n *\n * @param {HTMLElement} graderLayout\n * @param {Object} userPicker\n * @param {Function} saveGradeFunction\n */\nconst registerEventListeners = (graderLayout, userPicker, saveGradeFunction) => {\n const graderContainer = graderLayout.getContainer();\n graderContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.buttons.toggleFullscreen)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n graderLayout.toggleFullscreen();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.closeGrader)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n\n graderLayout.close();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.saveGrade)) {\n saveGradeFunction(userPicker.currentUser);\n }\n });\n};\n\n/**\n * Get the function used to save a user grade.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Function} setGradeForUser The function that will be called.\n * @return {Function}\n */\nconst getSaveUserGradeFunction = (root, setGradeForUser) => {\n return async(user) => {\n try {\n root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';\n const result = await setGradeForUser(user.id, root.querySelector(Selectors.regions.gradingPanel));\n if (result.success) {\n addToast(await getString('grades:gradesavedfor', 'mod_forum', user));\n }\n if (result.failed) {\n displayGradingError(root, user, result.error);\n }\n\n return result;\n } catch (err) {\n displayGradingError(root, user, err);\n\n return failedUpdate(err);\n }\n };\n};\n\n/**\n * Display a grading error, typically from a failed save.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Object} user The user who was errored\n * @param {Object} err The details of the error\n */\nconst displayGradingError = async(root, user, err) => {\n const [\n {html, js},\n errorString\n ] = await Promise.all([\n Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),\n await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),\n ]);\n\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);\n addToast(errorString);\n};\n\n/**\n * Launch the grader interface with the specified parameters.\n *\n * @param {Function} getListOfUsers A function to get the list of users\n * @param {Function} getContentForUser A function to get the content for a specific user\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Function} setGradeForUser A function to set the grade for a specific user\n */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null, moduleName, courseName, courseUrl\n} = {}) => {\n\n // We need all of these functions to be executed in series, if one step runs before another the interface\n // will not work.\n const [\n graderLayout,\n {html, js},\n userList,\n ] = await Promise.all([\n createFullScreenWindow({fullscreen: false, showLoader: false}),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true}\n }),\n getListOfUsers(),\n ]);\n const graderContainer = graderLayout.getContainer();\n\n const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);\n\n Templates.replaceNodeContents(graderContainer, html, js);\n const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser);\n\n const userIds = userList.map(user => user.id);\n const statusContainer = graderContainer.querySelector(Selectors.regions.statusContainer);\n // Fetch the userpicker for display.\n const userPicker = await getUserPicker(\n userList,\n user => {\n const renderContext = {\n status: null,\n index: userIds.indexOf(user.id) + 1,\n total: userList.length\n };\n Templates.render(templateNames.grader.status, renderContext).then(html => {\n statusContainer.innerHTML = html;\n return html;\n }).catch();\n updateUserContent(user);\n },\n saveGradeFunction,\n {\n initialUserId,\n },\n );\n\n // Register all event listeners.\n registerEventListeners(graderLayout, userPicker, saveGradeFunction);\n\n // Display the newly created user picker.\n displayUserPicker(graderContainer, userPicker.rootNode);\n};\n\nexport {getGradingPanelFunctions};\n"],"file":"grader.min.js"} \ No newline at end of file diff --git a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js index 55157765163bd..3c73b63703a36 100644 --- a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js +++ b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js @@ -1,2 +1,2 @@ -define ("mod_forum/local/grades/local/grader/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={buttons:{toggleFullscreen:b("action","togglefullscreen"),closeGrader:b("action","closegrader"),saveGrade:b("action","savegrade")},regions:{moduleReplace:b("region","module_content"),pickerRegion:b("region","user_picker"),gradingPanel:b("region","grade"),gradingPanelContainer:b("region","grading-panel-container"),gradingPanelErrors:b("region","grade-errors")}};a.default=c;return a.default}); +define ("mod_forum/local/grades/local/grader/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={buttons:{toggleFullscreen:b("action","togglefullscreen"),closeGrader:b("action","closegrader"),saveGrade:b("action","savegrade")},regions:{moduleReplace:b("region","module_content"),pickerRegion:b("region","user_picker"),gradingPanel:b("region","grade"),gradingPanelContainer:b("region","grading-panel-container"),gradingPanelErrors:b("region","grade-errors"),statusContainer:b("region","status-container")}};a.default=c;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map index d9c66415c489e..4af8b7cdb8b46 100644 --- a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map +++ b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../../src/local/grades/local/grader/selectors.js"],"names":["getDataSelector","name","value","buttons","toggleFullscreen","closeGrader","saveGrade","regions","moduleReplace","pickerRegion","gradingPanel","gradingPanelContainer","gradingPanelErrors"],"mappings":"kKA8BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,gBAAgB,CAAEJ,CAAe,CAAC,QAAD,CAAW,kBAAX,CAD5B,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,SAAS,CAAEN,CAAe,CAAC,QAAD,CAAW,WAAX,CAHrB,CADE,CAMXO,OAAO,CAAE,CACLC,aAAa,CAAER,CAAe,CAAC,QAAD,CAAW,gBAAX,CADzB,CAELS,YAAY,CAAET,CAAe,CAAC,QAAD,CAAW,aAAX,CAFxB,CAGLU,YAAY,CAAEV,CAAe,CAAC,QAAD,CAAW,OAAX,CAHxB,CAILW,qBAAqB,CAAEX,CAAe,CAAC,QAAD,CAAW,yBAAX,CAJjC,CAKLY,kBAAkB,CAAEZ,CAAe,CAAC,QAAD,CAAW,cAAX,CAL9B,CANE,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/selectors\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n buttons: {\n toggleFullscreen: getDataSelector('action', 'togglefullscreen'),\n closeGrader: getDataSelector('action', 'closegrader'),\n saveGrade: getDataSelector('action', 'savegrade'),\n },\n regions: {\n moduleReplace: getDataSelector('region', 'module_content'),\n pickerRegion: getDataSelector('region', 'user_picker'),\n gradingPanel: getDataSelector('region', 'grade'),\n gradingPanelContainer: getDataSelector('region', 'grading-panel-container'),\n gradingPanelErrors: getDataSelector('region', 'grade-errors'),\n },\n};\n\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../../../src/local/grades/local/grader/selectors.js"],"names":["getDataSelector","name","value","buttons","toggleFullscreen","closeGrader","saveGrade","regions","moduleReplace","pickerRegion","gradingPanel","gradingPanelContainer","gradingPanelErrors","statusContainer"],"mappings":"kKA8BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,gBAAgB,CAAEJ,CAAe,CAAC,QAAD,CAAW,kBAAX,CAD5B,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,SAAS,CAAEN,CAAe,CAAC,QAAD,CAAW,WAAX,CAHrB,CADE,CAMXO,OAAO,CAAE,CACLC,aAAa,CAAER,CAAe,CAAC,QAAD,CAAW,gBAAX,CADzB,CAELS,YAAY,CAAET,CAAe,CAAC,QAAD,CAAW,aAAX,CAFxB,CAGLU,YAAY,CAAEV,CAAe,CAAC,QAAD,CAAW,OAAX,CAHxB,CAILW,qBAAqB,CAAEX,CAAe,CAAC,QAAD,CAAW,yBAAX,CAJjC,CAKLY,kBAAkB,CAAEZ,CAAe,CAAC,QAAD,CAAW,cAAX,CAL9B,CAMLa,eAAe,CAAEb,CAAe,CAAC,QAAD,CAAW,kBAAX,CAN3B,CANE,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/selectors\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n buttons: {\n toggleFullscreen: getDataSelector('action', 'togglefullscreen'),\n closeGrader: getDataSelector('action', 'closegrader'),\n saveGrade: getDataSelector('action', 'savegrade'),\n },\n regions: {\n moduleReplace: getDataSelector('region', 'module_content'),\n pickerRegion: getDataSelector('region', 'user_picker'),\n gradingPanel: getDataSelector('region', 'grade'),\n gradingPanelContainer: getDataSelector('region', 'grading-panel-container'),\n gradingPanelErrors: getDataSelector('region', 'grade-errors'),\n statusContainer: getDataSelector('region', 'status-container'),\n },\n};\n\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/mod/forum/amd/src/local/grades/grader.js b/mod/forum/amd/src/local/grades/grader.js index caf471234c3b5..99b3e57d5072d 100644 --- a/mod/forum/amd/src/local/grades/grader.js +++ b/mod/forum/amd/src/local/grades/grader.js @@ -37,6 +37,7 @@ const templateNames = { gradingPanel: { error: 'mod_forum/local/grades/local/grader/gradingpanel/error', }, + status: 'mod_forum/local/grades/local/grader/status' }, }; @@ -211,10 +212,23 @@ export const launch = async(getListOfUsers, getContentForUser, getGradeForUser, Templates.replaceNodeContents(graderContainer, html, js); const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser); + const userIds = userList.map(user => user.id); + const statusContainer = graderContainer.querySelector(Selectors.regions.statusContainer); // Fetch the userpicker for display. const userPicker = await getUserPicker( userList, - updateUserContent, + user => { + const renderContext = { + status: null, + index: userIds.indexOf(user.id) + 1, + total: userList.length + }; + Templates.render(templateNames.grader.status, renderContext).then(html => { + statusContainer.innerHTML = html; + return html; + }).catch(); + updateUserContent(user); + }, saveGradeFunction, { initialUserId, diff --git a/mod/forum/amd/src/local/grades/local/grader/selectors.js b/mod/forum/amd/src/local/grades/local/grader/selectors.js index 3c3fbed197852..833117288017d 100644 --- a/mod/forum/amd/src/local/grades/local/grader/selectors.js +++ b/mod/forum/amd/src/local/grades/local/grader/selectors.js @@ -44,6 +44,7 @@ export default { gradingPanel: getDataSelector('region', 'grade'), gradingPanelContainer: getDataSelector('region', 'grading-panel-container'), gradingPanelErrors: getDataSelector('region', 'grade-errors'), + statusContainer: getDataSelector('region', 'status-container'), }, }; diff --git a/mod/forum/lang/en/forum.php b/mod/forum/lang/en/forum.php index 8c44f51857fa4..8d479d7ff1a46 100644 --- a/mod/forum/lang/en/forum.php +++ b/mod/forum/lang/en/forum.php @@ -342,6 +342,7 @@ $string['indicator:socialbreadth'] = 'Forum social'; $string['indicator:socialbreadth_help'] = 'This indicator is based on the social breadth reached by the student in a Forum activity.'; $string['starredonly'] = 'Search starred discussions only'; +$string['indexoutoftotal'] = '{$a->index} out of {$a->total}'; $string['inforum'] = 'in {$a}'; $string['inreplyto'] = 'In reply to {$a}'; $string['introblog'] = 'The posts in this forum were copied here automatically from blogs of users in this course because those blog entries are no longer available'; diff --git a/mod/forum/templates/local/grades/local/grader/grading.mustache b/mod/forum/templates/local/grades/local/grader/grading.mustache index 3d9d7cc305508..316829b7bba92 100644 --- a/mod/forum/templates/local/grades/local/grader/grading.mustache +++ b/mod/forum/templates/local/grades/local/grader/grading.mustache @@ -72,6 +72,9 @@ {{#pix}} t/left, core {{/pix}} {{#pix}} t/right, core {{/pix}} +
+ {{> mod_forum/local/grades/local/grader/status_placeholder }} +
diff --git a/mod/forum/templates/local/grades/local/grader/status.mustache b/mod/forum/templates/local/grades/local/grader/status.mustache new file mode 100644 index 0000000000000..82d9cf0e82c7c --- /dev/null +++ b/mod/forum/templates/local/grades/local/grader/status.mustache @@ -0,0 +1,37 @@ +{{! + 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 mod_forum/local/grades/local/grader/status + + Classes required for JS: + * none + + Data attributes required for JS: + * + + Context variables required for this template: + * + + Example context (json): + { + "status": "Graded", + "index": 1, + "total": 10 + } +}} +{{#status}}

{{.}}

{{/status}} +
{{#str}} indexoutoftotal, mod_forum, {"index": "{{index}}", "total":"{{total}}"}{{/str}}
diff --git a/mod/forum/templates/local/grades/local/grader/status_placeholder.mustache b/mod/forum/templates/local/grades/local/grader/status_placeholder.mustache new file mode 100644 index 0000000000000..cdd9cfe6147b7 --- /dev/null +++ b/mod/forum/templates/local/grades/local/grader/status_placeholder.mustache @@ -0,0 +1,33 @@ +{{! + 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 mod_forum/local/grades/local/grader/status_placeholder + + Classes required for JS: + * none + + Data attributes required for JS: + * + + Context variables required for this template: + * + + Example context (json): + {} +}} +
+