1153 lines
36 KiB
JavaScript
1153 lines
36 KiB
JavaScript
"use strict";
|
||
|
||
if ('serviceWorker' in navigator) {
|
||
console.log("Registering service worker");
|
||
navigator.serviceWorker.register('/sw.js');
|
||
}
|
||
|
||
class CsvxmlValidator {
|
||
|
||
fieldList; // {}{}
|
||
toValidate; // []{}
|
||
errors; // {}array
|
||
|
||
constructor(fieldList, csvRaw) {
|
||
|
||
this.errors = {
|
||
parsing: [],
|
||
mandatoryTags: [],
|
||
duplicateInvNos: [],
|
||
dependentColumns: [],
|
||
controlledLists: [],
|
||
mainImageResource: [],
|
||
};
|
||
|
||
this.fieldList = Object.freeze(fieldList);
|
||
|
||
const data = Papa.parse(csvRaw.trim(), {
|
||
delimiter: ";", // auto-detect
|
||
escapeChar: '"',
|
||
skipEmptyLines: true,
|
||
header: true,
|
||
});
|
||
|
||
if (data.errors.length !== 0) {
|
||
console.log("Errors encountered: ");
|
||
console.error(data.errors);
|
||
|
||
let msg = '';
|
||
for (let err of data.errors) {
|
||
msg += err.type + ': ' + err.message + "\n";
|
||
}
|
||
window.alert(msg);
|
||
}
|
||
|
||
let toValidate = data.data;
|
||
|
||
this.toValidate = toValidate;
|
||
|
||
if (toValidate.length === 0) {
|
||
alert("Error: No lines of content identified");
|
||
}
|
||
|
||
this.validate();
|
||
}
|
||
|
||
validate() {
|
||
|
||
this.validateMandatoryTagsPresent();
|
||
this.validateInvalidTagsPresent();
|
||
this.checkDuplicateInvNos();
|
||
this.checkDependentColumns();
|
||
this.checkControlledLists();
|
||
// this.checkMainImageResource();
|
||
|
||
}
|
||
|
||
validateMandatoryTagsPresent() {
|
||
|
||
let mandatoryFields = [];
|
||
for (let fieldName in this.fieldList) {
|
||
if (this.fieldList[fieldName].required === true) {
|
||
mandatoryFields.push(fieldName);
|
||
}
|
||
}
|
||
|
||
console.log(this.toValidate);
|
||
let lineCounter = 1;
|
||
for (let line of this.toValidate) {
|
||
for (let mandatoryField of mandatoryFields) {
|
||
if (line[mandatoryField] === undefined || line[mandatoryField] === null || line[mandatoryField] === '') {
|
||
this.errors.mandatoryTags.push("Missing or empty mandatory tag " + mandatoryField + " on line " + lineCounter);
|
||
}
|
||
}
|
||
lineCounter++;
|
||
}
|
||
|
||
}
|
||
|
||
validateInvalidTagsPresent() {
|
||
|
||
const headers = Object.keys(this.toValidate[0]);
|
||
|
||
for (let header of headers) {
|
||
if (this.fieldList[header] === undefined) {
|
||
this.errors.parsing.push("Invalid column " + header + " detected! Please remove this column or use the appropriate name!");
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
checkDuplicateInvNos() {
|
||
|
||
let invNoEncountered = [];
|
||
let lineCounter = 1;
|
||
for (let line of this.toValidate) {
|
||
if (invNoEncountered.includes(line.inventory_number)) {
|
||
this.errors.duplicateInvNos.push("Duplicate inventory number " + line.inventory_number + " on line " + lineCounter);
|
||
}
|
||
invNoEncountered.push(line.inventory_number);
|
||
lineCounter++;
|
||
}
|
||
|
||
}
|
||
|
||
checkDependentColumns() {
|
||
|
||
const headers = Object.keys(this.toValidate[0]);
|
||
|
||
for (let header of headers) {
|
||
if (this.fieldList[header] === undefined || this.fieldList[header].dependsOn === undefined || this.fieldList[header].dependsOn === null) continue;
|
||
|
||
let dependencies = this.fieldList[header].dependsOn;
|
||
for (let dep of dependencies) {
|
||
if (headers.includes(dep) === false) {
|
||
console.error("Dependency issue at column " + header + ": Corresponding column " + dep + " is missing");
|
||
console.log(headers);
|
||
this.errors.dependentColumns.push("Dependency issue at column " + header + ": Corresponding column " + dep + " is missing");
|
||
}
|
||
}
|
||
}
|
||
|
||
let lineCounter = 1;
|
||
for (let line of this.toValidate) {
|
||
for (let fieldName in line) {
|
||
|
||
if (line[fieldName] === '') continue;
|
||
if (this.fieldList[fieldName] === undefined) continue; // This may be the case if invalid fields are present
|
||
|
||
const dependencies = this.fieldList[fieldName].dependsOn;
|
||
if (dependencies === undefined) continue;
|
||
|
||
for (let dependency of dependencies) {
|
||
if (line[dependency] === '') {
|
||
console.error("Dependency issue at column " + fieldName + ": Corresponding column " + dependency + " is missing");
|
||
console.log(line);
|
||
this.errors.dependentColumns.push("Dependency issue at column " + fieldName + " (current value: " + line[fieldName] + "): Corresponding column " + dependency + " is empty");
|
||
}
|
||
}
|
||
}
|
||
lineCounter++;
|
||
}
|
||
|
||
}
|
||
|
||
checkControlledLists() {
|
||
|
||
let lineCounter = 1;
|
||
for (let line of this.toValidate) {
|
||
for (let fieldName in line) {
|
||
|
||
if (this.fieldList[fieldName] === undefined) {
|
||
console.log("Undefined but requested field " + fieldName);
|
||
continue;
|
||
}
|
||
|
||
const allowedValues = this.fieldList[fieldName].allowedValues;
|
||
|
||
// No error if the field doesn't have a controlled list
|
||
if (allowedValues === undefined || allowedValues === null) continue;
|
||
// No error if the line's content is in the list
|
||
if (Object.values(allowedValues).length === 0 || Object.values(allowedValues).includes(line[fieldName])) {
|
||
continue;
|
||
}
|
||
|
||
if (line[fieldName] === '') continue;
|
||
this.errors.controlledLists.push("Disallowed value used for column " + fieldName + " at line " + lineCounter + " (Allowed values are: " + Object.values(allowedValues).join(", ") + "; current value is " + line[fieldName] + ")");
|
||
}
|
||
lineCounter++;
|
||
}
|
||
|
||
}
|
||
|
||
checkMainImageResource() {
|
||
|
||
}
|
||
|
||
isValid() {
|
||
|
||
for (let errorClass in this.errors) {
|
||
if (this.errors[errorClass].length !== 0) return false;
|
||
}
|
||
|
||
return true;
|
||
|
||
}
|
||
|
||
/**
|
||
* Generates XML for the parsed lines
|
||
*/
|
||
generateXml() {
|
||
|
||
let output = [];
|
||
|
||
let xmlDoc = document.implementation.createDocument(null, "record");
|
||
for (let line of this.toValidate) {
|
||
let root = xmlDoc.createElement("record");
|
||
for (let fieldName in line) {
|
||
const elem = xmlDoc.createElement(fieldName);
|
||
elem.textContent = line[fieldName];
|
||
root.appendChild(elem);
|
||
}
|
||
output.push(root)
|
||
}
|
||
|
||
return output;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
class CsvxmlTooltip {
|
||
|
||
/**
|
||
* Function for setting the alignment of an element.
|
||
*
|
||
* @param {Event} e Event triggering the execution of this function.
|
||
* @param {DOMElement} elem Dom element to position.
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static getDirection(e, elem) {
|
||
|
||
if (window.innerHeight < e.clientY + elem.clientHeight) {
|
||
elem.style.top = "";
|
||
elem.style.bottom = (window.innerHeight - e.clientY) + "px";
|
||
}
|
||
else {
|
||
elem.style.bottom = "";
|
||
elem.style.top = (e.clientY + 4) + "px";
|
||
}
|
||
if (window.innerWidth < e.clientX + elem.clientWidth) {
|
||
elem.style.left = "";
|
||
elem.style.right = (window.innerWidth - e.clientX) + "px";
|
||
} else {
|
||
elem.style.right = "";
|
||
elem.style.left = (e.clientX + 3) + "px";
|
||
}
|
||
|
||
}
|
||
|
||
static positionMobile(newMain) {
|
||
|
||
if (window.matchMedia && window.matchMedia('(max-width:75em)').matches) {
|
||
|
||
newMain.style.left = "";
|
||
newMain.style.right = "";
|
||
newMain.style.top = "";
|
||
newMain.style.bottom = "";
|
||
|
||
if (newMain.classList.contains("atBottom") === false) {
|
||
newMain.classList.add("atBottom");
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
static triggerMouseMove(e) {
|
||
const newMain = document.getElementById("newToolTipMain");
|
||
if (newMain === undefined || newMain === null) return;
|
||
CsvxmlTooltip.getDirection(e, newMain);
|
||
CsvxmlTooltip.positionMobile(newMain);
|
||
}
|
||
|
||
static triggerMouseOut(e) {
|
||
|
||
const newMain = document.getElementById("newToolTipMain");
|
||
if (newMain !== undefined && newMain !== null) {
|
||
newMain.classList.remove("visible");
|
||
document.body.removeChild(newMain);
|
||
}
|
||
e.target.removeEventListener('mouseout', CsvxmlTooltip.triggerMouseOut);
|
||
|
||
}
|
||
|
||
static bindTooltipToElement(elem, tooltipTitle, tooltipContent) {
|
||
|
||
elem.addEventListener('mouseover', function(e) {
|
||
|
||
let newMain = document.getElementById("newToolTipMain");
|
||
if (newMain !== null) return;
|
||
newMain = document.createElement("div");
|
||
newMain.classList.add("newToolTip");
|
||
newMain.id = "newToolTipMain";
|
||
|
||
// Insert contents loaded.
|
||
|
||
newMain.setAttribute("data-title", tooltipTitle);
|
||
|
||
newMain.appendChild(tooltipContent);
|
||
|
||
document.body.appendChild(newMain);
|
||
newMain.classList.add("visible");
|
||
CsvxmlTooltip.getDirection(e, newMain);
|
||
CsvxmlTooltip.positionMobile(newMain);
|
||
|
||
newMain.addEventListener("mouseout", function(e) {
|
||
const newMain = document.getElementById("newToolTipMain");
|
||
if (newMain !== undefined && newMain !== null) {
|
||
newMain.classList.remove("visible");
|
||
document.body.removeChild(newMain);
|
||
}
|
||
});
|
||
|
||
elem.addEventListener("mouseout", CsvxmlTooltip.triggerMouseOut);
|
||
|
||
}, {passive: true});
|
||
|
||
elem.addEventListener("mousemove", CsvxmlTooltip.triggerMouseMove, {passive: true});
|
||
|
||
}
|
||
|
||
}
|
||
|
||
class CsvxmlDialogue {
|
||
|
||
static closeDialogue(e) {
|
||
|
||
if (e !== undefined) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
|
||
let dialogueArea = document.getElementById("dialogueArea");
|
||
if (dialogueArea !== null && dialogueArea !== false) {
|
||
while (dialogueArea.firstChild) {
|
||
dialogueArea.removeChild(dialogueArea.firstChild);
|
||
}
|
||
dialogueArea.parentElement.removeChild(dialogueArea);
|
||
|
||
document.removeEventListener('keydown', CsvxmlDialogue.closeDialogueByEscape, false);
|
||
}
|
||
|
||
}
|
||
|
||
static closeDialogueByEscape(e) {
|
||
|
||
if (e.keyCode === 27) { // 27 = Esc
|
||
|
||
CsvxmlDialogue.closeDialogue(e);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* Function for drawing a dialogue and attaching it to the body elem.
|
||
*
|
||
* @param {DOMElement} contents Contents.
|
||
*/
|
||
static drawDialogue(contents) {
|
||
let dialogueArea = document.createElement("div");
|
||
dialogueArea.id = "dialogueArea";
|
||
|
||
let dialogue = document.createElement("div");
|
||
dialogue.id = "dialogue";
|
||
|
||
dialogue.appendChild(contents);
|
||
dialogueArea.appendChild(dialogue);
|
||
|
||
document.body.appendChild(dialogueArea);
|
||
|
||
document.addEventListener('keydown', CsvxmlDialogue.closeDialogueByEscape);
|
||
|
||
return dialogue;
|
||
}
|
||
|
||
}
|
||
|
||
class CsvxmlPage {
|
||
|
||
fieldList;
|
||
fieldListFlat;
|
||
tls;
|
||
domHelpWrapper;
|
||
domUploaderWrapper;
|
||
domMainWrapper;
|
||
selectedFields;
|
||
|
||
csvBySelectionButton;
|
||
unsetSelectionButton;
|
||
|
||
constructor(fieldList, tls) {
|
||
this.fieldList = Object.freeze(fieldList);
|
||
|
||
let list = {};
|
||
for (let sectionName in fieldList) {
|
||
list = Object.assign(list, fieldList[sectionName]);
|
||
}
|
||
this.fieldListFlat = Object.freeze(list);
|
||
|
||
this.tls = Object.freeze(tls);
|
||
|
||
let domHelpWrapper = document.createElement("div");
|
||
domHelpWrapper.id = "helpSection";
|
||
this.domHelpWrapper = domHelpWrapper;
|
||
|
||
let domUploaderWrapper = document.createElement("div");
|
||
domUploaderWrapper.id = "uploader";
|
||
domUploaderWrapper.classList.add("uploader");
|
||
this.domUploaderWrapper = domUploaderWrapper;
|
||
|
||
let domMainWrapper = document.createElement("main");
|
||
this.domMainWrapper = domMainWrapper;
|
||
|
||
this.selectedFields = [];
|
||
}
|
||
|
||
generateCsv(selectedFields = []) {
|
||
|
||
let line1 = [];
|
||
let line2 = [];
|
||
let line3 = [];
|
||
|
||
for (let fieldName in this.fieldListFlat) {
|
||
console.log(fieldName);
|
||
console.log(selectedFields);
|
||
|
||
if (selectedFields.length !== 0 && selectedFields.includes(fieldName) === false) continue;
|
||
const field = this.fieldListFlat[fieldName];
|
||
|
||
line1.push(fieldName);
|
||
line2.push(field.name_human_readable);
|
||
|
||
if (field.allowedValues !== undefined) {
|
||
// Join for object
|
||
let values = [];
|
||
for (let key in field.allowedValues) {
|
||
values.push(field.allowedValues[key]);
|
||
}
|
||
line3.push(values.join(","));
|
||
}
|
||
else line3.push("");
|
||
|
||
}
|
||
|
||
const csvLine1 = '"' + line1.join('";"') + '"';
|
||
const csvLine2 = '"' + line2.join('";"') + '"';
|
||
const csvLine3 = '"' + line3.join('";"') + '"';
|
||
|
||
const toStore = csvLine1 + "\n" + csvLine2 + "\n" + csvLine3;
|
||
|
||
// Download
|
||
const triggerLink = document.createElement('a');
|
||
triggerLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(toStore));
|
||
triggerLink.setAttribute('download', "csvxml_museum-digital_template.csv");
|
||
|
||
triggerLink.style.display = 'none';
|
||
|
||
document.body.appendChild(triggerLink);
|
||
triggerLink.click();
|
||
document.body.removeChild(triggerLink);
|
||
|
||
|
||
}
|
||
|
||
zipUploadToXml(validator) {
|
||
|
||
// Wrap zipping in function to postload it
|
||
function runZipping() {
|
||
|
||
let zip = new JSZip();
|
||
|
||
let xmlFiles = validator.generateXml();
|
||
const serializer = new XMLSerializer();
|
||
let lineCounter = 0;
|
||
for (let xml of xmlFiles) {
|
||
zip.file(lineCounter + ".xml", serializer.serializeToString(xml));
|
||
lineCounter++;
|
||
}
|
||
|
||
zip.generateAsync({type:"blob"})
|
||
.then(function(content) {
|
||
const triggerLink = document.createElement('a');
|
||
triggerLink.href = window.URL.createObjectURL(content);
|
||
triggerLink.setAttribute('download', "csvxml.zip");
|
||
|
||
triggerLink.style.display = 'none';
|
||
|
||
document.body.appendChild(triggerLink);
|
||
triggerLink.click();
|
||
document.body.removeChild(triggerLink);
|
||
});
|
||
|
||
}
|
||
|
||
if (typeof JSZip === "undefined") {
|
||
|
||
const loadScript = document.createElement("script");
|
||
loadScript.setAttribute("src", "assets/js/jszip/dist/jszip.min.js");
|
||
loadScript.addEventListener('load', function() {
|
||
// console.log("Post-loaded OpenLayers");
|
||
runZipping();
|
||
}, {passive: true, once: true});
|
||
|
||
document.body.appendChild(loadScript);
|
||
}
|
||
else {
|
||
runZipping();
|
||
}
|
||
|
||
}
|
||
|
||
generateDialogueCloseButton() {
|
||
|
||
const cancelB = document.createElement("a");
|
||
cancelB.classList.add("icons");
|
||
cancelB.classList.add("iconsClose");
|
||
cancelB.classList.add("dialogueCloseX");
|
||
cancelB.id = "dialogueClose";
|
||
cancelB.textContent = "X";
|
||
cancelB.title = "Close";
|
||
cancelB.href = "#" + location.href;
|
||
cancelB.addEventListener('click', CsvxmlDialogue.closeDialogue);
|
||
return cancelB;
|
||
|
||
}
|
||
|
||
listValidationErrors(validator) {
|
||
|
||
console.log("Listing validation errors");
|
||
|
||
const dialogueContent = document.createElement("div");
|
||
|
||
const headline = document.createElement("h3");
|
||
headline.textContent = this.tls.validation_errors;
|
||
headline.appendChild(this.generateDialogueCloseButton());
|
||
dialogueContent.appendChild(headline);
|
||
|
||
const domErrorsSection = document.createElement("div");
|
||
|
||
for (let errorType in validator.errors) {
|
||
|
||
if (validator.errors[errorType].length === 0) continue;
|
||
|
||
const ulHl = document.createElement("h4");
|
||
ulHl.textContent = this.tls['errors_' + errorType] + " (" + validator.errors[errorType].length + ")";
|
||
ulHl.style.cursor = "pointer";
|
||
domErrorsSection.appendChild(ulHl);
|
||
|
||
const ul = document.createElement("ul");
|
||
|
||
for (let error of validator.errors[errorType]) {
|
||
const li = document.createElement("li");
|
||
li.textContent = error;
|
||
ul.appendChild(li);
|
||
}
|
||
|
||
ulHl.addEventListener('click', function() {
|
||
ul.classList.toggle("minimized");
|
||
});
|
||
|
||
domErrorsSection.appendChild(ul);
|
||
|
||
}
|
||
|
||
dialogueContent.appendChild(domErrorsSection);
|
||
|
||
const domDlSection = document.createElement("div");
|
||
const domDlA = document.createElement("span");
|
||
domDlA.textContent = this.tls.download;
|
||
domDlA.classList.add("buttonLike");
|
||
let app = this;
|
||
domDlA.addEventListener('click', function() {
|
||
app.zipUploadToXml(validator);
|
||
});
|
||
domDlSection.appendChild(domDlA);
|
||
dialogueContent.appendChild(domDlSection);
|
||
|
||
dialogue = CsvxmlDialogue.drawDialogue(dialogueContent);
|
||
|
||
}
|
||
|
||
uploadFileForValidation(file) {
|
||
|
||
const reader = new FileReader();
|
||
let utf8 = true;
|
||
reader.readAsText(file, utf8 ? 'UTF-8' : 'CP1251');
|
||
|
||
let app = this;
|
||
|
||
document.body.classList.add("loading");
|
||
|
||
reader.onload = function() {
|
||
|
||
function handleValidation() {
|
||
|
||
(async function() {
|
||
const result = reader.result;
|
||
if (utf8 && result.includes('<27>')) {
|
||
window.alert('The file encoding appears to not be UTF-8!\n\nTry exporting the file using the format "CSV (UTF-8)" if you use MS Excel or set the encoding to UTF-8 when exporting through LibreOffice!');
|
||
}
|
||
})();
|
||
|
||
// On loading success, check if the upload is valid JSON
|
||
console.log("Read file");
|
||
// Validate the file
|
||
let validator = new CsvxmlValidator(app.fieldListFlat, reader.result);
|
||
document.body.classList.remove("loading");
|
||
if (validator.isValid() === true) {
|
||
alert("Document is valid. Press ok to download.");
|
||
app.zipUploadToXml(validator);
|
||
}
|
||
else {
|
||
console.log("Identified invalid upload document");
|
||
app.listValidationErrors(validator);
|
||
}
|
||
|
||
}
|
||
|
||
console.log("Postload papaparse");
|
||
if (typeof Papa === "undefined") {
|
||
|
||
const loadScript = document.createElement("script");
|
||
loadScript.setAttribute("src", "assets/js/papaparse/papaparse.min.js");
|
||
loadScript.addEventListener('load', function() {
|
||
// console.log("Post-loaded OpenLayers");
|
||
handleValidation();
|
||
}, {passive: true, once: true});
|
||
document.body.appendChild(loadScript);
|
||
|
||
}
|
||
else {
|
||
handleValidation();
|
||
}
|
||
|
||
};
|
||
reader.onerror = function() {
|
||
alert(reader.error);
|
||
};
|
||
|
||
}
|
||
|
||
renderGenHeader() {
|
||
|
||
const header = document.createElement("header");
|
||
header.id = "mainHeader";
|
||
|
||
const logoArea = document.createElement("a");
|
||
logoArea.id = "logoArea";
|
||
logoArea.href = "https://www.museum-digital.org/";
|
||
|
||
const logoImg = document.createElement("img");
|
||
logoImg.src = "assets/img/mdlogo-code-128px.png";
|
||
logoImg.alt = "Logo of museum-digital";
|
||
logoArea.appendChild(logoImg);
|
||
|
||
const h2 = document.createElement("h2");
|
||
h2.textContent = "museum-digital";
|
||
logoArea.appendChild(h2);
|
||
|
||
header.appendChild(logoArea);
|
||
|
||
// Right side of the header
|
||
const nav = document.createElement("nav");
|
||
|
||
const lAbout = document.createElement("a");
|
||
lAbout.href = "https://en.about.museum-digital.org/about";
|
||
lAbout.textContent = this.tls.about;
|
||
nav.appendChild(lAbout);
|
||
|
||
const lContactList = document.createElement("div");
|
||
|
||
const lContact = document.createElement("a");
|
||
lContact.textContent = this.tls.contact;
|
||
lContact.href = "https://en.about.museum-digital.org/contact/";
|
||
lContactList.appendChild(lContact);
|
||
|
||
const lContactDiv = document.createElement("div");
|
||
|
||
const lImprint = document.createElement("a");
|
||
lImprint.textContent = this.tls.imprint;
|
||
lImprint.href = "https://en.about.museum-digital.org/impressum";
|
||
lContactDiv.appendChild(lImprint);
|
||
|
||
const lPrivacy = document.createElement("a");
|
||
lPrivacy.textContent = this.tls.privacy_policy;
|
||
lPrivacy.href = "https://en.about.museum-digital.org/privacy/";
|
||
lContactDiv.appendChild(lPrivacy);
|
||
|
||
lContactList.appendChild(lContactDiv);
|
||
nav.appendChild(lContactList);
|
||
|
||
const lNews = document.createElement("a")
|
||
lNews.textContent = this.tls.news;
|
||
lNews.href = "https://blog.museum-digital.org/";
|
||
nav.appendChild(lNews);
|
||
|
||
header.appendChild(nav);
|
||
document.body.appendChild(header);
|
||
|
||
}
|
||
|
||
renderHeader() {
|
||
|
||
const appHeader = document.createElement("header");
|
||
|
||
const h1 = document.createElement("h1");
|
||
|
||
const img = document.createElement("img");
|
||
img.width = "70";
|
||
img.height = "70";
|
||
img.src = "assets/img/mdlogo-csvxml.svg";
|
||
img.alt = "";
|
||
h1.appendChild(img);
|
||
|
||
const h1Span = document.createElement("span");
|
||
h1Span.textContent = "museum-digital:csvxml";
|
||
h1.appendChild(h1Span);
|
||
|
||
appHeader.appendChild(h1);
|
||
|
||
document.body.appendChild(appHeader);
|
||
|
||
}
|
||
|
||
renderHelpTexts() {
|
||
|
||
let app = this;
|
||
(async function() {
|
||
|
||
function appendQA(question, answer) {
|
||
|
||
const div = document.createElement("div");
|
||
div.classList.add("qaDiv");
|
||
|
||
const qElem = document.createElement("h3");
|
||
qElem.textContent = question;
|
||
qElem.style.cursor = "pointer";
|
||
div.appendChild(qElem);
|
||
|
||
qElem.addEventListener('click', function() {
|
||
|
||
console.log("Listing validation errors");
|
||
|
||
const dialogueContent = document.createElement("div");
|
||
|
||
const headline = document.createElement("h3");
|
||
headline.textContent = question;
|
||
headline.appendChild(app.generateDialogueCloseButton());
|
||
dialogueContent.appendChild(headline);
|
||
|
||
const answerDiv = document.createElement("div");
|
||
answerDiv.textContent = answer;
|
||
dialogueContent.appendChild(answerDiv);
|
||
CsvxmlDialogue.drawDialogue(dialogueContent);
|
||
});
|
||
|
||
return div;
|
||
|
||
}
|
||
|
||
const div = document.createElement("div");
|
||
|
||
div.appendChild(appendQA(app.tls.help_where_am_i, app.tls.help_where_am_i_content));
|
||
div.appendChild(appendQA(app.tls.help_what_is_csv, app.tls.help_what_is_csv_content));
|
||
div.appendChild(appendQA(app.tls.help_how_to_format_csv, app.tls.help_how_to_format_csv_content));
|
||
|
||
app.domHelpWrapper.appendChild(div);
|
||
|
||
})();
|
||
|
||
document.body.appendChild(this.domHelpWrapper);
|
||
|
||
}
|
||
|
||
renderUploader() {
|
||
|
||
let app = this;
|
||
(async function() {
|
||
|
||
const h2 = document.createElement("h2");
|
||
h2.textContent = app.tls.upload;
|
||
app.domUploaderWrapper.appendChild(h2);
|
||
|
||
const form = document.createElement("form");
|
||
|
||
const label = document.createElement("label");
|
||
label.textContent = app.tls.select_csv_file_for_upload;
|
||
label.setAttribute("for", "fileToUpload");
|
||
form.appendChild(label);
|
||
|
||
const input = document.createElement("input");
|
||
input.type = "file";
|
||
input.id = "fileToUpload";
|
||
input.setAttribute("tabindex", "1");
|
||
input.accept = ".csv";
|
||
input.required = "required";
|
||
input.addEventListener('change', async function() {
|
||
app.uploadFileForValidation(input.files[0]);
|
||
});
|
||
form.appendChild(input);
|
||
|
||
/*
|
||
const button = document.createElement("button");
|
||
button.textContent = "Upload"; // TODO
|
||
button.type = "submit";
|
||
form.appendChild(button);
|
||
*/
|
||
|
||
app.domUploaderWrapper.appendChild(form);
|
||
|
||
})();
|
||
|
||
document.body.appendChild(this.domUploaderWrapper);
|
||
|
||
}
|
||
|
||
// Takes a callback function
|
||
doForFieldList(callback) {
|
||
let fieldLists = document.getElementsByClassName("fieldList");
|
||
for (let i = 0, max = fieldLists.length; i < max; i++) {
|
||
|
||
let fields = fieldLists[i].getElementsByTagName("li");
|
||
for (let j = 0, maxj = fields.length; j < maxj; j++) {
|
||
|
||
callback(fields[j]);
|
||
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
toggleListFieldSelectionState(field) {
|
||
|
||
let app = this;
|
||
|
||
let newValue = field.getAttribute("data-alt");
|
||
field.setAttribute("data-alt", field.textContent);
|
||
field.textContent = newValue;
|
||
field.classList.toggle("humanTLToggled");
|
||
|
||
if (field.classList.contains("humanTLToggled") === false) return;
|
||
|
||
let dependencies = this.fieldListFlat[field.id].dependsOn;
|
||
if (dependencies !== undefined && dependencies !== null) {
|
||
let linkedFields = this.fieldListFlat[field.id].dependsOn;
|
||
for (let i = 0, max = linkedFields.length; i < max; i++) {
|
||
let linkedField = document.getElementById(linkedFields[i]);
|
||
if (linkedField.classList.contains("humanTLToggled") === true) continue;
|
||
this.toggleListFieldSelectionState(linkedField);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
checkCSVBySelectionAccessibility() {
|
||
|
||
let selected = document.getElementsByClassName("humanTLToggled");
|
||
if (selected.length === 0) {
|
||
this.csvBySelectionButton.classList.add("invisible");
|
||
this.unsetSelectionButton.classList.add("invisible");
|
||
}
|
||
else {
|
||
this.csvBySelectionButton.classList.remove("invisible");
|
||
this.unsetSelectionButton.classList.remove("invisible");
|
||
}
|
||
|
||
}
|
||
|
||
getOptionsSection() {
|
||
|
||
function genButton(id, text, link = "") {
|
||
|
||
const output = document.createElement("span");
|
||
output.id = id;
|
||
output.setAttribute("tabindex", "1");
|
||
output.textContent = text;
|
||
output.classList.add("buttonLike");
|
||
if (link !== "") output.href = link;
|
||
return output;
|
||
|
||
}
|
||
|
||
const options = document.createElement("div");
|
||
options.classList.add("options");
|
||
|
||
const app = this;
|
||
|
||
const dlAllButton = genButton("dlAll", this.tls.download_csv_all);
|
||
dlAllButton.cursor = "pointer";
|
||
dlAllButton.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
app.generateCsv();
|
||
});
|
||
options.appendChild(dlAllButton);
|
||
|
||
this.csvBySelectionButton = genButton("csvBySelection", this.tls.download_csv_by_selection);
|
||
this.csvBySelectionButton.classList.add("invisible");
|
||
options.appendChild(this.csvBySelectionButton);
|
||
|
||
const optionSelectRequired = genButton("selectRequired", this.tls.select_required_fields);
|
||
options.appendChild(optionSelectRequired);
|
||
|
||
const optionSelectAll = genButton("selectAll", this.tls.select_all_fields);
|
||
options.appendChild(optionSelectAll);
|
||
|
||
this.unsetSelectionButton = genButton("unsetSelection", this.tls.unset_selection);
|
||
this.unsetSelectionButton.classList.add("invisible");
|
||
options.appendChild(this.unsetSelectionButton);
|
||
|
||
this.csvBySelectionButton.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
|
||
let selected = document.getElementsByClassName("humanTLToggled");
|
||
let selectedFields = [];
|
||
for (let i = 0, max = selected.length; i < max; i++) {
|
||
selectedFields += selected[i].getAttribute("data-value");
|
||
}
|
||
app.generateCsv(selectedFields);
|
||
|
||
});
|
||
|
||
optionSelectRequired.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
|
||
app.doForFieldList(function(field) {
|
||
if (field.classList.contains("requiredField") === false) return;
|
||
if (field.classList.contains("humanTLToggled") === true) return;
|
||
|
||
app.toggleListFieldSelectionState(field);
|
||
app.checkCSVBySelectionAccessibility();
|
||
|
||
});
|
||
|
||
});
|
||
|
||
optionSelectAll.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
|
||
app.doForFieldList(function(field) {
|
||
if (field.classList.contains("humanTLToggled") === true) return;
|
||
|
||
app.toggleListFieldSelectionState(field);
|
||
app.checkCSVBySelectionAccessibility();
|
||
|
||
});
|
||
|
||
});
|
||
|
||
this.unsetSelectionButton.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
|
||
app.doForFieldList(function(field) {
|
||
if (field.classList.contains("humanTLToggled") === false) return;
|
||
|
||
app.toggleListFieldSelectionState(field);
|
||
app.checkCSVBySelectionAccessibility();
|
||
|
||
});
|
||
|
||
});
|
||
|
||
return options;
|
||
|
||
}
|
||
|
||
renderMain() {
|
||
|
||
const domH2 = document.createElement("h2");
|
||
domH2.textContent = this.tls.currently_approved_tags;
|
||
this.domMainWrapper.appendChild(domH2);
|
||
|
||
this.domMainWrapper.appendChild(this.getOptionsSection());
|
||
|
||
for (let sectionName in this.fieldList) {
|
||
|
||
const domDiv = document.createElement("div");
|
||
|
||
const domH3 = document.createElement("h3");
|
||
domH3.textContent = sectionName;
|
||
domDiv.appendChild(domH3);
|
||
|
||
const domUl = document.createElement("ul");
|
||
domUl.classList.add("fieldList");
|
||
|
||
const sectionFields = this.fieldList[sectionName];
|
||
for (let fieldName in sectionFields) {
|
||
const field = sectionFields[fieldName];
|
||
|
||
const domLi = document.createElement("li");
|
||
domLi.textContent = fieldName;
|
||
domLi.id = fieldName;
|
||
domLi.setAttribute("data-alt", field.name_human_readable)
|
||
domLi.setAttribute("data-value", fieldName)
|
||
if (field.required === true) domLi.classList.add("requiredField");
|
||
domUl.appendChild(domLi);
|
||
|
||
const tooltipContent = document.createElement("div");
|
||
const explicaP = document.createElement("p");
|
||
explicaP.textContent = field.explica;
|
||
tooltipContent.appendChild(explicaP);
|
||
|
||
if (field.remarks !== undefined && field.remarks !== '') {
|
||
|
||
const remarkHl = document.createElement("h4");
|
||
remarkHl.textContent = this.tls.remarks;
|
||
tooltipContent.appendChild(remarkHl)
|
||
const remarkCont = document.createElement("p");
|
||
remarkCont = field.remarks;
|
||
tooltipContent.appendChild(remarkCont);
|
||
|
||
}
|
||
|
||
if (field.allowedValues !== undefined && Object.values(field.allowedValues).length !== 0) {
|
||
const allowedHl = document.createElement("h4");
|
||
allowedHl.textContent = this.tls.allowed_values;
|
||
tooltipContent.appendChild(allowedHl);
|
||
const allowedList = document.createElement("p");
|
||
allowedList.textContent = Object.values(field.allowedValues).join(', ');
|
||
tooltipContent.appendChild(allowedList);
|
||
}
|
||
|
||
CsvxmlTooltip.bindTooltipToElement(domLi, field.name_human_readable, tooltipContent);
|
||
|
||
}
|
||
|
||
domDiv.appendChild(domUl);
|
||
|
||
this.domMainWrapper.appendChild(domDiv);
|
||
|
||
}
|
||
|
||
document.body.appendChild(this.domMainWrapper);
|
||
|
||
let app = this;
|
||
this.doForFieldList(function(field) {
|
||
|
||
// Each field should switch its visible content and human-readable
|
||
// translation on a click.
|
||
field.addEventListener('click', function(e) {
|
||
|
||
app.toggleListFieldSelectionState(field);
|
||
app.checkCSVBySelectionAccessibility();
|
||
|
||
});
|
||
|
||
});
|
||
|
||
}
|
||
|
||
renderFooter() {
|
||
|
||
const footer = document.createElement("footer");
|
||
|
||
const licenseStatement = document.createElement("p");
|
||
licenseStatement.textContent = "This work is licensed under the GNU Affero Public License Version 3.";
|
||
footer.appendChild(licenseStatement);
|
||
|
||
const footerOptions = document.createElement("div");
|
||
|
||
const codeLink = document.createElement("a");
|
||
codeLink.textContent = "Source code";
|
||
codeLink.href = "https://gitea.armuli.eu/museum-digital/csvxml";
|
||
footerOptions.appendChild(codeLink);
|
||
|
||
if ('serviceWorker' in navigator) {
|
||
const refreshB = document.createElement("span");
|
||
refreshB.textContent = "Reload application";
|
||
refreshB.setAttribute("tabindex", 1);
|
||
refreshB.addEventListener('click', function(e) {
|
||
|
||
Promise.all(['csvxml-cache-v1'].map(function(cache) {
|
||
caches.has(cache).then(function(hasCache) {
|
||
if (hasCache === true) {
|
||
caches.delete(cache).then(function(deletionStatus) {});
|
||
}
|
||
})
|
||
}))
|
||
location.reload()
|
||
|
||
}, {passive: true, once: true});
|
||
footerOptions.appendChild(refreshB);
|
||
}
|
||
|
||
footer.appendChild(footerOptions);
|
||
document.body.appendChild(footer);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
(async function() {
|
||
|
||
function getLang() {
|
||
|
||
const allowedLangs = document.documentElement.getAttribute("data-allowed-langs").split(',');
|
||
|
||
if (navigator.language === undefined) return 'en';
|
||
|
||
const browserLang = navigator.language.toLowerCase().substr(0, 2);
|
||
console.log(browserLang);
|
||
|
||
if (allowedLangs.includes(browserLang)) return browserLang;
|
||
else return 'en';
|
||
|
||
}
|
||
|
||
const lang = getLang();
|
||
document.documentElement.setAttribute("lang", lang);
|
||
|
||
document.body.classList.add("loading");
|
||
|
||
let loaded = 0;
|
||
|
||
let fieldList;
|
||
let tls;
|
||
|
||
function loadPage() {
|
||
|
||
document.body.classList.remove("loading");
|
||
|
||
const page = new CsvxmlPage(fieldList, tls);
|
||
page.renderGenHeader();
|
||
page.renderHeader();
|
||
page.renderHelpTexts();
|
||
page.renderUploader();
|
||
page.renderMain();
|
||
page.renderFooter();
|
||
|
||
}
|
||
|
||
window.fetch('/json/fields.' + lang + '.json', {
|
||
method: 'GET', cache: 'no-cache', credentials: 'same-origin',
|
||
}).then(function(response) {
|
||
return response.json();
|
||
}).then(function(elements) {
|
||
fieldList = elements;
|
||
loaded++;
|
||
if (loaded === 2) loadPage();
|
||
});
|
||
|
||
window.fetch('/json/tls.' + lang + '.json', {
|
||
method: 'GET', cache: 'no-cache', credentials: 'same-origin',
|
||
}).then(function(response) {
|
||
return response.json();
|
||
}).then(function(elements) {
|
||
tls = elements;
|
||
loaded++;
|
||
if (loaded === 2) loadPage();
|
||
});
|
||
|
||
})();
|