Neale Pickett
·
2023-09-14
ksa.mjs
1import * as moth from "../moth.mjs"
2import * as common from "../common.mjs"
3
4const server = new moth.Server("../")
5
6/**
7 * Update "doing" indicators
8 *
9 * @param {String | null} what Text to display, or null to not update text
10 * @param {Number | null} finished Percentage complete to display, or null to not update progress
11 */
12function doing(what, finished = null) {
13 for (let e of document.querySelectorAll(".doing")) {
14 e.classList.remove("hidden")
15 if (what) {
16 e.textContent = what
17 }
18 if (finished) {
19 e.value = finished
20 } else {
21 e.removeAttribute("value")
22 }
23 }
24}
25function done() {
26 for (let e of document.querySelectorAll(".doing")) {
27 e.classList.add("hidden")
28 }
29}
30
31async function GetNice() {
32 let NiceElementsByIdentifier = {}
33 let resp = await fetch("NICEFramework2017.json")
34 let obj = await resp.json()
35 for (let e of obj.elements) {
36 NiceElementsByIdentifier[e.element_identifier] = e
37 }
38 return NiceElementsByIdentifier
39}
40
41/**
42 * Fetch a puzzle, and fill its KSAs and rows.
43 *
44 * This is done once per puzzle, in an asynchronous function, allowing the
45 * application to perform multiple blocking operations simultaneously.
46 */
47async function FetchAndFill(puzzle, KSAs, rows) {
48 try {
49 await puzzle.Populate()
50 }
51 catch (error) {
52 // Keep on going with whatever Populate was able to fill
53 }
54 for (let KSA of (puzzle.KSAs || [])) {
55 KSAs.add(KSA)
56 }
57
58 for (let row of rows) {
59 row.querySelector(".category").textContent = puzzle.Category
60 row.querySelector(".points").textContent = puzzle.Points
61 row.querySelector(".ksas").textContent = (puzzle.KSAs || []).join(" ")
62 row.querySelector(".error").textContent = puzzle.Error.Body
63 }
64}
65
66async function init() {
67 doing("Fetching NICE framework data")
68 let nicePromise = GetNice()
69
70 doing("Retrieving server state")
71 let state = await server.GetState()
72
73 doing("Retrieving all puzzles")
74 let KSAsByCategory = {}
75 let puzzlerowTemplate = document.querySelector("template#puzzlerow")
76 let puzzles = state.Puzzles()
77 let promises = []
78 for (let category of state.Categories()) {
79 KSAsByCategory[category] = new Set()
80 }
81 let pending = puzzles.length
82 for (let puzzle of puzzles) {
83 // Make space in the table, so everything fills in sorted order
84 let rows = []
85 for (let tbody of document.querySelectorAll("tbody")) {
86 let row = puzzlerowTemplate.content.cloneNode(true).firstElementChild
87 tbody.appendChild(row)
88 rows.push(row)
89 }
90
91 // Queue up a fetch, and update progress bar
92 let promise = FetchAndFill(puzzle, KSAsByCategory[puzzle.Category], rows)
93 promises.push(promise)
94 promise.then(() => doing(null, 1 - (--pending / puzzles.length)))
95
96 if (promises.length > 50) {
97 // Chrome runs out of resources if you queue up too many of these at once
98 await Promise.all(promises)
99 promises = []
100 }
101 }
102 await Promise.all(promises)
103
104 doing("Retrieving NICE identifiers")
105 let NiceElementsByIdentifier = await nicePromise
106
107
108 doing("Filling KSAs By Category")
109 let allKSAs = new Set()
110 for (let div of document.querySelectorAll(".KSAsByCategory")) {
111 for (let category of state.Categories()) {
112 doing(`Filling KSAs for category: ${category}`)
113 let KSAs = [...KSAsByCategory[category]]
114 KSAs.sort()
115
116 div.appendChild(document.createElement("h3")).textContent = category
117 let ul = div.appendChild(document.createElement("ul"))
118 for (let k of KSAs) {
119 let ksa = k.split(/\s+/)[0]
120 let ne = NiceElementsByIdentifier[ksa] || { text: "???" }
121 let text = `${ksa}: ${ne.text}`
122 ul.appendChild(document.createElement("li")).textContent = text
123 allKSAs.add(text)
124 }
125 }
126 }
127
128 doing("Filling KSAs")
129 for (let e of document.querySelectorAll(".allKSAs")) {
130 let KSAs = [...allKSAs]
131 KSAs.sort()
132 for (let text of KSAs) {
133 e.appendChild(document.createElement("li")).textContent = text
134 }
135 }
136
137 done()
138}
139
140common.WhenDOMLoaded(init)