Move on to a fully pre-compiled setup

See #14
This commit is contained in:
Joshua Ramon Enslin 2022-11-07 02:55:54 +01:00
parent 014523c17d
commit 3e4554f759
Signed by: jrenslin
GPG Key ID: 46016F84501B70AE
17 changed files with 324 additions and 182 deletions

View File

@ -1,5 +1,9 @@
# Validator for the standard csv format for imports to museum-digital
# Development
To update, run `scripts/compile.php`.
## Dependencies
- [JSZip](https://github.com/Stuk/jszip), dual-licensed under MIT & GPLv3

View File

@ -10,6 +10,8 @@ declare(strict_types = 1);
const CACHE_DIR_PERMS = 0775;
const ALLOWED_LANGS = ['de', 'en', 'hu'];
const TL_FILE_DIRS = [
__DIR__ . "/../l10n/musdb/",
__DIR__ . "/../l10n/importer/",

View File

@ -38,7 +38,7 @@ a { text-decoration: none; color: inherit; }
h1 { display: block; max-width: 600px; margin: 0 auto 1.5em auto; }
h1 > * { display: inline-block; vertical-align: middle; color: var(--color-fg-less); }
h1 img { height: 2em; margin-right: .2em; border-radius: .1em; opacity: .7; transition: opacity .4s; }
h1 img { height: 2em; margin-right: .5em; border-radius: .1em; opacity: .7; transition: opacity .4s; }
h1 img:hover { opacity: 1; }
main,
@ -118,6 +118,23 @@ ul.fieldList > li.humanTLToggled { border-color: var(--color-accent-hover); back
.actionList > li a { display: inline-block; padding: .3em; border-radius: .3em; transition: background .4s; }
.actionList > li a:hover { background: var(--color-accent-hover); }
.loading:before,
.loading:after { content: " "; display: block;
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
height: 64px; width: 64px; margin: 0; padding: 0;
border-radius: 50%;
border: 8px solid var(--color-accent-hover);
border-color: var(--color-accent-hover) transparent transparent transparent;
z-index: 100;
animation: rotating 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; animation-delay: -0.15s; }
.loading:after { animation-delay: -0.45s; }
@keyframes rotating {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* =============
| Dark mode
|============ */

View File

@ -1,32 +1,35 @@
@import 'editMenu.css';@font-face{font-family:sourceSansPro;src:local('Source-Sans-Pro'),local('Source Sans Pro'),url(../fonts/SourceSansPro-Regular.woff2)format('woff2'),url(../fonts/SourceSansPro-Regular.ttf)format('truetype');font-display:swap}*{box-sizing:border-box;z-index:1}
body{margin:2em;background:#FFF;font-family:sourceSansPro,Arial,Helvetica,Times;font-size:1.2em;line-height:1.5em}
@import 'editMenu.css';@import 'dialogue.css';:root{--color-bg-normal:#FFF;--color-bg-raised:#EEE;--color-bg-raised2:#FAFAFA;--color-fg-normal:#000;--color-fg-less:#212121;--color-borders:#D6D6D6;--color-borders-dark:#646464;--color-accent-normal:#FFCCBC;--color-accent-hover:#CB9B8C}
@font-face{font-family:sourceSansPro;src:local('Source-Sans-Pro'),local('Source Sans Pro'),url(../fonts/SourceSansPro-Regular.woff2)format('woff2'),url(../fonts/SourceSansPro-Regular.ttf)format('truetype');font-display:swap}*{box-sizing:border-box;z-index:1}
body{margin:2em;background:var(--color-bg-normal);font-family:sourceSansPro,Arial,Helvetica,Times;font-size:1.2em;line-height:1.5em}
a{text-decoration:none;color:inherit}
h1{display:block;max-width:600px;margin:0 auto 1.5em auto}
h1>*{display:inline-block;vertical-align:middle;color:#212121}
h1 img{height:2em;margin-right:.2em;border-radius:.1em;opacity:.7;transition:opacity.4s}
h1>*{display:inline-block;vertical-align:middle;color:var(--color-fg-less)}
h1 img{height:2em;margin-right:.5em;border-radius:.1em;opacity:.7;transition:opacity.4s}
h1 img:hover{opacity:1}
body>div,body>form{display:block;max-width:90vw;margin:0 auto 3em auto;padding-bottom:3em}
main,body>div,body>form{display:block;max-width:90vw;margin:0 auto 3em auto;padding-bottom:3em}
form>div{margin-bottom:1em}
label{display:block;font-weight:bold;margin-bottom:.5em}.invisible{display:none!important;opacity:0}.buttonLike,select,button,textarea,input{display:block;width:100%;padding:.5em.5em;border:2.5px solid #EEE;background:inherit;color:#424242;font-family:roboto;font-size:1em;border-radius:.2em;transition:border.2s}.buttonLike{display:inline-block;text-align:center}
input[type="submit"],button{padding:.5em.8em}.buttonLike,input[type="submit"],button{background:#FFCCBC;color:#424242;border:none;font-weight:bold;text-transform:uppercase;transition:background.2s,color.2s}
label{display:block;font-weight:bold;margin-bottom:.5em}.invisible{display:none!important;opacity:0}.buttonLike,select,button,textarea,input{display:block;width:100%;padding:.5em.5em;border:2.5px solid var(--color-bg-raised);background:inherit;color:var(--color-fg-less);font-family:roboto;font-size:1em;border-radius:.2em;transition:border.2s}.buttonLike{display:inline-block;text-align:center}
input[type="submit"],button{padding:.5em.8em}.buttonLike,input[type="submit"],button{background:var(--color-accent-normal);color:var(--color-fg-less);border:none;font-weight:bold;text-transform:uppercase;transition:background.2s,color.2s}
textarea{line-height:1.2em}
select:hover,textarea:hover,input:hover{border-color:#888}.buttonLike:focus,input[type="submit"]:focus,button:focus,.buttonLike:hover,input[type="submit"]:hover,button:hover{background:#CB9B8C;color:#000}
select:hover,textarea:hover,input:hover{border-color:#888}.buttonLike:focus,input[type="submit"]:focus,button:focus,.buttonLike:hover,input[type="submit"]:hover,button:hover{background:var(--color-accent-hover);color:#000}
aside.buttonLike+.buttonLike,aside button+button{margin-top:.5em}
aside>h4:first-child{margin-top:0}
select:focus,textarea:focus,input:focus{border-color:#CB9B8C;box-shadow:none}
select:focus,textarea:focus,input:focus{border-color:var(--color-accent-hover);box-shadow:none}
textarea:invalid,input:invalid{box-shadow:none}
textarea:invalid:focus,input:invalid:focus{border-right-width:1em}
textarea{min-height:30vh}
table{width:100%;max-height:60vh;margin:2em 0;border-collapse:collapse;overflow:auto}
th{padding:.3em.5em;text-align:left;border-bottom:2px solid #424242}
tbody>tr:nth-child(2n+1){background:#F2F2F2}
td{padding:.3em.5em;border-bottom:1px solid #D6D6D6}
body>div.uploader{background:#F2F2F2;border:2px solid #EEE;padding:1em 1em}
th{padding:.3em.5em;text-align:left;border-bottom:2px solid var(--color-fg-less)}
tbody>tr:nth-child(2n+1){background:var(--color-bg-raised2)}
td{padding:.3em.5em;border-bottom:1px solid var(--color-borders)}
body>div.uploader{background:var(--color-bg-raised2);border:2px solid var(--color-bg-raised);padding:1em 1em}
ul.fieldList{display:block;margin:.5em 0;padding:0 0;list-style:none}
ul.fieldList>li{display:inline-block;padding:.3em;margin:.1em;border:1px solid #D6D6D6;background:#FAFAFA;cursor:pointer;transition:background.4s,border.4s,box-shadow.4s}
ul.fieldList>li:hover{background:#FFF;border-color:#212121}
ul.fieldList>li{display:inline-block;padding:.3em;margin:.1em;border:1px solid var(--color-borders);background:var(--color-bg-raised2);cursor:pointer;transition:background.4s,border.4s,box-shadow.4s}
ul.fieldList>li:hover{background:var(--color-bg-normal);border-color:var(--color-fg-less)}
ul.fieldList>li.requiredField:before{display:inline-block;content:" \002612 ";margin-right:.5em}
ul.fieldList>li.humanTLToggled{border-color:#CB9B8C;background:#CB9B8C;box-shadow:0 8px 6px-6px black}.options>a.buttonLike{display:inline-block;width:auto;margin:.15em 0;padding:.4em.5em;text-transform:inherit;cursor:pointer;background:initial;border:2px solid #D6D6D6;opacity:1;transition:background.4s,opacity.4s}.options>a.buttonLike:hover{background:#D6D6D6}.actionList{margin:1em 0 1em 1em;padding:.5em 0}.actionList>li a{display:inline-block;padding:.3em;border-radius:.3em;transition:background.4s}.actionList>li a:hover{background:#CB9B8C}
ul.fieldList>li.humanTLToggled{border-color:var(--color-accent-hover);background:var(--color-accent-hover);box-shadow:0 8px 6px-6px black}.options>a.buttonLike{display:inline-block;width:auto;margin:.15em 0;padding:.4em.5em;text-transform:inherit;cursor:pointer;background:initial;border:2px solid var(--color-borders);opacity:1;transition:background.4s,opacity.4s}.options>a.buttonLike:hover{background:var(--color-borders)}.actionList{margin:1em 0 1em 1em;padding:.5em 0}.actionList>li a{display:inline-block;padding:.3em;border-radius:.3em;transition:background.4s}.actionList>li a:hover{background:var(--color-accent-hover)}.loading:before,.loading:after{content:" ";display:block;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);height:64px;width:64px;margin:0;padding:0;border-radius:50%;border:8px solid var(--color-accent-hover);border-color:var(--color-accent-hover)transparent transparent transparent;z-index:100;animation:rotating 1.2s cubic-bezier(0.5,0,0.5,1)infinite;animation-delay:-0.15s}.loading:after{animation-delay:-0.45s}
@keyframes rotating{from{transform:rotate(0deg)}
to{transform:rotate(360deg)}}
@media(prefers-color-scheme:dark){body{background:#263238;color:#ECEFF1}
input,select,textarea{background:inherit;color:inherit;border:3px solid #37474F;transition:border.4s,box-shadow.4s}
a.buttonLike:focus,.options>a.buttonLike:focus,button:focus,select:focus,textarea:focus,input:focus,textarea:active,input:active,a.buttonLike:hover,.options>a.buttonLike:hover,ul.fieldList>li:hover,button:hover,select:hover,textarea:hover,input:hover{background:inherit;border-color:#FFF;box-shadow:initial;border-radius:.2em}

View File

@ -380,7 +380,7 @@ class CsvxmlPage {
csvBySelectionButton;
unsetSelectionButton;
constructor(fieldList) {
constructor(fieldList, tls) {
this.fieldList = Object.freeze(fieldList);
let list = {};
@ -389,7 +389,7 @@ class CsvxmlPage {
}
this.fieldListFlat = Object.freeze(list);
this.tls = Object.freeze(JSON.parse(document.body.getAttribute("data-tls")));
this.tls = Object.freeze(tls);
let domUploaderWrapper = document.createElement("div");
domUploaderWrapper.id = "uploader";
@ -587,6 +587,29 @@ class CsvxmlPage {
}
renderHeader() {
const header = 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);
header.appendChild(h1);
document.body.appendChild(header);
}
renderUploader() {
let app = this;
@ -853,19 +876,60 @@ class CsvxmlPage {
}
(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.body.classList.add("loading");
window.fetch('/?output=json', {
method: 'GET', cache: 'no-cache',
credentials: 'same-origin',
}).then(function(response) {
return response.json();
}).then(function(elements) {
let loaded = 0;
let fieldList;
let tls;
function loadPage() {
document.body.classList.remove("loading");
const page = new CsvxmlPage(elements);
const page = new CsvxmlPage(fieldList, tls);
page.renderHeader();
page.renderUploader();
page.renderMain();
}
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();
});
})();

66
public/assets/js/csvxmlV2.min.js vendored Normal file
View File

@ -0,0 +1,66 @@
"use strict";class CsvxmlValidator{fieldList;toValidate;errors;constructor(fieldList,csvRaw){this.errors={parsing:[],mandatoryTags:[],duplicateInvNos:[],dependentColumns:[],controlledLists:[],mainImageResource:[],};this.fieldList=Object.freeze(fieldList);const lines=csvRaw.trim().replace("\r\n","\n").split("\n");let separator;let delimiter;if(csvRaw.substr(0,1)==='"'){separator='";"';delimiter='"'}else{separator=';';delimiter=''}
let headersFields=lines.shift();let headers;if(delimiter==="")headers=headersFields.split(separator);else headers=headersFields.substr(1,headersFields.length-2).split(separator);let expectedFieldCount=headers.length;let toValidate=[];let lineCounter=1;for(let line of lines){if(delimiter!==''){line=line.substr(1,line.length-2)}
if(line.length<=headers.length)continue;let lineContents={};let fields=line.split(separator);if(fields.length!==headers.length){this.errors.parsing.push("Number of columns in line "+lineCounter+" does not match number of headers")}
for(let i=0,max=fields.length;i<max;i++){if(headers[i]===undefined||headers[i]===null){this.errors.parsing.push("ERROR parsing line "+lineCounter+"; column "+i);continue}
lineContents[headers[i]]=fields[i]}
if(Object.values(lineContents).join("").length===0)continue;toValidate.push(lineContents);lineCounter++}
this.toValidate=toValidate;if(toValidate.length===0){alert("Error: No lines of content identified")}
this.validate()}
validate(){this.validateMandatoryTagsPresent();this.checkDuplicateInvNos();this.checkDependentColumns();this.checkControlledLists()}
validateMandatoryTagsPresent(){let mandatoryFields=[];for(let fieldName in this.fieldList){if(this.fieldList[fieldName].required===!0){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++}}
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)===!1){this.errors.dependentColumns.push("Dependency issue at column "+header+": Corresponding column "+dep+" is missing")}}}}
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}
let allowedValues=this.fieldList[fieldName].allowedValues;if(allowedValues===undefined||allowedValues===null)continue;if(Object.values(allowedValues).length===0||Object.values(allowedValues).includes(line[fieldName])){continue}
this.errors.controlledLists.push("Disallowed value used for column "+fieldName+" at line "+lineCounter+" (Allowed values are: "+Object.values(allowedValues).join(", ")+")")}
lineCounter++}}
checkMainImageResource(){}
isValid(){for(let errorClass in this.errors){if(this.errors[errorClass].length!==0)return!1}
return!0}
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{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")===!1){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";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:!0});elem.addEventListener("mousemove",CsvxmlTooltip.triggerMouseMove,{passive:!0})}}
class CsvxmlDialogue{static closeDialogue(e){if(e!==undefined){e.preventDefault();e.stopPropagation()}
let dialogueArea=document.getElementById("dialogueArea");if(dialogueArea!==null&&dialogueArea!==!1){while(dialogueArea.firstChild){dialogueArea.removeChild(dialogueArea.firstChild)}
dialogueArea.parentElement.removeChild(dialogueArea);document.removeEventListener('keydown',CsvxmlDialogue.closeDialogueByEscape,!1)}}
static closeDialogueByEscape(e){if(e.keyCode===27){CsvxmlDialogue.closeDialogue(e)}}
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;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 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)===!1)continue;const field=this.fieldListFlat[fieldName];line1.push(fieldName);line2.push(field.name_human_readable);if(field.allowedValues!==undefined){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;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){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(){runZipping()},{passive:!0,once:!0});document.body.appendChild(loadScript)}else{runZipping()}}
listValidationErrors(validator){const dialogueContent=document.createElement("div");const headline=document.createElement("h3");headline.textContent=this.tls.validation_errors;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);headline.appendChild(cancelB);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();reader.readAsText(file);let app=this;reader.onload=function(){console.log("Read file");let validator=new CsvxmlValidator(app.fieldListFlat,reader.result);if(validator.isValid()===!0){alert("Document is valid. Press ok to download.");app.zipUploadToXml(validator)}else{app.listValidationErrors(validator)}};reader.onerror=function(){alert(reader.error)}}
renderHeader(){const header=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);header.appendChild(h1);document.body.appendChild(header)}
renderUploader(){let app=this;(async function(){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.accept=".csv";input.required="required";input.addEventListener('change',async function(){app.uploadFileForValidation(input.files[0])});form.appendChild(input);app.domUploaderWrapper.appendChild(form)})();document.body.appendChild(this.domUploaderWrapper)}
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")===!1)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")===!0)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("a");output.id=id;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(){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){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){app.doForFieldList(function(field){if(field.classList.contains("requiredField")===!1)return;if(field.classList.contains("humanTLToggled")===!0)return;app.toggleListFieldSelectionState(field);app.checkCSVBySelectionAccessibility()})});optionSelectAll.addEventListener('click',function(e){app.doForFieldList(function(field){if(field.classList.contains("humanTLToggled")===!0)return;app.toggleListFieldSelectionState(field);app.checkCSVBySelectionAccessibility()})});this.unsetSelectionButton.addEventListener('click',function(e){app.doForFieldList(function(field){if(field.classList.contains("humanTLToggled")===!1)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===!0)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){field.addEventListener('click',function(e){app.toggleListFieldSelectionState(field);app.checkCSVBySelectionAccessibility()})})}}(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.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.renderHeader();page.renderUploader();page.renderMain()}
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();})})()

23
public/index.htm Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html data-allowed-langs="de,en,hu">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<meta name="description" content="Validate import CSV files for museum-digital" />
<link rel="stylesheet" type="text/css" href="assets/css/csvxml.css" />
<meta name="theme-color" content="#aa4400" />
<link rel="shortcut icon" sizes="128x128" href="assets/img/mdlogo-csvxml.svg" />
<meta name="robots" content="noindex" />
<title>CSVXML :: museum-digital</title>
<meta name="keywords" content="Imports, museum-digital" />
</head>
<body class="loading">
<script src="assets/js/csvxmlV2.js" type="text/javascript" async></script>
</body>
</html>

View File

@ -1,85 +0,0 @@
<?PHP
/**
* New start page for CSVXML.
*
* @link https://groupit.museum-digital.de/csvxml/
*
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
*/
declare(strict_types = 1);
require_once __DIR__ . "/../functions/functions.php";
$allowed_langs = ['ar', 'de', 'en', 'hu', 'id', 'it', 'pl', 'pt'];
$lang = MD_STD::get_user_lang($allowed_langs, "en");
$tlLoader = new MDTlLoader("csxml_start_v2", $lang);
$outFormat = MD_STD_IN::get_http_input_text("output", "html", ['html', 'json']);
if ($outFormat === 'json') {
$fieldsGetter = new CsvxmlAvailableFields($lang);
$availableFields = $fieldsGetter->getFields();
header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0'); // HTTP/1.1
header('Pragma: no-cache'); // HTTP/1.0
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Allow-Headers: X-PINGOTHER, Content-Type, Accept-Encoding, cache-control");
header("Access-Control-Max-Age: 86400");
header('content-type: application/json');
echo json_encode($availableFields);
return;
}
echo '
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<meta name="description" content="Validate import CSV files for museum-digital" />
<link rel="stylesheet" type="text/css" href="assets/css/csvxml.css" />
<meta name="theme-color" content="#aa4400" />
<link rel="shortcut icon" sizes="128x128" href="assets/img/mdlogo-csvxml.svg" />
<meta name="robots" content="noindex" />
<title>CSVXML :: museum-digital</title>
<meta name="keywords" content="Imports, museum-digital" />
</head>
<body class="loading" data-tls="' . htmlspecialchars(MD_STD::json_encode([
'remarks' => $tlLoader->tl('basis', 'basis', 'remarks'),
'download' => $tlLoader->tl('export', 'export', 'download'),
'upload' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'upload'),
'select_csv_file_for_upload' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'select_csv_file_for_upload'),
'currently_approved_tags' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'currently_approved_tags'),
'download_csv_all' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'download_csv_all'),
'download_csv_by_selection' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'download_csv_by_selection'),
'select_required_fields' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'select_required_fields'),
'select_all_fields' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'select_all_fields'),
'unset_selection' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'unset_selection'),
'file_format' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'file_format'),
'validation_errors' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'validation_errors'),
'errors_parsing' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_parsing'),
'errors_mandatoryTags' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_mandatoryTags'),
'errors_duplicateInvNos' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_duplicateInvNos'),
'errors_dependentColumns' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_dependentColumns'),
'errors_controlledLists' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_controlledLists'),
'errors_mainImageResource' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_mainImageResource'),
'allowed_values' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'allowed_values'),
])) . '">
<h1>
<img src="assets/img/mdlogo-csvxml.svg" alt="" />
<span>museum-digital:csvxml</span>
</h1>
<script src="assets/js/csvxmlV2.js" type="text/javascript" async></script>
</body>
</html>';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/json/tls.de.json Normal file
View File

