diff --git a/public/dom-building.js b/public/dom-building.js
new file mode 100644
index 0000000..41db48c
--- /dev/null
+++ b/public/dom-building.js
@@ -0,0 +1,207 @@
+
+async function buildRemovalCell(td, sKey, pKey) {
+ td.textContent = "x"
+ td.addEventListener("click", async function() {
+ if (confirm("Do you want to remove this entry?")) {
+ console.log("Removing entry:", sKey, pKey)
+ searchSubjectNodeRecursively(userProfile, sKey, (node) => delete node[pKey])
+ await finalizeProfileChanges()
+ }
+ })
+}
+
+function buildProfileTableRecursively(node, depth, table) {
+ let subject = node["@id"]
+ for (let [predicate, objectOrArray] of Object.entries(node)) {
+ if (predicate.startsWith("@")) continue
+ if (!Array.isArray(objectOrArray)) {
+ let tds = buildRowAndColumns(table)
+ let label = determineLabelForTableEntries(predicate)
+ tds[depth].textContent = label
+ tds[depth].title = predicate
+ tds[depth + 1].textContent = determineLabelForTableEntries(objectOrArray)
+ tds[depth + 1].title = objectOrArray
+ tds[depth + 1].addEventListener("click", async function() {
+ let input = prompt(`Enter new value for ${label}`)
+ if (input !== null) {
+ console.log("Changing entry value:", subject, predicate, "-->", input)
+ searchSubjectNodeRecursively(userProfile, subject, (node) => node[predicate] = input)
+ await finalizeProfileChanges()
+ }
+ })
+ buildRemovalCell(tds[maxDepth + 2], subject, predicate)
+ continue
+ }
+ for (let arrayElement of objectOrArray) {
+ let tds = buildRowAndColumns(table)
+ tds[depth].textContent = dfShortUriToLabel(predicate)
+ tds[depth].title = predicate
+ // this will delete all array elements of the same type at the moment TODO fix
+ buildRemovalCell(tds[maxDepth + 2], subject, predicate)
+ buildProfileTableRecursively(arrayElement, depth + 1, table)
+ }
+ }
+}
+
+// this is needed to know the correct number of columns in the profile table in advance
+function determineDepthOfProfileTreeRecursively(jsonNode, depth) {
+ if (depth > maxDepth) maxDepth = depth
+ for (let objectOrArray of Object.values(jsonNode)) {
+ if (!Array.isArray(objectOrArray)) continue
+ for (let arrayElement of objectOrArray) determineDepthOfProfileTreeRecursively(arrayElement, depth + 1)
+ }
+}
+
+function buildProfileTable() {
+ let div = document.getElementById("userProfileDiv")
+ div.innerHTML = ""
+ let table = document.createElement("table")
+ table.className = "framed-table"
+ div.appendChild(table)
+ maxDepth = 0
+ determineDepthOfProfileTreeRecursively(userProfile, 0)
+ buildProfileTableRecursively(userProfile, 0, table)
+}
+
+function buildFocusInputSelectChoices() {
+ let categories = {}
+ let rps = []
+ for (let rp of Object.values(metadata.rp)) {
+ rps.push({
+ value: rp.uri,
+ label: rp.title,
+ })
+ for (let cat of rp.categories) {
+ if (!categories[cat]) categories[cat] = []
+ categories[cat].push(rp.uri)
+ }
+ }
+ metadata.categories = categories
+ focusInputSelect.setChoices([
+ {
+ label: "Categories",
+ choices: Object.keys(categories).map(uri => {
+ return {
+ value: uri,
+ label: uri.split("#")[1],
+ }
+ })
+ },
+ {
+ label: "Requirement Profiles",
+ choices: rps
+ },
+ ])
+}
+
+async function buildPrioritizedMissingDataList() {
+ let div = document.getElementById("missingDataPointsDiv")
+ div.textContent = ""
+ let prioritizedList = []
+
+ let missingData = validateAllReport.missingUserInputsAggregated
+
+ let focusRPs = []
+ for (let focusItem of focusInputSelect.getValue(true)) {
+ if (metadata.categories[focusItem]) {
+ focusRPs = focusRPs.concat(metadata.categories[focusItem])
+ } else {
+ focusRPs.push(focusItem)
+ }
+ }
+
+ for (let datafield of Object.values(missingData)) {
+ let usedInRpUris = datafield.usedIn.map(usedInRP => usedInRP.rpUri)
+ if (focusRPs.length > 0 && !usedInRpUris.some(rpUri => focusRPs.includes(rpUri))) {
+ continue
+ }
+
+ let usedInTitles = []
+ let lastMissingCounter = 0
+ for (let usedInRP of datafield.usedIn) {
+ usedInTitles.push(metadata.rp[usedInRP.rpUri].title + (usedInRP.isLastMissingUserInput ? " (!)" : ""))
+ if (usedInRP.isLastMissingUserInput) lastMissingCounter += 1
+ }
+ prioritizedList.push({
+ subject: datafield.subject,
+ dfUri: datafield.dfUri,
+ objectHasClass: metadata.df[datafield.dfUri]?.objectHasClass,
+ label: metadata.df[datafield.dfUri]?.label ?? datafield.dfUri.split("#")[1],
+ score: datafield.usedIn.length + lastMissingCounter,
+ usedInTitles: usedInTitles
+ })
+ }
+ prioritizedList.sort((a, b) => b.score - a.score)
+ prioritizedList.forEach((entry) => { // entry = wrapped datafield
+ let spanEl = document.createElement("span")
+ spanEl.title = entry.usedInTitles.join("\n")
+ spanEl.style.color = "gray"
+ let textNode = document.createTextNode(entry.score + ": ")
+ spanEl.appendChild(textNode)
+ div.appendChild(spanEl)
+ spanEl = document.createElement("a")
+ spanEl.textContent = collectSubjectClassLabelsAlongPath(entry)
+ searchNodeByEntryPredicateRecursively(userProfile, "ff:hasDeferred", (node) => {
+ node["ff:hasDeferred"].forEach(deferment => {
+ if (deferment["rdf:subject"] === entry.subject && deferment["rdf:predicate"] === entry.dfUri) {
+ spanEl.style.textDecoration = "line-through"
+ }
+ })
+ })
+ spanEl.addEventListener("click", async function(event) {
+ event.preventDefault()
+ if (entry.objectHasClass) {
+ if (confirm("Do you want to add a " + metadata.df[entry.objectHasClass].label + "?")) {
+ instantiateNewObjectClassUnderSubject(entry.subject, entry.dfUri, entry.objectHasClass)
+ await finalizeProfileChanges()
+ }
+ return
+ }
+ let input = prompt("What is your value for: " + entry.label)
+ if (input !== null) {
+ addEntryToSubject(entry.subject, entry.dfUri, input)
+ await finalizeProfileChanges()
+ }
+ })
+ div.appendChild(spanEl)
+ spanEl = document.createElement("span")
+ spanEl.style.fontSize = "x-small"
+ spanEl.style.color = "silver"
+ spanEl.innerHTML = " defer this"
+ spanEl.addEventListener("click", async function() {
+ addDeferment(entry.subject, entry.dfUri)
+ await finalizeProfileChanges()
+ })
+ div.appendChild(spanEl)
+ div.appendChild(document.createElement("br"))
+ })
+ for (const elem of Array.from(document.getElementsByClassName("loadingDiv"))) {
+ elem.style.display = "none"
+ }
+}
+
+function collectSubjectClassLabelsAlongPath(entry) { // prioritizedListEntry
+ if (shortenLongUri(entry.subject) === "ff:mainPerson") {
+ return entry.label
+ }
+ let sUri = shortenLongUri(entry.subject)
+ let path = ""
+ let finalPath
+ function recurse(node, arrIndex, sUri, path) {
+ if (node["@type"] !== "ff:Citizen") path += ", " + metadata.df[expandShortUri(node["@type"])].label + " " + arrIndex
+ for (let objectOrArray of Object.values(node)) {
+ if (objectOrArray === sUri) {
+ finalPath = path.substring(1)
+ }
+ if (Array.isArray(objectOrArray)) {
+ for (let i = 0; i < objectOrArray.length; i++) {
+ let arrayEl = objectOrArray[i]
+ recurse(arrayEl, i, sUri, path)
+ }
+ }
+ }
+
+ }
+ recurse(userProfile, 0, sUri, path)
+ return finalPath + ": " + entry.label
+}
diff --git a/public/index.html b/public/index.html
index 866e601..d341fd2 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,6 +4,11 @@
FörderFunke
+
+
+
+
+
@@ -64,471 +69,6 @@ Report
await buildPrioritizedMissingDataList()
}, false)
- const EMPTY_PROFILE = { "@id": "ff:mainPerson", "@type": "ff:Citizen" }
- let userProfile
- let maxDepth = 0
- let latestRPsRepoCommit
- let turtleMap
- let metadata
- let validateAllReport
- let eligibleRPs
-
- async function parseTurtleFiles() {
- turtleMap = {
- "datafields": await fetchAsset("requirement-profiles/datafields.ttl"),
- "materialization": await fetchAsset("requirement-profiles/materialization.ttl"),
- "shacl": {}
- }
- const shaclListCsv = await fetchAsset("shacl-list.csv")
- for (let line of shaclListCsv.split("\n")) {
- let [filename, rpUri] = line.split(",")
- rpUri = expandShortUri(rpUri)
- turtleMap.shacl[rpUri] = await fetchAsset("requirement-profiles/shacl/" + filename)
- }
- metadata = {
- df: await MatchingEngine.extractDatafieldsMetadata(turtleMap.datafields),
- rp: await MatchingEngine.extractRequirementProfilesMetadata(Object.values(turtleMap.shacl))
- }
-
- const selectEl = document.getElementById("dfDropdown")
- selectEl.innerHTML = ""
- for (let df of Object.values(metadata.df)) {
- const optionEl = document.createElement("option")
- optionEl.value = df.uri
- optionEl.textContent = df.label
- selectEl.appendChild(optionEl)
- }
- selectEl.addEventListener("change", function(event) {
- const selectedValue = event.target.value
- // TODO
- })
-
- console.log("metadata", metadata)
- buildFocusInputSelectChoices()
- }
-
- function buildFocusInputSelectChoices() {
- let categories = {}
- let rps = []
- for (let rp of Object.values(metadata.rp)) {
- rps.push({
- value: rp.uri,
- label: rp.title,
- })
- for (let cat of rp.categories) {
- if (!categories[cat]) categories[cat] = []
- categories[cat].push(rp.uri)
- }
- }
- metadata.categories = categories
- focusInputSelect.setChoices([
- {
- label: "Categories",
- choices: Object.keys(categories).map(uri => {
- return {
- value: uri,
- label: uri.split("#")[1],
- }
- })
- },
- {
- label: "Requirement Profiles",
- choices: rps
- },
- ])
- }
-
- async function update() {
- await buildProfileTable()
-
- let userProfileTurtle = await MatchingEngine.convertUserProfileToTurtle(userProfile)
- console.log("userProfileTurtle", userProfileTurtle)
- validateAllReport = await MatchingEngine.validateAll(userProfileTurtle, turtleMap.shacl, turtleMap.datafields, turtleMap.materialization)
- console.log("validateAllReport", validateAllReport)
-
- let tableEl = document.getElementById("reportTable")
- tableEl.textContent = ""
- eligibleRPs = []
-
- for (let report of validateAllReport.reports) {
- if (report.result === "eligible") {
- eligibleRPs.push(report.rpUri)
- }
- let tr = document.createElement("tr")
- let td = document.createElement("td")
- td.textContent = getRpTitle(report.rpUri)
- searchNodeByEntryPredicateRecursively(userProfile, "ff:hasCompliedRequirementProfile", (node) => {
- node["ff:hasCompliedRequirementProfile"].forEach(rp => {
- if (rp["ff:hasRpUri"] === report.rpUri) {
- td.style.textDecoration = "line-through"
- }
- })
- })
- if (report.containsDeferredMissingUserInput) {
- td.style.textDecoration = "line-through"
- }
- if (!metadata.rp[report.rpUri] || metadata.rp[report.rpUri].categories.length === 0) {
- td.title = "No category info available"
- } else {
- td.title = metadata.rp[report.rpUri].categories.map(cat => cat.split("#")[1]).join("\n")
- }
- tr.appendChild(td)
- td = document.createElement("td")
- td.textContent = report.result
- let msg = ""
- switch (report.result) {
- case "eligible":
- td.style.color = "green"
- td.style.fontWeight = "bold"
- msg += JSON.stringify(report.materializationReport) // TODO
- break
- case "ineligible":
- td.style.color = "red"
- for (let violation of report.violations) {
- msg += violation.message + "\n"
- }
- break
- case "undeterminable":
- td.style.color = "gray"
- msg += "Missing data points:\n"
- for (let missing of report.missingUserInput) {
- if (metadata.df[missing.dfUri]) {
- msg += "- " + metadata.df[missing.dfUri].label + "\n"
- }
- }
- break
- }
- td.title = msg
- tr.appendChild(td)
- td = document.createElement("td")
- td.style.fontSize = "x-small"
- td.style.color = "silver"
- td.innerHTML = " already getting this"
- td.addEventListener("click", async function() {
- let newInstanceUri = instantiateNewObjectClassUnderSubject("ff:mainPerson", "ff:hasCompliedRequirementProfile", "ff:CompliedRequirementProfile")
- addEntryToSubject(newInstanceUri, "ff:hasRpUri", report.rpUri)
-
- let userProfileTurtle = await MatchingEngine.convertUserProfileToTurtle(userProfile)
- let inferenceReport = await MatchingEngine.inferNewUserDataFromCompliedRPs(userProfileTurtle, turtleMap.shacl[report.rpUri])
- let msg = ""
- let triples = []
- for (let triple of inferenceReport.triples) {
- if (!triple.deferredBy) {
- msg += shortenLongUri(triple.s) + " " + shortenLongUri(triple.p) + " " + shortenLongUri(triple.o) + "\n"
- triples.push(triple)
- }
- }
- if (triples.length > 0 && confirm("These entries were inferred based on your already complied requirement profile '" + metadata.rp[report.rpUri].title
- + "':\n\n" + msg + "\nWould you like to add them to your profile?")) {
- for (let triple of triples) {
- addEntryToSubject(triple.s, triple.p, triple.o)
- }
- } // else {}: should we offer to add deferments if they choose not to? But it won't pop up again anyway since inferNewUserDataFromCompliedRPs() is not called regularly
- await finalizeProfileChanges()
- })
- tr.appendChild(td)
- tableEl.appendChild(tr)
- }
-
- await buildPrioritizedMissingDataList()
- }
-
- async function buildPrioritizedMissingDataList() {
- let div = document.getElementById("missingDataPointsDiv")
- div.textContent = ""
- let prioritizedList = []
-
- let missingData = validateAllReport.missingUserInputsAggregated
-
- let focusRPs = []
- for (let focusItem of focusInputSelect.getValue(true)) {
- if (metadata.categories[focusItem]) {
- focusRPs = focusRPs.concat(metadata.categories[focusItem])
- } else {
- focusRPs.push(focusItem)
- }
- }
-
- for (let datafield of Object.values(missingData)) {
- let usedInRpUris = datafield.usedIn.map(usedInRP => usedInRP.rpUri)
- if (focusRPs.length > 0 && !usedInRpUris.some(rpUri => focusRPs.includes(rpUri))) {
- continue
- }
-
- let usedInTitles = []
- let lastMissingCounter = 0
- for (let usedInRP of datafield.usedIn) {
- usedInTitles.push(metadata.rp[usedInRP.rpUri].title + (usedInRP.isLastMissingUserInput ? " (!)" : ""))
- if (usedInRP.isLastMissingUserInput) lastMissingCounter += 1
- }
- prioritizedList.push({
- subject: datafield.subject,
- dfUri: datafield.dfUri,
- objectHasClass: metadata.df[datafield.dfUri]?.objectHasClass,
- label: metadata.df[datafield.dfUri]?.label ?? datafield.dfUri.split("#")[1],
- score: datafield.usedIn.length + lastMissingCounter,
- usedInTitles: usedInTitles
- })
- }
- prioritizedList.sort((a, b) => b.score - a.score)
- prioritizedList.forEach((entry) => { // entry = wrapped datafield
- let spanEl = document.createElement("span")
- spanEl.title = entry.usedInTitles.join("\n")
- spanEl.style.color = "gray"
- let textNode = document.createTextNode(entry.score + ": ")
- spanEl.appendChild(textNode)
- div.appendChild(spanEl)
- spanEl = document.createElement("a")
- spanEl.textContent = collectSubjectClassLabelsAlongPath(entry)
- searchNodeByEntryPredicateRecursively(userProfile, "ff:hasDeferred", (node) => {
- node["ff:hasDeferred"].forEach(deferment => {
- if (deferment["rdf:subject"] === entry.subject && deferment["rdf:predicate"] === entry.dfUri) {
- spanEl.style.textDecoration = "line-through"
- }
- })
- })
- spanEl.addEventListener("click", async function(event) {
- event.preventDefault()
- if (entry.objectHasClass) {
- if (confirm("Do you want to add a " + metadata.df[entry.objectHasClass].label + "?")) {
- instantiateNewObjectClassUnderSubject(entry.subject, entry.dfUri, entry.objectHasClass)
- await finalizeProfileChanges()
- }
- return
- }
- let input = prompt("What is your value for: " + entry.label)
- if (input !== null) {
- addEntryToSubject(entry.subject, entry.dfUri, input)
- await finalizeProfileChanges()
- }
- })
- div.appendChild(spanEl)
- spanEl = document.createElement("span")
- spanEl.style.fontSize = "x-small"
- spanEl.style.color = "silver"
- spanEl.innerHTML = " defer this"
- spanEl.addEventListener("click", async function() {
- addDeferment(entry.subject, entry.dfUri)
- await finalizeProfileChanges()
- })
- div.appendChild(spanEl)
- div.appendChild(document.createElement("br"))
- })
- for (const elem of Array.from(document.getElementsByClassName("loadingDiv"))) {
- elem.style.display = "none"
- }
- }
-
- function collectSubjectClassLabelsAlongPath(entry) { // prioritizedListEntry
- if (shortenLongUri(entry.subject) === "ff:mainPerson") {
- return entry.label
- }
- let sUri = shortenLongUri(entry.subject)
- let path = ""
- let finalPath
- function recurse(node, arrIndex, sUri, path) {
- if (node["@type"] !== "ff:Citizen") path += ", " + metadata.df[expandShortUri(node["@type"])].label + " " + arrIndex
- for (let objectOrArray of Object.values(node)) {
- if (objectOrArray === sUri) {
- finalPath = path.substring(1)
- }
- if (Array.isArray(objectOrArray)) {
- for (let i = 0; i < objectOrArray.length; i++) {
- let arrayEl = objectOrArray[i]
- recurse(arrayEl, i, sUri, path)
- }
- }
- }
-
- }
- recurse(userProfile, 0, sUri, path)
- return finalPath + ": " + entry.label
- }
-
- function searchNodeByEntryPredicateRecursively(node, predicateValue, action) {
- for (let [predicate, objectOrArray] of Object.entries(node)) {
- if (predicate === predicateValue) { // e.g. ff:hasChild as the key
- action(node)
- return
- }
- if (Array.isArray(objectOrArray)) {
- for (let arrayEl of objectOrArray) {
- searchNodeByEntryPredicateRecursively(arrayEl, predicateValue, action)
- }
- }
- }
- }
-
- function addEntryToSubject(subject, predicate, objectValue) {
- subject = shortenLongUri(subject)
- predicate = shortenLongUri(predicate)
- console.log("Adding entry:", subject, predicate, "-->", objectValue)
- searchSubjectNodeRecursively(userProfile, subject, (node) => node[predicate] = objectValue)
- }
-
- function instantiateNewObjectClassUnderSubject(subject, predicate, objectClass) {
- console.log("Adding object class instantiation:", subject, predicate, "-->", objectClass)
- let shortObjectClassUri = shortenLongUri(objectClass)
- let newInstanceUri = shortObjectClassUri.toLowerCase()
- let nodeFound = false
- searchNodeByEntryPredicateRecursively(userProfile, predicate, (node) => {
- nodeFound = true
- newInstanceUri = newInstanceUri + node[predicate].length
- node[predicate].push({ "@id": newInstanceUri, "@type": shortObjectClassUri }) // verify that this works TODO
- })
- if (!nodeFound) { // e.g. no ff:hasChild array yet
- newInstanceUri = newInstanceUri + "0"
- addEntryToSubject(subject, predicate, [{ "@id": newInstanceUri, "@type": shortObjectClassUri }])
- }
- return newInstanceUri
- }
-
- async function finalizeProfileChanges() {
- if (!await validateUserProfile()) return
-
- let userProfileTurtle = await MatchingEngine.convertUserProfileToTurtle(userProfile)
- let materializationReport = await MatchingEngine.checkUserProfileForMaterializations(userProfileTurtle, turtleMap.materialization)
- // run inferNewUserDataFromCompliedRPs() here too?
- console.log("materializationReport", materializationReport)
-
- let msg = ""
- let triples = []
- for (let round of materializationReport.rounds) {
- for (let [ruleUri, entry] of Object.entries(round)) {
- let ruleLocalName = ruleUri.split("#")[1]
- msg += "Via materialization rule '" + ruleLocalName + "' (input: "
- msg += entry.input ? metadata.df[entry.input].label : "?"
- msg += " output: " + (entry.output ? metadata.df[entry.output].label : "?") + "):\n"
- for (let triple of entry.triples) {
- if (!triple.deferredBy) {
- msg += shortenLongUri(triple.s) + " " + shortenLongUri(triple.p) + " " + shortenLongUri(triple.o) + "\n"
- triples.push(triple)
- }
- }
- }
- }
- if (triples.length > 0) {
- if (confirm("The following entries where inferred from your existing entries:\n\n" + msg + "\nWould you like to add them to your profile?")) {
- for (let triple of triples) {
- addEntryToSubject(triple.s, triple.p, triple.o)
- }
- if (!await validateUserProfile()) return
- } else {
- if (!confirm("May I ask you later again to add these? ")) {
- for (let triple of triples) {
- addDeferment(triple.s, triple.p)
- }
- }
- }
- }
- console.log("userProfile", userProfile)
- localStorage.setItem("userProfile", JSON.stringify(userProfile))
- await update()
- }
-
- async function validateUserProfile() {
- let userProfileTurtle = await MatchingEngine.convertUserProfileToTurtle(userProfile)
- let report = await MatchingEngine.validateUserProfile(userProfileTurtle, turtleMap.datafields)
- if (!report.conforms) {
- console.log("User profile validation violations:", report.violations)
- // pretty print violations TODO
- alert("Your profile is not valid: " + JSON.stringify(report.violations))
- return false
- }
- return true
- }
-
- function searchSubjectNodeRecursively(node, sKey, action) {
- if (node["@id"] === sKey) {
- action(node)
- return
- }
- for (let objectOrArray of Object.values(node)) {
- if (Array.isArray(objectOrArray)) {
- for (let arrayEl of objectOrArray) {
- searchSubjectNodeRecursively(arrayEl, sKey, action)
- }
- }
- }
- }
-
- async function buildRemovalCell(td, sKey, pKey) {
- td.textContent = "x"
- td.addEventListener("click", async function() {
- if (confirm("Do you want to remove this entry?")) {
- console.log("Removing entry:", sKey, pKey)
- searchSubjectNodeRecursively(userProfile, sKey, (node) => delete node[pKey])
- await finalizeProfileChanges()
- }
- })
- }
-
- function buildProfileTableRecursively(node, depth, table) {
- let subject = node["@id"]
- for (let [predicate, objectOrArray] of Object.entries(node)) {
- if (predicate.startsWith("@")) continue
- if (!Array.isArray(objectOrArray)) {
- let tds = buildRowAndColumns(table)
- let label = determineLabelForTableEntries(predicate)
- tds[depth].textContent = label
- tds[depth].title = predicate
- tds[depth + 1].textContent = determineLabelForTableEntries(objectOrArray)
- tds[depth + 1].title = objectOrArray
- tds[depth + 1].addEventListener("click", async function() {
- let input = prompt(`Enter new value for ${label}`)
- if (input !== null) {
- console.log("Changing entry value:", subject, predicate, "-->", input)
- searchSubjectNodeRecursively(userProfile, subject, (node) => node[predicate] = input)
- await finalizeProfileChanges()
- }
- })
- buildRemovalCell(tds[maxDepth + 2], subject, predicate)
- continue
- }
- for (let arrayElement of objectOrArray) {
- let tds = buildRowAndColumns(table)
- tds[depth].textContent = dfShortUriToLabel(predicate)
- tds[depth].title = predicate
- // this will delete all array elements of the same type at the moment TODO fix
- buildRemovalCell(tds[maxDepth + 2], subject, predicate)
- buildProfileTableRecursively(arrayElement, depth + 1, table)
- }
- }
- }
-
- // this is needed to know the correct number of columns in the profile table in advance
- function determineDepthOfProfileTreeRecursively(jsonNode, depth) {
- if (depth > maxDepth) maxDepth = depth
- for (let objectOrArray of Object.values(jsonNode)) {
- if (!Array.isArray(objectOrArray)) continue
- for (let arrayElement of objectOrArray) determineDepthOfProfileTreeRecursively(arrayElement, depth + 1)
- }
- }
-
- function buildProfileTable() {
- let div = document.getElementById("userProfileDiv")
- div.innerHTML = ""
- let table = document.createElement("table")
- table.className = "framed-table"
- div.appendChild(table)
- maxDepth = 0
- determineDepthOfProfileTreeRecursively(userProfile, 0)
- buildProfileTableRecursively(userProfile, 0, table)
- }
-
- async function run() {
- latestRPsRepoCommit = await fetchAsset("latest-rps-repo-commit.txt")
- setInterval(checkForNewRepoCommits, 60 * 1000)
- await parseTurtleFiles()
-
- if (localStorage.getItem("userProfile") === null) {
- localStorage.setItem("userProfile", JSON.stringify(EMPTY_PROFILE))
- }
- userProfile = JSON.parse(localStorage.getItem("userProfile"))
- if (!await validateUserProfile()) return
- await update()
- }
-
run()