1 |
efrain |
1 |
{"version":3,"file":"grader.min.js","sources":["../../../src/local/grades/grader.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\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 * @copyright 2019 Mathew May <mathew.solutions>\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 {addNotification} from 'core/notification';\nimport {getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison';\nimport Modal from 'core/modal_cancel';\nimport {subscribe} from 'core/pubsub';\nimport DrawerEvents from 'core/drawer_events';\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 searchResults: 'mod_forum/local/grades/local/grader/user_picker/user_search',\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 * @returns {array} An array containing the HTML, and JS.\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 * @param {Function} saveGradeForUser\n * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser, saveGradeForUser) => {\n let firstLoad = true;\n\n return async(user) => {\n const spinner = firstLoad ? null : 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\n const form = panel.querySelector('form');\n fillInitialValues(form);\n\n form.addEventListener('submit', event => {\n saveGradeForUser(user);\n event.preventDefault();\n });\n\n panelContainer.scrollTop = 0;\n firstLoad = false;\n\n if (spinner) {\n spinner.resolve();\n }\n return userGrade;\n };\n};\n\n/**\n * Show the search results container and hide the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst showSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.add('hidden');\n userPickerContainer.classList.add('hidden');\n searchResultsContainer.classList.remove('hidden');\n};\n\n/**\n * Hide the search results container and show the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst hideSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.remove('hidden');\n userPickerContainer.classList.remove('hidden');\n searchResultsContainer.classList.add('hidden');\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst showUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.remove('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'true');\n toggleSearchButton.classList.add('expand');\n toggleSearchButton.classList.remove('collapse');\n\n // Hide the grading info container from screen reader.\n const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);\n gradingInfoContainer.setAttribute('aria-hidden', 'true');\n\n // Hide the collapse grading drawer button from screen reader.\n const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);\n collapseGradingDrawer.setAttribute('aria-hidden', 'true');\n collapseGradingDrawer.setAttribute('tabindex', '-1');\n\n searchInput.focus();\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst hideUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.add('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'false');\n toggleSearchButton.classList.add('collapse');\n toggleSearchButton.classList.remove('expand');\n toggleSearchButton.focus();\n\n // Show the grading info container to screen reader.\n const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);\n gradingInfoContainer.removeAttribute('aria-hidden');\n\n // Show the collapse grading drawer button from screen reader.\n const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);\n collapseGradingDrawer.removeAttribute('aria-hidden');\n collapseGradingDrawer.setAttribute('tabindex', '0');\n\n searchInput.value = '';\n};\n\n/**\n * Find the list of users who's names include the given search term.\n *\n * @param {Array} userList List of users for the grader\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchForUsers = (userList, searchTerm) => {\n if (searchTerm === '') {\n return userList;\n }\n\n searchTerm = searchTerm.toLowerCase();\n\n return userList.filter((user) => {\n return user.fullname.toLowerCase().includes(searchTerm);\n });\n};\n\n/**\n * Render the list of users in the search results area.\n *\n * @param {HTMLElement} searchResultsContainer The container element for search results\n * @param {Array} users The list of users to display\n */\nconst renderSearchResults = async(searchResultsContainer, users) => {\n const {html, js} = await Templates.renderForPromise(templateNames.grader.searchResults, {users});\n Templates.replaceNodeContents(searchResultsContainer, html, js);\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 * @param {Array} userList List of users for the grader.\n */\nconst registerEventListeners = (graderLayout, userPicker, saveGradeFunction, userList) => {\n const graderContainer = graderLayout.getContainer();\n const toggleSearchButton = graderContainer.querySelector(Selectors.buttons.toggleSearch);\n const searchInputContainer = graderContainer.querySelector(Selectors.regions.userSearchContainer);\n const searchInput = searchInputContainer.querySelector(Selectors.regions.userSearchInput);\n const bodyContainer = graderContainer.querySelector(Selectors.regions.bodyContainer);\n const userPickerContainer = graderContainer.querySelector(Selectors.regions.pickerRegion);\n const searchResultsContainer = graderContainer.querySelector(Selectors.regions.searchResultsContainer);\n\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 if (e.target.closest(Selectors.buttons.toggleSearch)) {\n if (toggleSearchButton.getAttribute('aria-expanded') === 'true') {\n // Search is open so let's close it.\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n } else {\n // Search is closed so let's open it.\n showUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n showSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n renderSearchResults(searchResultsContainer, userList);\n }\n\n return;\n }\n\n const selectUserButton = e.target.closest(Selectors.buttons.selectUser);\n if (selectUserButton) {\n const userId = selectUserButton.getAttribute('data-userid');\n const user = userList.find(user => user.id == userId);\n userPicker.setUserId(userId);\n userPicker.showUser(user);\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n }\n });\n\n // Debounce the search input so that it only executes 300 milliseconds after the user has finished typing.\n searchInput.addEventListener('input', debounce(() => {\n const users = searchForUsers(userList, searchInput.value);\n renderSearchResults(searchResultsContainer, users);\n }, 300));\n\n // Remove the right margin of the content container when the grading panel is hidden so that it expands to full-width.\n subscribe(DrawerEvents.DRAWER_HIDDEN, (drawerRoot) => {\n const gradingPanel = drawerRoot[0];\n if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {\n setContentContainerMargin(graderContainer, 0);\n }\n });\n\n // Bring back the right margin of the content container when the grading panel is shown to give space for the grading panel.\n subscribe(DrawerEvents.DRAWER_SHOWN, (drawerRoot) => {\n const gradingPanel = drawerRoot[0];\n if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {\n setContentContainerMargin(graderContainer, gradingPanel.offsetWidth);\n }\n });\n};\n\n/**\n * Adjusts the right margin of the content container.\n *\n * @param {HTMLElement} graderContainer The container for the grader app.\n * @param {Number} rightMargin The right margin value.\n */\nconst setContentContainerMargin = (graderContainer, rightMargin) => {\n const contentContainer = graderContainer.querySelector(Selectors.regions.moduleContainer);\n if (contentContainer) {\n contentContainer.style.marginRight = `${rightMargin}px`;\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(\n user.id,\n root.querySelector(Selectors.values.sendStudentNotifications).value,\n root.querySelector(Selectors.regions.gradingPanel)\n );\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, {type: 'warning'});\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 * @param {Object} preferences Preferences for the launch function\n * @param {Number} preferences.initialUserId\n * @param {string} preferences.moduleName\n * @param {string} preferences.courseName\n * @param {string} preferences.courseUrl\n * @param {boolean} preferences.sendStudentNotifications\n * @param {null|HTMLElement} preferences.focusOnClose\n */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null,\n moduleName,\n courseName,\n courseUrl,\n sendStudentNotifications,\n focusOnClose = null,\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\n // We need this promise to resolve separately so that we can avoid loading the whole interface if there are no users.\n const userList = await getListOfUsers();\n if (!userList.length) {\n addNotification({\n message: await getString('nouserstograde', 'core_grades'),\n type: \"error\",\n });\n return;\n }\n\n // Now that we have confirmed there are at least some users let's boot up the grader interface.\n const [\n graderLayout,\n {html, js},\n ] = await Promise.all([\n createFullScreenWindow({\n fullscreen: false,\n showLoader: false,\n focusOnClose,\n }),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true},\n defaultsendnotifications: sendStudentNotifications,\n }),\n ]);\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, saveGradeFunction);\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 async(user) => {\n const userGrade = await updateUserContent(user);\n const renderContext = {\n status: userGrade.hasgrade,\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 },\n saveGradeFunction,\n {\n initialUserId,\n },\n );\n\n // Register all event listeners.\n registerEventListeners(graderLayout, userPicker, saveGradeFunction, userList);\n\n // Display the newly created user picker.\n displayUserPicker(graderContainer, userPicker.rootNode);\n};\n\n/**\n * Show the grade for a specific user.\n *\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Number} userid The ID of a specific user\n * @param {String} moduleName the name of the module\n * @param {object} param\n * @param {null|HTMLElement} param.focusOnClose\n */\nexport const view = async(getGradeForUser, userid, moduleName, {\n focusOnClose = null,\n} = {}) => {\n\n const userGrade = await getGradeForUser(userid);\n\n const [\n modal,\n gradeTemplateData\n ] = await Promise.all([\n Modal.create({\n title: moduleName,\n large: true,\n removeOnClose: true,\n returnElement: focusOnClose,\n show: true,\n body: Templates.render('mod_forum/local/grades/view_grade', userGrade),\n }),\n renderGradeTemplate(userGrade)\n ]);\n\n const bodyPromise = await modal.getBodyPromise();\n const gradeReplace = bodyPromise[0].querySelector('[data-region=\"grade-template\"]');\n Templates.replaceNodeContents(gradeReplace, gradeTemplateData.html, gradeTemplateData.js);\n};\n\nconst renderGradeTemplate = (userGrade) => Templates.renderForPromise(userGrade.templatename, userGrade.grade);\n\nexport {getGradingPanelFunctions};\n"],"names":["templateNames","app","gradingPanel","error","searchResults","status","displayUserPicker","root","html","pickerRegion","querySelector","Selectors","regions","replaceNodeContents","fetchContentFromRender","js","getUpdateUserContentFunction","getContentForUser","getGradeForUser","saveGradeForUser","firstLoad","async","spinner","userGrade","Promise","all","user","id","then","moduleReplace","gradingPanelHtml","gradingPanelJS","Templates","render","templatename","grade","panelContainer","gradingPanelContainer","panel","form","addEventListener","event","preventDefault","scrollTop","resolve","hideSearchResultContainer","bodyContainer","userPickerContainer","searchResultsContainer","classList","remove","add","hideUserSearchInput","toggleSearchButton","searchContainer","searchInput","setAttribute","focus","parentElement","gradingInfoContainer","removeAttribute","collapseGradingDrawer","buttons","value","renderSearchResults","users","renderForPromise","registerEventListeners","graderLayout","userPicker","saveGradeFunction","userList","graderContainer","getContainer","toggleSearch","searchInputContainer","userSearchContainer","userSearchInput","e","target","closest","toggleFullscreen","stopImmediatePropagation","closeGrader","close","saveGrade","currentUser","getAttribute","innerHTML","showUserSearchInput","showSearchResultContainer","selectUserButton","selectUser","userId","find","setUserId","showUser","searchTerm","toLowerCase","filter","fullname","includes","searchForUsers","DrawerEvents","DRAWER_HIDDEN","drawerRoot","setContentContainerMargin","DRAWER_SHOWN","offsetWidth","rightMargin","contentContainer","moduleContainer","style","marginRight","getSaveUserGradeFunction","setGradeForUser","gradingPanelErrors","result","values","sendStudentNotifications","success","failed","displayGradingError","err","errorString","message","type","getListOfUsers","initialUserId","moduleName","courseName","courseUrl","focusOnClose","length","fullscreen","showLoader","drawer","show","defaultsendnotifications","updateUserContent","userIds","map","statusContainer","renderContext","hasgrade","index","indexOf","total","catch","rootNode","userid","modal","gradeTemplateData","Modal","create","title","large","removeOnClose","returnElement","body","renderGradeTemplate","bodyPromise","getBodyPromise","gradeReplace"],"mappings":"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"}
|