@ -0,0 +1 @@
{"remarks":"Notizen","download":"Download","upload":"Hochladen","select_csv_file_for_upload":"Bitte w\u00e4hlen Sie eine CSV Datei als Basis zum Erstellen von XML Dateien","currently_approved_tags":"Derzeit zum Import verf\u00fcgbare Tags \/ Felder","download_csv_all":"CSV-Vorlage mit allen Feldern runterladen","download_csv_by_selection":"CSV-Vorlage auf Basis der Auswahl herunterladen","select_required_fields":"Pflichtfelder ausw\u00e4hlen","select_all_fields":"Alle Felder ausw\u00e4hlen","unset_selection":"Auswahl entfernen","file_format":"Dateiformat","validation_errors":"Validierungsfehler","errors_parsing":"Parsing-Fehler","errors_mandatoryTags":"Fehlende Pflichtfelder","errors_duplicateInvNos":"Doppelte Inventarnummern","errors_dependentColumns":"Spaltenabh\u00e4ngigkeiten nicht ber\u00fccksichtigt","errors_controlledLists":"Kontrollierte Werte","errors_mainImageResource":"Fehlende Haupt-Bilder oder Ressourcen","allowed_values":"Zugelassene Werte"}

1
public/json/tls.en.json Normal file
View File

