// ==UserScript==
// @name Almascript - Alma Start Process List Helper
// @namespace https://greasyfork.org/en/users/8332-sreyemnayr
// @version 2019.8.6.1
// @description Show what isn't done and display uploaded files.
// @author Ryan Meyers
// @match https://*.getalma.com/workflows/processes/*/review
// @require https://greasyfork.org/scripts/388114-pdf-js/code/PDFjs.js?version=721820
// @require https://greasyfork.org/scripts/388210-html2pdf-js/code/html2pdfjs.js?version=722443
// @require https://unpkg.com/pdf-lib@1.0.1/dist/pdf-lib.min.js
// @require https://unpkg.com/file-saver@2.0.2/dist/FileSaver.min.js
// @grant unsafeWindow
// ==/UserScript==
// Loaded via <script> tag, create shortcut to access PDF.js exports.
var pdfjsLib = window["pdfjs-dist/build/pdf"];
// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc =
"//greasyfork.org/scripts/388115-pdf-js-worker/code/PDFjs%20Worker.js?version=721821";
const readBlobAsArrayBuffer = (blob) => {
const temporaryFileReader = new FileReader();
return new Promise((resolve, reject) => {
temporaryFileReader.onerror = () => {
temporaryFileReader.abort();
reject(new DOMException("Problem parsing input file."));
};
temporaryFileReader.onload = () => {
resolve(temporaryFileReader.result);
};
temporaryFileReader.readAsArrayBuffer(blob);
});
};
function clearBody(body) {
body = body.replace(/src=/g,"data-src=");
body = body.replace(/<link/g, "nolink");
return body;
}
function fetchAndUpdate(node) {
const updateNode = node;
fetch(node.href)
.then(function(response) {
return response.text();
})
.then(function(body) {
body = clearBody(body);
var parser = new DOMParser();
var doc = parser.parseFromString(body, "text/html");
var xpath =
"//li[contains(@class,'task')][not(contains(@class,'task-complete'))]";
var result = document.evaluate(
xpath,
doc,
null,
XPathResult.ANY_TYPE,
null
);
//console.log(result);
var node,
nodes = [];
while ((node = result.iterateNext())) {
//console.log(node.textContent.trim());
var newNode = document.createElement("div");
newNode.classList.add("pill");
newNode.innerHTML =
'<i class="far fa-times-circle" style="color:#eb6841;"></i>' +
node.textContent.trim();
updateNode.parentElement.parentElement.children[4].append(newNode);
}
});
}
function fetchHealthForm(node) {
var updateNode = node;
var pdfIcon = document.createElement("div");
pdfIcon.classList.add(
"pure-button",
"pure-button-pdf",
"pure-button-large",
"image-icon-button"
);
var iconElement = document.createElement("i");
iconElement.classList.add("far", "fa-images", "fa-1x", "pdfIcon");
pdfIcon.append(iconElement);
var studentName,
processName = "";
updateNode.parentElement.parentElement.children[0].append(pdfIcon);
pdfIcon.onclick = async function() {
iconElement.classList.add("lds-circle");
fetch(node.href)
.then(function(response) {
return response.text();
})
.then(function(body) {
//console.log(body);
body = clearBody(body);
var parser = new DOMParser();
var doc = parser.parseFromString(body, "text/html");
var xpath = "//li[contains(@class,'task')]";
var result = document.evaluate(
xpath,
doc,
null,
XPathResult.ANY_TYPE,
null
);
//console.log(result);
var node,
nodes = [];
var numFiles = 0;
var filesDone = 0;
while ((node = result.iterateNext())) {
var taskUri = node.dataset.href;
var formUri = taskUri.replace("task-details", "form");
let headers = new Headers({
Accept: "application/json",
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
});
fetch(formUri, { method: "GET", headers: headers })
.then(function(response) {
return response.json();
})
.then(function(myJson) {
//console.log(myJson);
var jsonHTML = myJson.Message.html;
jsonHTML = jsonHTML.replace(/form-section/g, "form-section-off");
jsonHTML = jsonHTML.replace(
/<ul class/g,
'<ul style="display:none;" class'
);
//console.log(jsonHTML);
//var files = jsonHTML.match(/<a href="(\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*)">/g);
var files = jsonHTML.match(
/\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*/g
);
if (files) {
numFiles += files.length;
iconElement.classList.add("lds-circle");
for (var file of files) {
fetch(file)
.then(function(response) {
return response.blob();
})
.then(async function(blob) {
console.log(blob.type);
let reader = new FileReader();
reader.readAsArrayBuffer(blob);
reader.onload = async function() {
var newImg;
//blob.arrayBuffer().then(async function(myBuffer){
if (blob.type === "application/pdf") {
newImg = document.createElement("canvas");
var loadingTask = pdfjsLib.getDocument(file);
loadingTask.promise.then(
function(pdf) {
console.log("PDF loaded");
// Fetch the first page
var pageNumber = 1;
pdf.getPage(pageNumber).then(function(page) {
console.log("Page loaded");
var scale = 0.25;
var viewport = page.getViewport(scale);
// Prepare canvas using PDF page dimensions
var canvas = newImg;
var context = canvas.getContext("2d");
canvas.height = 230;
canvas.width = 160;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function() {
console.log("Page rendered");
});
});
},
function(reason) {
// PDF loading error
console.error(reason);
}
);
//newImg = document.createElement('a');
//newImg.href = file;
//newImg.innerHTML = "Download";
updateNode.append(newImg);
} else {
newImg = document.createElement("img");
newImg.src = file;
newImg.width = 160;
updateNode.append(newImg);
}
filesDone += 1;
if (filesDone === numFiles) {
iconElement.classList.remove("lds-circle");
}
};
});
}
}
});
}
})
.then(function() {});
};
}
function doIncomplete() {
var xpath = "//tr[td[text()='Active (in progress)']]/td[2]/a";
var result = document.evaluate(
xpath,
document,
null,
XPathResult.ANY_TYPE,
null
);
var node,
nodes = [];
while ((node = result.iterateNext())) {
nodes.push(node);
//console.log(node.href);
fetchAndUpdate(node);
}
}
function clickAllImageButtons() {
var node;
for (node of document.getElementsByClassName("image-icon-button")) {
node.click();
}
}
function downloadAllPDFs() {
var node;
for (node of document.getElementsByClassName("pdf-icon-button")) {
node.click();
}
}
function doCompletePDFButtons() {
var xpath =
"//tr[td[text()='Active (complete)' or text()='Complete']]/td[2]/a";
var result = document.evaluate(
xpath,
document,
null,
XPathResult.ANY_TYPE,
null
);
//var result = document.evaluate(xpath,document.cloneNode(true))
var node,
nodes = [];
while ((node = result.iterateNext())) {
nodes.push(node);
//console.log(node.href);
}
for (node of nodes) {
generatePDF(node);
generatePDF(node, true);
fetchHealthForm(node);
}
}
function generatePDF(node, justHealth = false) {
const onlyHealth = justHealth;
var updateNode = node;
var pdfIcon = document.createElement("div");
var iconElement = document.createElement("i");
if (onlyHealth) {
pdfIcon.classList.add(
"pure-button",
"pure-button-pdf",
"pure-button-large",
"health-icon-button"
);
iconElement.classList.add("fas", "fa-notes-medical", "fa-1x", "pdfIcon");
} else {
pdfIcon.classList.add(
"pure-button",
"pure-button-pdf",
"pure-button-large",
"pdf-icon-button"
);
iconElement.classList.add("fas", "fa-file-pdf", "fa-1x", "pdfIcon");
}
pdfIcon.append(iconElement);
var studentName,
processName = "";
updateNode.parentElement.parentElement.children[0].append(pdfIcon);
pdfIcon.onclick = async function() {
iconElement.classList.add("lds-circle");
const bigPdf = await PDFLib.PDFDocument.create();
var allHTML = [];
const bigFetch = await fetch(node.href);
var body = await bigFetch.text();
body = clearBody(body);
//console.log(body);
var parser = new DOMParser();
var doc = parser.parseFromString(body, "text/html");
var xpath;
studentName = doc.getElementsByClassName("fn")[0].innerText.trim();
if (onlyHealth) {
processName =
"HEALTH " + doc.getElementById("page-header").innerText.trim();
xpath =
"//li[contains(@class,'task')][div/h5[contains(text(),'Health' ) or contains(text(), 'Medi')]]";
} else {
processName = doc.getElementById("page-header").innerText.trim();
xpath = "//li[contains(@class,'task')]";
}
var result = document.evaluate(
xpath,
doc,
null,
XPathResult.ANY_TYPE,
null
);
var numDone = 0;
var task;
while ((task = result.iterateNext())) {
var taskUri = task.dataset.href;
var formUri = taskUri.replace("task-details", "form");
let headers = new Headers({
Accept: "application/json",
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
});
var taskNum = parseInt(taskUri.match(/task=([0-9]+)/)[1]);
console.log(taskNum);
var fetchResponse = await fetch(formUri, { method: "GET", headers: headers });
var myJson = await fetchResponse.json();
console.log(myJson);
var jsonHTML = myJson.Message.html;
jsonHTML = jsonHTML.replace(/form-section/g, "form-section-off");
jsonHTML = jsonHTML.replace(
/<ul class/g,
'<ul style="display:none;" class'
);
console.log(jsonHTML);
//var files = jsonHTML.match(/<a href="(\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*)">/g);
var files = jsonHTML.match(
/\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*/g
);
if (files) {
for (var file of files) {
var fileResponse = await fetch(file);
var blob = await fileResponse.blob();
console.log(blob.type);
const blobArrayBuffer = await readBlobAsArrayBuffer(blob);
//blob.arrayBuffer().then(async function(myBuffer){
if (blob.type === "application/pdf") {
const pdf = await PDFLib.PDFDocument.load(
blobArrayBuffer
);
console.log(pdf);
const numPages = pdf.getPages().length;
const copiedPages = await bigPdf.copyPages(
pdf,
Array.from(Array(numPages).keys())
);
copiedPages.forEach(page => {
bigPdf.addPage(page);
});
} else if (blob.type === "image/jpeg") {
// Embed the JPG image bytes and PNG image bytes
const jpgImage = await bigPdf.embedJpg(blobArrayBuffer);
// Get the width/height of the JPG image scaled down to 25% of its original size
const jpgDims = jpgImage.scale(0.5);
// Add a blank page to the document
const page = bigPdf.addPage([
jpgDims.width,
jpgDims.height
]);
// Draw the JPG image in the center of the page
page.drawImage(jpgImage, {
x: page.getWidth() / 2 - jpgDims.width / 2,
y: page.getHeight() / 2 - jpgDims.height / 2,
width: jpgDims.width,
height: jpgDims.height
});
} else if (blob.type === "image/png") {
// Embed the JPG image bytes and PNG image bytes
const jpgImage = await bigPdf.embedPng(blobArrayBuffer);
// Get the width/height of the JPG image scaled down to 25% of its original size
const jpgDims = jpgImage.scale(0.5);
// Add a blank page to the document
const page = bigPdf.addPage([
jpgDims.width,
jpgDims.height
]);
// Draw the JPG image in the center of the page
page.drawImage(jpgImage, {
x: page.getWidth() / 2 - jpgDims.width / 2,
y: page.getHeight() / 2 - jpgDims.height / 2,
width: jpgDims.width,
height: jpgDims.height
});
}
//bigPdf.addPage(pdf);
// console.log(pdf);
//var objectURL = URL.createObjectURL(myBlob);
//let reader = new FileReader();
//reader.readAsDataURL(myBlob);
//reader.onload = function() {
// console.log(myBlob);
//allHTML += "<embed src=\""+reader.result+"\" width=\"850\" height=\"1100\" class=\"page-break\" type=\"application/pdf\">";
//numDone += 1;
// pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 3) )).toString() + "%";
//};
}
}
console.log(files);
var jsonHeader = myJson.Message.header;
allHTML[taskNum] = "<h1>" + jsonHeader + "</h1>" + jsonHTML;
// numDone += 1;
// pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 3))).toString() + "%";
// <a href="/workflows/processes/5d0a73db7b86eb6fe20f6092/5d1a18b97b86eb0a7532e0f9/5d1a18b97b86eb39037d417d/get-file?id=5d372340a814e42a0d1872e1">
}
html2pdf()
.set({
html2canvas: { scale: 2 },
pagebreak: {
before: ".page-break",
avoid: ["div", "h1", ".form-section-offs"]
}
})
.from('<div style="padding:100px;">' + allHTML.join(" ") + "</div>")
.output("datauristring")
.then(async function(pdfAsString) {
//pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 2))).toString() + "%";
const htmlPdf = await PDFLib.PDFDocument.load(pdfAsString);
//pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1.5))).toString() + "%";
const numPages = bigPdf.getPages().length;
const copiedPages = await htmlPdf.copyPages(
bigPdf,
Array.from(Array(numPages).keys())
);
//pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1.2))).toString() + "%";
copiedPages.forEach(page => {
htmlPdf.addPage(page);
});
//pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1))).toString() + "%";
//const pdfUrl = URL.createObjectURL(
// new Blob([await htmlPdf.save()], { type: 'application/pdf' }),
//);
saveAs(
new Blob([await htmlPdf.save()]),
studentName + " - " + processName + ".pdf"
);
//pdfButtonProgress.innerHTML = parseInt(100 * (numDone / totalTasks)).toString() + "%";
iconElement.classList.remove("lds-circle");
//pdfButtonText.innerHTML = "Saved";
//pdfButtonProgress.innerHTML = "";
//htmlPdf.save();
});
}
//saveAs(await htmlPdf.save(), "Form.pdf");
//window.open(pdfUrl, '_blank');
//htmlPdf.save();
// });
// console.log(allHTML);
}
(async function() {
"use strict";
var newStyle = document.createElement("style");
newStyle.innerHTML = `
.pill {
background-color: #fff;
padding: .5em;
border-radius: 5px;
display: inline-block;
cursor: default;
margin-top: 1em;
font-size: 8pt;
}
.pure-button-pdf { color: #eb6841; background: #fff; padding: 0.1em;}
.pdfIcon { margin-left:2px; margin-right:2px;}
.lds-circle { display: inline-block; transform: translateZ(1px); }
.lds-circle { display: inline-block; animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; }
@keyframes lds-circle { 0%, 100% { animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5); } 0% { transform: rotateY(0deg); } 50% { transform: rotateY(1800deg); animation-timing-function: cubic-bezier(0, 0.5, 0.5, 1); } 100% { transform: rotateY(3600deg); } }
`;
document.getElementsByTagName("head")[0].append(newStyle);
var showFormsButton = document.createElement("button");
showFormsButton.onclick = clickAllImageButtons;
showFormsButton.innerHTML =
'<i class="far fa-images"></i> Show Thumbnails for All Uploads';
showFormsButton.classList.add("pure-button");
document.getElementById("page-header").append(showFormsButton);
var allPDFsButton = document.createElement("button");
allPDFsButton.onclick = downloadAllPDFs;
allPDFsButton.innerHTML =
'<i class="far fa-file-pdf"></i> Download all Completed as PDFs';
allPDFsButton.classList.add("pure-button");
document.getElementById("page-header").append(allPDFsButton);
doIncomplete();
doCompletePDFButtons();
})();