Rev 1 | AutorÃa | Comparar con el anterior | Ultima modificación | Ver Log |
{"version":3,"file":"collapse.min.js","sources":["../src/collapse.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 * Allow the user to show and hide columns of the report at will.\n *\n * @module gradereport_grader/collapse\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or
later\n */\nimport * as Repository from 'gradereport_grader/collapse/repository';\nimport search_combobox from 'core/comboboxsearch/search_combobox';\nimport {renderForPromise, replaceNodeContents, replaceNode} from 'core/templates';\nimport {debounce} from 'core/utils';\nimport Dropdown from 'theme_boost/bootstrap/dropdown';\nimport {getStrings} from 'core/str';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport storage from 'core/localstorage';\nimport {addIconToContainer} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\n\n// Contain our selectors within this file until they could be of use elsewhere.\nconst selectors = {\n component: '.collapse-columns',\n formDropdown: '.columnsdropdownform',\n formItems: {\n cancel: 'cancel',\n save: 'save',\n checked: 'input[type=\"checkbox\"]:checked',\n currentlyUnchecked: 'input[type=\"checkbox\"]:not([data-action=\"selectall\"])',\n },\n hider:
'hide',\n expand: 'expand',\n colVal: '[data-col]',\n itemVal: '[data-itemid]',\n content: '[data-collapse=\"content\"]',\n sort: '[data-collapse=\"sort\"]',\n expandbutton: '[data-collapse=\"expandbutton\"]',\n rangerowcell: '[data-collapse=\"rangerowcell\"]',\n avgrowcell: '[data-collapse=\"avgrowcell\"]',\n menu: '[data-collapse=\"menu\"]',\n icons: '.data-collapse_gradeicons',\n count: '[data-collapse=\"count\"]',\n placeholder: '.collapsecolumndropdown [data-region=\"placeholder\"]',\n fullDropdown: '.collapsecolumndropdown',\n searchResultContainer: '.searchresultitemscontainer',\n cellMenuButton: '.cellmenubtn',\n};\n\nconst countIndicator = document.querySelector(selectors.count);\n\nexport default class ColumnSearch extends search_combobox {\n\n userID = -1;\n courseID = null;\n defaultSort = '';\n\n nodes = [];\n\n gradeStrings = null;\n userStrings = null;\n stringMap = [];\n\n static init(userID, courseID, defaultSort) {\n
return new ColumnSearch(userID, courseID, defaultSort);\n }\n\n constructor(userID, courseID, defaultSort) {\n super();\n this.userID = userID;\n this.courseID = courseID;\n this.defaultSort = defaultSort;\n this.component = document.querySelector(selectors.component);\n\n const pendingPromise = new Pending();\n // Display a loader whilst collapsing appropriate columns (based on the locally stored state for the current user).\n addIconToContainer(document.querySelector('.gradeparent')).then((loader) => {\n setTimeout(() => {\n // Get the users' checked columns to change.\n this.getDataset().forEach((item) => {\n this.nodesUpdate(item);\n });\n this.renderDefault();\n\n // Once the grade categories have been re-collapsed, remove the loader and display the Gradebook setup content.\n loader.remove();\n document.que
rySelector('.gradereport-grader-table').classList.remove('d-none');\n }, 10);\n }).then(() => pendingPromise.resolve()).catch(Notification.exception);\n\n this.component.addEventListener('hide.bs.dropdown', () => {\n const searchResultContainer = this.component.querySelector(selectors.searchResultContainer);\n searchResultContainer.scrollTop = 0;\n\n // Use setTimeout to make sure the following code is executed after the click event is handled.\n setTimeout(() => {\n if (this.searchInput.value !== '') {\n this.searchInput.value = '';\n this.searchInput.dispatchEvent(new Event('input', {bubbles: true}));\n }\n });\n });\n }\n\n /**\n * The overall div that contains the searching widget.\n *\n * @returns {string}\n */\n componentSelector() {\n return '.collapse-columns';\n }\n\n /**\n * The dropdown div that contains
the searching widget result space.\n *\n * @returns {string}\n */\n dropdownSelector() {\n return '.searchresultitemscontainer';\n }\n\n /**\n * Return the dataset that we will be searching upon.\n *\n * @returns {Array}\n */\n getDataset() {\n if (!this.dataset) {\n const cols = this.fetchDataset();\n this.dataset = JSON.parse(cols) ? JSON.parse(cols).split(',') : [];\n }\n this.datasetSize = this.dataset.length;\n return this.dataset;\n }\n\n /**\n * Get the data we will be searching against in this component.\n *\n * @returns {string}\n */\n fetchDataset() {\n return storage.get(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`);\n }\n\n /**\n * Given a user performs an action, update the users' preferences.\n */\n setPreferences() {\n storage.set(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`,\n JSON.str
ingify(this.getDataset().join(','))\n );\n }\n\n /**\n * Register clickable event listeners.\n */\n registerClickHandlers() {\n // Register click events within the component.\n this.component.addEventListener('click', this.clickHandler.bind(this));\n\n document.addEventListener('click', this.docClickHandler.bind(this));\n }\n\n /**\n * The handler for when a user interacts with the component.\n *\n * @param {MouseEvent} e The triggering event that we are working with.\n */\n clickHandler(e) {\n super.clickHandler(e);\n // Prevent BS from closing the dropdown if they click elsewhere within the dropdown besides the form.\n if (e.target.closest(selectors.fullDropdown)) {\n e.stopPropagation();\n }\n }\n\n /**\n * Externally defined click function to improve memory handling.\n *\n * @param {MouseEvent} e\n * @returns {Promise<void>}\n */\n async docClickHandler(e) {\n i
f (e.target.dataset.hider === selectors.hider) {\n e.preventDefault();\n const pendingPromise = new Pending('gradereport_grader/collapse:docClickHandler:hide');\n const desiredToHide = e.target.closest(selectors.colVal) ?\n e.target.closest(selectors.colVal)?.dataset.col :\n e.target.closest(selectors.itemVal)?.dataset.itemid;\n const idx = this.getDataset().indexOf(desiredToHide);\n if (idx === -1) {\n this.getDataset().push(desiredToHide);\n }\n await this.prefcountpipe();\n\n await this.nodesUpdate(desiredToHide);\n pendingPromise.resolve();\n }\n\n if (e.target.closest('button')?.dataset.hider === selectors.expand) {\n e.preventDefault();\n const pendingPromise = new Pending('gradereport_grader/collapse:docClickHandler:expand');\n const desiredToHide = e.target.closest(selectors.colVal) ?\n e.target.cl
osest(selectors.colVal)?.dataset.col :\n e.target.closest(selectors.itemVal)?.dataset.itemid;\n const idx = this.getDataset().indexOf(desiredToHide);\n this.getDataset().splice(idx, 1);\n\n await this.prefcountpipe();\n\n await this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.col);\n await this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.itemid);\n pendingPromise.resolve();\n }\n }\n\n /**\n * Handle any keyboard inputs.\n */\n registerInputEvents() {\n // Register & handle the text input.\n this.searchInput.addEventListener('input', debounce(async() => {\n if (this.getSearchTerm() === this.searchInput.value && this.searchResultsVisible()) {\n window.console.warn(`Search term matches input value - skipping`);\n // Debounce can happen multiple times quickly.\n return;\n }\n this.setSearchTerm
s(this.searchInput.value);\n // We can also require a set amount of input before search.\n if (this.searchInput.value === '') {\n // Hide the \"clear\" search button in the search bar.\n this.clearSearchButton.classList.add('d-none');\n } else {\n // Display the \"clear\" search button in the search bar.\n this.clearSearchButton.classList.remove('d-none');\n }\n const pendingPromise = new Pending();\n // User has given something for us to filter against.\n await this.filterrenderpipe().then(() => {\n pendingPromise.resolve();\n return true;\n });\n }, 300, {pending: true}));\n }\n\n /**\n * Handle the form submission within the dropdown.\n */\n registerFormEvents() {\n const form = this.component.querySelector(selectors.formDropdown);\n const events = [\n 'click',\n CustomEv
ents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n const selectall = form.querySelector('[data-action=\"selectall\"]');\n\n // Register clicks & keyboard form handling.\n events.forEach((event) => {\n const submitBtn = form.querySelector(`[data-action=\"${selectors.formItems.save}\"`);\n form.addEventListener(event, (e) => {\n // Stop Bootstrap from being clever.\n e.stopPropagation();\n const input = e.target.closest('input');\n if (input) {\n // If the user is unchecking an item, we need to uncheck the select all if it's checked.\n if (selectall.checked && !input.checked) {\n selectall.checked = false;\n }\n const checkedCount = Array.from(form.querySelectorAll(selectors.formItems.checked)).length;\n // Ch
eck if any are clicked or not then change disabled.\n submitBtn.disabled = checkedCount <= 0;\n }\n }, false);\n\n // Stop Bootstrap from being clever.\n this.searchInput.addEventListener(event, e => e.stopPropagation());\n this.clearSearchButton.addEventListener(event, async(e) => {\n e.stopPropagation();\n this.searchInput.value = '';\n this.setSearchTerms(this.searchInput.value);\n await this.filterrenderpipe();\n });\n selectall.addEventListener(event, (e) => {\n // Stop Bootstrap from being clever.\n e.stopPropagation();\n if (!selectall.checked) {\n const touncheck = Array.from(form.querySelectorAll(selectors.formItems.checked));\n touncheck.forEach(item => {\n item.checked = false;\n });\n submitBtn.dis
abled = true;\n } else {\n const currentUnchecked = Array.from(form.querySelectorAll(selectors.formItems.currentlyUnchecked));\n currentUnchecked.forEach(item => {\n item.checked = true;\n });\n submitBtn.disabled = false;\n }\n });\n });\n\n form.addEventListener('submit', async(e) => {\n e.preventDefault();\n if (e.submitter.dataset.action === selectors.formItems.cancel) {\n Dropdown.getOrCreateInstance(this.component).toggle();\n return;\n }\n // Get the users' checked columns to change.\n const checkedItems = [...form.elements].filter(item => item.checked);\n checkedItems.forEach((item) => {\n const idx = this.getDataset().indexOf(item.dataset.collapse);\n this.getDataset().splice(idx, 1);\n this.nodesUpdate(item.d
ataset.collapse);\n });\n // Reset the check all & submit to false just in case.\n selectall.checked = false;\n e.submitter.disabled = true;\n await this.prefcountpipe();\n });\n }\n\n async nodesUpdate(item) {\n const colNodesToHide = [...document.querySelectorAll(`[data-col=\"${item}\"]`)];\n const itemIDNodesToHide = [...document.querySelectorAll(`[data-itemid=\"${item}\"]`)];\n const elements = [...colNodesToHide, ...itemIDNodesToHide];\n if (elements && elements.length) {\n const pendingPromise = new Pending('gradereport_grader/collapse:nodesUpdate:' + item);\n this.updateDisplay(elements).then(() => pendingPromise.resolve()).catch(Notification.exception);\n }\n }\n\n /**\n * Update the user preferences, count display then render the results.\n *\n * @returns {Promise<void>}\n */\n async prefcountpipe() {\n this.setPreferences();\n this.count
Update();\n await this.filterrenderpipe();\n }\n\n /**\n * Dictate to the search component how and what we want to match upon.\n *\n * @param {Array} filterableData\n * @returns {Array} An array of objects containing the system reference and the user readable value.\n */\n async filterDataset(filterableData) {\n const stringUserMap = await this.fetchRequiredUserStrings();\n const stringGradeMap = await this.fetchRequiredGradeStrings();\n // Custom user profile fields are not in our string map and need a bit of extra love.\n const customFieldMap = this.fetchCustomFieldValues();\n this.stringMap = new Map([...stringGradeMap, ...stringUserMap, ...customFieldMap]);\n\n const searching = filterableData.map(s => {\n const mapObj = this.stringMap.get(s);\n if (mapObj === undefined) {\n return {key: s, string: s};\n }\n return {\n key: s,\n string: map
Obj.itemname ?? this.stringMap.get(s),\n category: mapObj.category ?? '',\n };\n });\n // Sometimes we just want to show everything.\n if (this.getPreppedSearchTerm() === '') {\n return searching;\n }\n // Other times we want to actually filter the content.\n return searching.filter((col) => {\n return col.string.toString().toLowerCase().includes(this.getPreppedSearchTerm());\n });\n }\n\n /**\n * Given we have a subset of the dataset, set the field that we matched upon to inform the end user.\n */\n filterMatchDataset() {\n this.setMatchedResults(\n this.getMatchedResults().map((column) => {\n return {\n name: column.key,\n displayName: column.string ?? column.key,\n category: column.category ?? '',\n };\n })\n );\n }\n\n /**\n * With an array of nodes, switch thei
r classes and values.\n *\n * @param {Array} elements The elements to update.\n */\n async updateDisplay(elements) {\n const promises = [];\n elements.forEach((element) => {\n promises.push(this.updateDisplayForElement(element));\n });\n\n await Promise.all(promises);\n }\n\n /**\n * Update display for given element, switch its classes and values.\n *\n * @param {HTMLElement} element The element to update.\n */\n async updateDisplayForElement(element) {\n const content = element.querySelector(selectors.content);\n const sort = element.querySelector(selectors.sort);\n const expandButton = element.querySelector(selectors.expandbutton);\n const rangeRowCell = element.querySelector(selectors.rangerowcell);\n const avgRowCell = element.querySelector(selectors.avgrowcell);\n const cellMenuButton = element.querySelector(selectors.cellMenuButton);\n const nodeSet = [\n element.qu
erySelector(selectors.menu),\n element.querySelector(selectors.icons),\n content\n ];\n\n // This can be further improved to reduce redundant similar calls.\n if (element.classList.contains('cell')) {\n // The column is actively being sorted, lets reset that and reload the page.\n if (sort !== null) {\n window.location = this.defaultSort;\n }\n if (content === null) {\n // If it's not a content cell, it must be an overall average or a range cell.\n const rowCell = avgRowCell ?? rangeRowCell;\n\n rowCell?.classList.toggle('d-none');\n } else if (content.classList.contains('d-none')) {\n // We should always have content but some cells do not contain menus or other actions.\n element.classList.remove('collapsed');\n // If there are many nodes, apply the following.\n if (content.childNodes.length >
1) {\n content.classList.add('d-flex');\n }\n nodeSet.forEach(node => {\n node?.classList.remove('d-none');\n });\n expandButton?.classList.add('d-none');\n cellMenuButton?.focus();\n } else {\n element.classList.add('collapsed');\n content.classList.remove('d-flex');\n nodeSet.forEach(node => {\n node?.classList.add('d-none');\n });\n expandButton?.classList.remove('d-none');\n }\n }\n }\n\n /**\n * Update the visual count of collapsed columns or hide the count all together.\n */\n countUpdate() {\n countIndicator.textContent = this.getDatasetSize();\n if (this.getDatasetSize() > 0) {\n this.component.parentElement.classList.add('d-flex');\n this.component.parentElement.classList.remove('d-none');\n } else {\n
this.component.parentElement.classList.remove('d-flex');\n this.component.parentElement.classList.add('d-none');\n }\n }\n\n /**\n * Build the content then replace the node by default we want our form to exist.\n */\n async renderDefault() {\n this.setMatchedResults(await this.filterDataset(this.getDataset()));\n this.filterMatchDataset();\n\n // Update the collapsed button pill.\n this.countUpdate();\n const {html, js} = await renderForPromise('gradereport_grader/collapse/collapsebody', {\n 'instance': this.instance,\n 'results': this.getMatchedResults(),\n 'userid': this.userID,\n });\n replaceNode(selectors.placeholder, html, js);\n this.updateNodes();\n\n // Given we now have the body, we can set up more triggers.\n this.registerFormEvents();\n this.registerInputEvents();\n\n // Add a small BS listener so that we can set the focus correctly on open.
\n this.component.addEventListener('shown.bs.dropdown', () => {\n this.searchInput.focus({preventScroll: true});\n this.selectallEnable();\n });\n }\n\n /**\n * Build the content then replace the node.\n */\n async renderDropdown() {\n const {html, js} = await renderForPromise('gradereport_grader/collapse/collapseresults', {\n instance: this.instance,\n 'results': this.getMatchedResults(),\n 'searchTerm': this.getSearchTerm(),\n });\n replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);\n this.selectallEnable();\n // Reset the expand button to be disabled as we have re-rendered the dropdown.\n const form = this.component.querySelector(selectors.formDropdown);\n const expandButton = form.querySelector(`[data-action=\"${selectors.formItems.save}\"`);\n expandButton.disabled = true;\n }\n\n /**\n * Given we render the dropdown, Determine if we w
ant to enable the select all checkbox.\n */\n selectallEnable() {\n const form = this.component.querySelector(selectors.formDropdown);\n const selectall = form.querySelector('[data-action=\"selectall\"]');\n selectall.disabled = this.getMatchedResults().length === 0;\n }\n\n /**\n * If we have any custom user profile fields, grab their system & readable names to add to our string map.\n *\n * @returns {array<string,*>} An array of associated string arrays ready for our map.\n */\n fetchCustomFieldValues() {\n const customFields = document.querySelectorAll('[data-collapse-name]');\n // Cast from NodeList to array to grab all the values.\n return [...customFields].map(field => [field.parentElement.dataset.col, field.dataset.collapseName]);\n }\n\n /**\n * Given the set of profile fields we can possibly search, fetch their strings,\n * so we can report to screen readers the field that matched.\n *\n * @returns {Promi
se<void>}\n */\n fetchRequiredUserStrings() {\n if (!this.userStrings) {\n const requiredStrings = [\n 'username',\n 'firstname',\n 'lastname',\n 'email',\n 'city',\n 'country',\n 'department',\n 'institution',\n 'idnumber',\n 'phone1',\n 'phone2',\n ];\n this.userStrings = getStrings(requiredStrings.map((key) => ({key})))\n .then((stringArray) => new Map(\n requiredStrings.map((key, index) => ([key, stringArray[index]]))\n ));\n }\n return this.userStrings;\n }\n\n /**\n * Given the set of gradable items we can possibly search, fetch their strings,\n * so we can report to screen readers the field that matched.\n *\n * @returns {Promise<void>}\n */\n fetchRequiredGradeStrings() {\n if (!this.gradeString
s) {\n this.gradeStrings = Repository.gradeItems(this.courseID)\n .then((result) => new Map(\n result.gradeItems.map(key => ([key.id, key]))\n ));\n }\n return this.gradeStrings;\n }\n}\n"],"names":["selectors","cancel","save","checked","currentlyUnchecked","countIndicator","document","querySelector","ColumnSearch","search_combobox","userID","courseID","defaultSort","constructor","component","pendingPromise","Pending","then","loader","setTimeout","getDataset","forEach","item","nodesUpdate","renderDefault","remove","classList","resolve","catch","Notification","exception","addEventListener","this","scrollTop","searchInput","value","dispatchEvent","Event","bubbles","componentSelector","dropdownSelector","dataset","cols","fetchDataset","JSON","parse","split","datasetSize","length","storage","get","setPreferences","set","stringify","join","registerClickHandlers","clickHandler","bind","docClickHandler","e","target","closest","stopPropagat
ion","hider","preventDefault","desiredToHide","_e$target$closest","col","_e$target$closest2","itemid","indexOf","push","prefcountpipe","_e$target$closest4","_e$target$closest5","idx","splice","_e$target$closest6","_e$target$closest7","registerInputEvents","async","getSearchTerm","searchResultsVisible","window","console","warn","setSearchTerms","clearSearchButton","add","filterrenderpipe","pending","registerFormEvents","form","events","CustomEvents","activate","keyboardActivate","define","selectall","event","submitBtn","input","checkedCount","Array","from","querySelectorAll","disabled","submitter","action","getOrCreateInstance","toggle","elements","filter","collapse","updateDisplay","countUpdate","filterableData","stringUserMap","fetchRequiredUserStrings","stringGradeMap","fetchRequiredGradeStrings","customFieldMap","fetchCustomFieldValues","stringMap","Map","searching","map","s","mapObj","undefined","key","string","itemname","category","getPreppedSearchTerm","toString","toLowerCase","includes","filterMatchDa
taset","setMatchedResults","getMatchedResults","column","name","displayName","promises","element","updateDisplayForElement","Promise","all","content","sort","expandButton","rangeRowCell","avgRowCell","cellMenuButton","nodeSet","contains","location","rowCell","childNodes","node","focus","textContent","getDatasetSize","parentElement","filterDataset","html","js","instance","updateNodes","preventScroll","selectallEnable","getHTMLElements","searchDropdown","field","collapseName","userStrings","requiredStrings","stringArray","index","gradeStrings","Repository","gradeItems","result","id"],"mappings":"4hEAmCMA,oBACS,oBADTA,uBAEY,uBAFZA,oBAGS,CACPC,OAAQ,SACRC,KAAM,OACNC,QAAS,iCACTC,mBAAoB,yDAPtBJ,gBASK,OATLA,iBAUM,SAVNA,iBAWM,aAXNA,kBAYO,gBAZPA,kBAaO,4BAbPA,eAcI,yBAdJA,uBAeY,iCAfZA,uBAgBY,iCAhBZA,qBAiBU,+BAjBVA,eAkBI,yBAlBJA,gBAmBK,4BAnBLA,gBAoBK,0BApBLA,sBAqBW,sDArBXA,uBAsBY,0BAtBZA,gCAuBqB,8BAvBrBA,yBAwBc,eAGdK,eAAiBC,SAASC,cAAcP,uBAEzBQ,qBAAqBC,qCAY1BC,OAAQC,SAAUC,oBACnB,IAAIJ,aAAaE,OAAQC,SAAUC,aAG9CC,YAAYH,OAAQC,
SAAUC,oDAdpB,mCACC,yCACG,iCAEN,wCAEO,yCACD,uCACF,SAQHF,OAASA,YACTC,SAAWA,cACXC,YAAcA,iBACdE,UAAYR,SAASC,cAAcP,2BAElCe,eAAiB,IAAIC,qDAERV,SAASC,cAAc,iBAAiBU,MAAMC,SAC7DC,YAAW,UAEFC,aAAaC,SAASC,YAClBC,YAAYD,cAEhBE,gBAGLN,OAAOO,SACPnB,SAASC,cAAc,6BAA6BmB,UAAUD,OAAO,YACtE,OACJR,MAAK,IAAMF,eAAeY,YAAWC,MAAMC,sBAAaC,gBAEtDhB,UAAUiB,iBAAiB,oBAAoB,KAClBC,KAAKlB,UAAUP,cAAcP,iCACrCiC,UAAY,EAGlCd,YAAW,KACwB,KAA3Ba,KAAKE,YAAYC,aACZD,YAAYC,MAAQ,QACpBD,YAAYE,cAAc,IAAIC,MAAM,QAAS,CAACC,SAAS,YAW5EC,0BACW,oBAQXC,yBACW,8BAQXpB,iBACSY,KAAKS,QAAS,OACTC,KAAOV,KAAKW,oBACbF,QAAUG,KAAKC,MAAMH,MAAQE,KAAKC,MAAMH,MAAMI,MAAM,KAAO,eAE/DC,YAAcf,KAAKS,QAAQO,OACzBhB,KAAKS,QAQhBE,sBACWM,sBAAQC,gDAAyClB,KAAKrB,qBAAYqB,KAAKtB,SAMlFyC,uCACYC,gDAAyCpB,KAAKrB,qBAAYqB,KAAKtB,QACnEkC,KAAKS,UAAUrB,KAAKZ,aAAakC,KAAK,OAO9CC,6BAESzC,UAAUiB,iBAAiB,QAASC,KAAKwB,aAAaC,KAAKzB,OAEhE1B,SAASyB,iBAAiB,QAASC,KAAK0B,gBAAgBD,KAAKzB,OAQjEwB,aAAaG,SACHH,aAAaG,GAEfA,EAAEC,OAAOC,QAAQ7D,yBACjB2D,EAAEG,wCAUYH,6BACdA,EAAEC,OAAOnB,QAAQsB,QAAU/D,gBAAiB,0CAC5C2D,EAAEK,uBAC
IjD,eAAiB,IAAIC,iBAAQ,oDAC7BiD,cAAgBN,EAAEC,OAAOC,QAAQ7D,4CACnC2D,EAAEC,OAAOC,QAAQ7D,sDAAjBkE,kBAAoCzB,QAAQ0B,+BAC5CR,EAAEC,OAAOC,QAAQ7D,wDAAjBoE,mBAAqC3B,QAAQ4B,QAEpC,IADDrC,KAAKZ,aAAakD,QAAQL,qBAE7B7C,aAAamD,KAAKN,qBAErBjC,KAAKwC,sBAELxC,KAAKT,YAAY0C,eACvBlD,eAAeY,yCAGfgC,EAAEC,OAAOC,QAAQ,kEAAWpB,QAAQsB,SAAU/D,iBAAkB,iFAChE2D,EAAEK,uBACIjD,eAAiB,IAAIC,iBAAQ,sDAC7BiD,cAAgBN,EAAEC,OAAOC,QAAQ7D,6CACnC2D,EAAEC,OAAOC,QAAQ7D,uDAAjByE,mBAAoChC,QAAQ0B,+BAC5CR,EAAEC,OAAOC,QAAQ7D,wDAAjB0E,mBAAqCjC,QAAQ4B,OAC3CM,IAAM3C,KAAKZ,aAAakD,QAAQL,oBACjC7C,aAAawD,OAAOD,IAAK,SAExB3C,KAAKwC,sBAELxC,KAAKT,uCAAYoC,EAAEC,OAAOC,QAAQ7D,uDAAjB6E,mBAAoCpC,QAAQ0B,WAC7DnC,KAAKT,uCAAYoC,EAAEC,OAAOC,QAAQ7D,uDAAjB8E,mBAAoCrC,QAAQ4B,QACnEtD,eAAeY,WAOvBoD,2BAES7C,YAAYH,iBAAiB,SAAS,oBAASiD,aAC5ChD,KAAKiD,kBAAoBjD,KAAKE,YAAYC,OAASH,KAAKkD,mCACxDC,OAAOC,QAAQC,wDAIdC,eAAetD,KAAKE,YAAYC,OAEN,KAA3BH,KAAKE,YAAYC,WAEZoD,kBAAkB7D,UAAU8D,IAAI,eAGhCD,kBAAkB7D,UAAUD,OAAO,gBAEtCV,eAAiB,IAAIC,uBAErBgB,KAAKyD,mBAAmBxE,MAAK,KAC/BF,eAAeY,WACR,OAEZ,IAAK,CAAC+D
,SAAS,KAMtBC,2BACUC,KAAO5D,KAAKlB,UAAUP,cAAcP,wBACpC6F,OAAS,CACX,QACAC,mCAAaD,OAAOE,SACpBD,mCAAaD,OAAOG,qDAEXC,OAAO3F,SAAUuF,cAExBK,UAAYN,KAAKrF,cAAc,6BAGrCsF,OAAOxE,SAAS8E,cACNC,UAAYR,KAAKrF,sCAA+BP,oBAAoBE,WAC1E0F,KAAK7D,iBAAiBoE,OAAQxC,IAE1BA,EAAEG,wBACIuC,MAAQ1C,EAAEC,OAAOC,QAAQ,YAC3BwC,MAAO,CAEHH,UAAU/F,UAAYkG,MAAMlG,UAC5B+F,UAAU/F,SAAU,SAElBmG,aAAeC,MAAMC,KAAKZ,KAAKa,iBAAiBzG,oBAAoBG,UAAU6C,OAEpFoD,UAAUM,SAAWJ,cAAgB,MAE1C,QAGEpE,YAAYH,iBAAiBoE,OAAOxC,GAAKA,EAAEG,yBAC3CyB,kBAAkBxD,iBAAiBoE,OAAOnB,MAAAA,IAC3CrB,EAAEG,uBACG5B,YAAYC,MAAQ,QACpBmD,eAAetD,KAAKE,YAAYC,aAC/BH,KAAKyD,sBAEfS,UAAUnE,iBAAiBoE,OAAQxC,OAE/BA,EAAEG,kBACGoC,UAAU/F,QAMR,CACsBoG,MAAMC,KAAKZ,KAAKa,iBAAiBzG,oBAAoBI,qBAC7DiB,SAAQC,OACrBA,KAAKnB,SAAU,KAEnBiG,UAAUM,UAAW,MAXD,CACFH,MAAMC,KAAKZ,KAAKa,iBAAiBzG,oBAAoBG,UAC7DkB,SAAQC,OACdA,KAAKnB,SAAU,KAEnBiG,UAAUM,UAAW,SAWjCd,KAAK7D,iBAAiB,UAAUiD,MAAAA,OAC5BrB,EAAEK,iBACEL,EAAEgD,UAAUlE,QAAQmE,SAAW5G,oBAAoBC,qCAC1C4G,oBAAoB7E,KAAKlB,WAAWgG,SAI5B,IAAIlB,KAAKmB,UAAUC,QAAO1F,MAAQA,KAAKnB,UAC/CkB,SAA
SC,aACZqD,IAAM3C,KAAKZ,aAAakD,QAAQhD,KAAKmB,QAAQwE,eAC9C7F,aAAawD,OAAOD,IAAK,QACzBpD,YAAYD,KAAKmB,QAAQwE,aAGlCf,UAAU/F,SAAU,EACpBwD,EAAEgD,UAAUD,UAAW,QACjB1E,KAAKwC,qCAIDlD,YAGRyF,SAAW,IAFM,IAAIzG,SAASmG,sCAA+BnF,gBACzC,IAAIhB,SAASmG,yCAAkCnF,iBAErEyF,UAAYA,SAAS/D,OAAQ,OACvBjC,eAAiB,IAAIC,iBAAQ,2CAA6CM,WAC3E4F,cAAcH,UAAU9F,MAAK,IAAMF,eAAeY,YAAWC,MAAMC,sBAAaC,uCAUpFqB,sBACAgE,oBACCnF,KAAKyD,uCASK2B,sBACVC,oBAAsBrF,KAAKsF,2BAC3BC,qBAAuBvF,KAAKwF,4BAE5BC,eAAiBzF,KAAK0F,8BACvBC,UAAY,IAAIC,IAAI,IAAIL,kBAAmBF,iBAAkBI,uBAE5DI,UAAYT,eAAeU,KAAIC,gDAC3BC,OAAShG,KAAK2F,UAAUzE,IAAI6E,eACnBE,IAAXD,OACO,CAACE,IAAKH,EAAGI,OAAQJ,GAErB,CACHG,IAAKH,EACLI,gCAAQH,OAAOI,sDAAYpG,KAAK2F,UAAUzE,IAAI6E,GAC9CM,kCAAUL,OAAOK,sDAAY,aAID,KAAhCrG,KAAKsG,uBACET,UAGJA,UAAUb,QAAQ7C,KACdA,IAAIgE,OAAOI,WAAWC,cAAcC,SAASzG,KAAKsG,0BAOjEI,0BACSC,kBACD3G,KAAK4G,oBAAoBd,KAAKe,mDACnB,CACHC,KAAMD,OAAOX,IACba,mCAAaF,OAAOV,gDAAUU,OAAOX,IACrCG,kCAAUQ,OAAOR,sDAAY,4BAWzBtB,gBACViC,SAAW,GACjBjC,SAAS1F,SAAS4H,UACdD,SAASzE,KAAKvC,KAAKkH,wBAAwBD,mBAGzCE,QAAQC
,IAAIJ,wCAQQC,eACpBI,QAAUJ,QAAQ1I,cAAcP,mBAChCsJ,KAAOL,QAAQ1I,cAAcP,gBAC7BuJ,aAAeN,QAAQ1I,cAAcP,wBACrCwJ,aAAeP,QAAQ1I,cAAcP,wBACrCyJ,WAAaR,QAAQ1I,cAAcP,sBACnC0J,eAAiBT,QAAQ1I,cAAcP,0BACvC2J,QAAU,CACZV,QAAQ1I,cAAcP,gBACtBiJ,QAAQ1I,cAAcP,iBACtBqJ,YAIAJ,QAAQvH,UAAUkI,SAAS,WAEd,OAATN,OACAnE,OAAO0E,SAAW7H,KAAKpB,aAEX,OAAZyI,QAAkB,OAEZS,QAAUL,MAAAA,WAAAA,WAAcD,aAE9BM,MAAAA,SAAAA,QAASpI,UAAUoF,OAAO,eACnBuC,QAAQ3H,UAAUkI,SAAS,WAElCX,QAAQvH,UAAUD,OAAO,aAErB4H,QAAQU,WAAW/G,OAAS,GAC5BqG,QAAQ3H,UAAU8D,IAAI,UAE1BmE,QAAQtI,SAAQ2I,OACZA,MAAAA,MAAAA,KAAMtI,UAAUD,OAAO,aAE3B8H,MAAAA,cAAAA,aAAc7H,UAAU8D,IAAI,UAC5BkE,MAAAA,gBAAAA,eAAgBO,UAEhBhB,QAAQvH,UAAU8D,IAAI,aACtB6D,QAAQ3H,UAAUD,OAAO,UACzBkI,QAAQtI,SAAQ2I,OACZA,MAAAA,MAAAA,KAAMtI,UAAU8D,IAAI,aAExB+D,MAAAA,cAAAA,aAAc7H,UAAUD,OAAO,WAQ3C0F,cACI9G,eAAe6J,YAAclI,KAAKmI,iBAC9BnI,KAAKmI,iBAAmB,QACnBrJ,UAAUsJ,cAAc1I,UAAU8D,IAAI,eACtC1E,UAAUsJ,cAAc1I,UAAUD,OAAO,iBAEzCX,UAAUsJ,cAAc1I,UAAUD,OAAO,eACzCX,UAAUsJ,cAAc1I,UAAU8D,IAAI,sCAQ1CmD,wBAAwB3G,KAAKqI,cAAcrI,KAAKZ,oBAChDsH,0BAGAvB,o
BACCmD,KAACA,KAADC,GAAOA,UAAY,+BAAiB,2CAA4C,UACtEvI,KAAKwI,iBACNxI,KAAK4G,2BACN5G,KAAKtB,oCAEPV,sBAAuBsK,KAAMC,SACpCE,mBAGA9E,0BACAZ,2BAGAjE,UAAUiB,iBAAiB,qBAAqB,UAC5CG,YAAY+H,MAAM,CAACS,eAAe,SAClCC,kDAQHL,KAACA,KAADC,GAAOA,UAAY,+BAAiB,8CAA+C,CACrFC,SAAUxI,KAAKwI,iBACJxI,KAAK4G,+BACF5G,KAAKiD,qDAEHjD,KAAK4I,kBAAkBC,eAAgBP,KAAMC,SAC5DI,kBAEQ3I,KAAKlB,UAAUP,cAAcP,wBAChBO,sCAA+BP,oBAAoBE,WAChEwG,UAAW,EAM5BiE,kBACiB3I,KAAKlB,UAAUP,cAAcP,wBACnBO,cAAc,6BAC3BmG,SAA+C,IAApC1E,KAAK4G,oBAAoB5F,OAQlD0E,+BAGW,IAFcpH,SAASmG,iBAAiB,yBAEtBqB,KAAIgD,OAAS,CAACA,MAAMV,cAAc3H,QAAQ0B,IAAK2G,MAAMrI,QAAQsI,gBAS1FzD,+BACStF,KAAKgJ,YAAa,OACbC,gBAAkB,CACpB,WACA,YACA,WACA,QACA,OACA,UACA,aACA,cACA,WACA,SACA,eAECD,aAAc,mBAAWC,gBAAgBnD,KAAKI,OAAUA,IAAAA,SACxDjH,MAAMiK,aAAgB,IAAItD,IACvBqD,gBAAgBnD,KAAI,CAACI,IAAKiD,QAAW,CAACjD,IAAKgD,YAAYC,oBAG5DnJ,KAAKgJ,YAShBxD,mCACSxF,KAAKoJ,oBACDA,aAAeC,WAAWC,WAAWtJ,KAAKrB,UAC1CM,MAAMsK,QAAW,IAAI3D,IAClB2D,OAAOD,WAAWxD,KAAII,KAAQ,CAACA,IAAIsD,GAAItD,WAG5ClG,KAAKoJ"}