@ -0,0 +1 @@
{"remarks":"Remarks","download":"Download","upload":"Upload","select_csv_file_for_upload":"Please select a CSV file to create XML files","currently_approved_tags":"Currently approved tags (column names) for md:import","download_csv_all":"Download CSV template with all fields","download_csv_by_selection":"Download CSV template based on selection","select_required_fields":"Select required fields","select_all_fields":"Select all fields","unset_selection":"Unset selection","file_format":"File format","validation_errors":"Validation errors","errors_parsing":"Parse errors","errors_mandatoryTags":"Missing mandatory tags","errors_duplicateInvNos":"Duplicate inventory numbers","errors_dependentColumns":"Column dependencies unresolved","errors_controlledLists":"Controlled lists","errors_mainImageResource":"Missing main images or resources","allowed_values":"Allowed values"}

1
public/json/tls.hu.json Normal file
View File

@ -0,0 +1 @@
{"remarks":"Megjegyz\u00e9sek","download":"Let\u00f6lt\u00e9s","upload":"Felt\u00f6lt\u00e9s","select_csv_file_for_upload":"Please select a CSV file to create XML files","currently_approved_tags":"Jelenleg j\u00f3v\u00e1hagyott c\u00edmk\u00e9k (oszlopnevek) az md:importhoz","download_csv_all":"Download CSV template with all fields","download_csv_by_selection":"Download CSV template based on selection","select_required_fields":"K\u00f6telez\u0151 mez\u0151k kijel\u00f6l\u00e9se","select_all_fields":"\u00d6sszes mez\u0151 kijel\u00f6l\u00e9s","unset_selection":"Kijel\u00f6l\u00e9s megsz\u00fcntet\u00e9se","file_format":"Kiterjeszt\u00e9s","validation_errors":"Validation errors","errors_parsing":"Parse errors","errors_mandatoryTags":"Missing mandatory tags","errors_duplicateInvNos":"Duplicate inventory numbers","errors_dependentColumns":"Column dependencies unresolved","errors_controlledLists":"Controlled lists","errors_mainImageResource":"Missing main images or resources","allowed_values":"Allowed values"}

108
scripts/compile.php Normal file
View File

@ -0,0 +1,108 @@
<?PHP
/**
* Compiles the client-side application from PHP parts.
*
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
*/
declare(strict_types = 1);
require_once __DIR__ . "/../functions/functions.php";
/**
* Generates the json for a translation file.
*
* @param string $lang Language.
*
* @return string
*/
function generateFieldsIndex(string $lang):string {
$fieldsGetter = new CsvxmlAvailableFields($lang);
$availableFields = $fieldsGetter->getFields();
return MD_STD::json_encode($availableFields);
}
/**
* Generates the json for a translation file.
*
* @param string $lang Language.
*
* @return string
*/
function generateTranslationFile(string $lang):string {
$tlLoader = new MDTlLoader("csvxml_tl_cache", $lang);
return MD_STD::json_encode([
'remarks' => $tlLoader->tl('basis', 'basis', 'remarks'),
'download' => $tlLoader->tl('export', 'export', 'download'),
'upload' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'upload'),
'select_csv_file_for_upload' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'select_csv_file_for_upload'),
'currently_approved_tags' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'currently_approved_tags'),
'download_csv_all' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'download_csv_all'),
'download_csv_by_selection' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'download_csv_by_selection'),
'select_required_fields' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'select_required_fields'),
'select_all_fields' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'select_all_fields'),
'unset_selection' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'unset_selection'),
'file_format' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'file_format'),
'validation_errors' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'validation_errors'),
'errors_parsing' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_parsing'),
'errors_mandatoryTags' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_mandatoryTags'),
'errors_duplicateInvNos' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_duplicateInvNos'),
'errors_dependentColumns' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_dependentColumns'),
'errors_controlledLists' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_controlledLists'),
'errors_mainImageResource' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'errors_mainImageResource'),
'allowed_values' => $tlLoader->tl("csvxml-overview", "csvxml_overview", 'allowed_values'),
]);
}
/**
* Generate index.htm.
*
* @return string
*/
function generateAppShell():string {
return '<!DOCTYPE HTML>
<html data-allowed-langs="' . htmlspecialchars(implode(',', ALLOWED_LANGS)) . '">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<meta name="description" content="Validate import CSV files for museum-digital" />
<link rel="stylesheet" type="text/css" href="assets/css/csvxml.css" />
<meta name="theme-color" content="#aa4400" />
<link rel="shortcut icon" sizes="128x128" href="assets/img/mdlogo-csvxml.svg" />
<meta name="robots" content="noindex" />
<title>CSVXML :: museum-digital</title>
<meta name="keywords" content="Imports, museum-digital" />
</head>
<body class="loading">
<h1>
<img src="assets/img/mdlogo-csvxml.svg" alt="" />
<span>museum-digital:csvxml</span>
</h1>
<script src="assets/js/csvxmlV2.js" type="text/javascript" async></script>
</body>
</html>';
}
const SERVED_ROOT = __DIR__ . '/../public/';
const SERVED_JSON_ROOT = __DIR__ . '/../public/json/';
if (!is_dir(SERVED_JSON_ROOT)) mkdir(SERVED_JSON_ROOT);
foreach (ALLOWED_LANGS as $lang) {
file_put_contents(SERVED_JSON_ROOT . 'fields.' . $lang . '.json', generateFieldsIndex($lang));
file_put_contents(SERVED_JSON_ROOT . 'tls.' . $lang . '.json', generateTranslationFile($lang));
}
file_put_contents(SERVED_ROOT . 'index.htm', generateAppShell());

View File

@ -1,33 +0,0 @@
<?PHP
/**
* This script contains tests for the home page.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
declare(strict_types = 1);
// phpcs:disable
use PHPUnit\Framework\TestCase;
/**
* Test class for the start page.
*/
final class CsvTest extends TestCase {
/**
* Test for HTML output.
*
* @return void
*/
public function testHTMLOutput():void {
include __DIR__ . "/../public/csv.php";
$output = $this->getActualOutput();
self::assertIsString($output);
ob_clean();
}
}

View File

@ -1,33 +0,0 @@
<?PHP
/**
* This script contains tests for the home page.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
declare(strict_types = 1);
// phpcs:disable
use PHPUnit\Framework\TestCase;
/**
* Test class for the start page.
*/
final class StartPageTest extends TestCase {
/**
* Test for HTML output.
*
* @return void
*/
public function testHTMLOutput():void {
include __DIR__ . "/../public/index.php";
$output = $this->getActualOutput();
self::assertIsString($output);
ob_clean();
}
}