Almascript - Alma Start Process List Helper

Show what isn't done and display uploaded files.

  1. // ==UserScript==
  2. // @name Almascript - Alma Start Process List Helper
  3. // @namespace https://greasyfork.org/en/users/8332-sreyemnayr
  4. // @version 2019.8.6.1
  5. // @description Show what isn't done and display uploaded files.
  6. // @author Ryan Meyers
  7. // @match https://*.getalma.com/workflows/processes/*/review
  8. // @require https://greasyfork.org/scripts/388114-pdf-js/code/PDFjs.js?version=721820
  9. // @require https://greasyfork.org/scripts/388210-html2pdf-js/code/html2pdfjs.js?version=722443
  10. // @require https://unpkg.com/pdf-lib@1.0.1/dist/pdf-lib.min.js
  11. // @require https://unpkg.com/file-saver@2.0.2/dist/FileSaver.min.js
  12. // @grant unsafeWindow
  13. // ==/UserScript==
  14.  
  15. // Loaded via <script> tag, create shortcut to access PDF.js exports.
  16. var pdfjsLib = window["pdfjs-dist/build/pdf"];
  17.  
  18. // The workerSrc property shall be specified.
  19. pdfjsLib.GlobalWorkerOptions.workerSrc =
  20. "//greasyfork.org/scripts/388115-pdf-js-worker/code/PDFjs%20Worker.js?version=721821";
  21.  
  22. const readBlobAsArrayBuffer = (blob) => {
  23. const temporaryFileReader = new FileReader();
  24.  
  25. return new Promise((resolve, reject) => {
  26. temporaryFileReader.onerror = () => {
  27. temporaryFileReader.abort();
  28. reject(new DOMException("Problem parsing input file."));
  29. };
  30.  
  31. temporaryFileReader.onload = () => {
  32. resolve(temporaryFileReader.result);
  33. };
  34. temporaryFileReader.readAsArrayBuffer(blob);
  35. });
  36. };
  37.  
  38. function clearBody(body) {
  39. body = body.replace(/src=/g,"data-src=");
  40. body = body.replace(/<link/g, "nolink");
  41. return body;
  42. }
  43.  
  44. function fetchAndUpdate(node) {
  45. const updateNode = node;
  46.  
  47. fetch(node.href)
  48. .then(function(response) {
  49. return response.text();
  50. })
  51. .then(function(body) {
  52. body = clearBody(body);
  53. var parser = new DOMParser();
  54. var doc = parser.parseFromString(body, "text/html");
  55. var xpath =
  56. "//li[contains(@class,'task')][not(contains(@class,'task-complete'))]";
  57. var result = document.evaluate(
  58. xpath,
  59. doc,
  60. null,
  61. XPathResult.ANY_TYPE,
  62. null
  63. );
  64. //console.log(result);
  65. var node,
  66. nodes = [];
  67. while ((node = result.iterateNext())) {
  68. //console.log(node.textContent.trim());
  69. var newNode = document.createElement("div");
  70. newNode.classList.add("pill");
  71.  
  72. newNode.innerHTML =
  73. '<i class="far fa-times-circle" style="color:#eb6841;"></i>' +
  74. node.textContent.trim();
  75.  
  76. updateNode.parentElement.parentElement.children[4].append(newNode);
  77. }
  78. });
  79. }
  80.  
  81. function fetchHealthForm(node) {
  82. var updateNode = node;
  83. var pdfIcon = document.createElement("div");
  84. pdfIcon.classList.add(
  85. "pure-button",
  86. "pure-button-pdf",
  87. "pure-button-large",
  88. "image-icon-button"
  89. );
  90. var iconElement = document.createElement("i");
  91. iconElement.classList.add("far", "fa-images", "fa-1x", "pdfIcon");
  92. pdfIcon.append(iconElement);
  93. var studentName,
  94. processName = "";
  95.  
  96. updateNode.parentElement.parentElement.children[0].append(pdfIcon);
  97.  
  98. pdfIcon.onclick = async function() {
  99. iconElement.classList.add("lds-circle");
  100.  
  101. fetch(node.href)
  102. .then(function(response) {
  103. return response.text();
  104. })
  105. .then(function(body) {
  106. //console.log(body);
  107. body = clearBody(body);
  108. var parser = new DOMParser();
  109. var doc = parser.parseFromString(body, "text/html");
  110. var xpath = "//li[contains(@class,'task')]";
  111. var result = document.evaluate(
  112. xpath,
  113. doc,
  114. null,
  115. XPathResult.ANY_TYPE,
  116. null
  117. );
  118. //console.log(result);
  119. var node,
  120. nodes = [];
  121. var numFiles = 0;
  122. var filesDone = 0;
  123. while ((node = result.iterateNext())) {
  124. var taskUri = node.dataset.href;
  125. var formUri = taskUri.replace("task-details", "form");
  126. let headers = new Headers({
  127. Accept: "application/json",
  128. "Content-Type": "application/json",
  129. "X-Requested-With": "XMLHttpRequest"
  130. });
  131.  
  132. fetch(formUri, { method: "GET", headers: headers })
  133. .then(function(response) {
  134. return response.json();
  135. })
  136. .then(function(myJson) {
  137. //console.log(myJson);
  138. var jsonHTML = myJson.Message.html;
  139. jsonHTML = jsonHTML.replace(/form-section/g, "form-section-off");
  140. jsonHTML = jsonHTML.replace(
  141. /<ul class/g,
  142. '<ul style="display:none;" class'
  143. );
  144. //console.log(jsonHTML);
  145. //var files = jsonHTML.match(/<a href="(\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*)">/g);
  146. var files = jsonHTML.match(
  147. /\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*/g
  148. );
  149. if (files) {
  150. numFiles += files.length;
  151. iconElement.classList.add("lds-circle");
  152. for (var file of files) {
  153. fetch(file)
  154. .then(function(response) {
  155. return response.blob();
  156. })
  157. .then(async function(blob) {
  158. console.log(blob.type);
  159. let reader = new FileReader();
  160. reader.readAsArrayBuffer(blob);
  161. reader.onload = async function() {
  162. var newImg;
  163.  
  164. //blob.arrayBuffer().then(async function(myBuffer){
  165. if (blob.type === "application/pdf") {
  166. newImg = document.createElement("canvas");
  167.  
  168. var loadingTask = pdfjsLib.getDocument(file);
  169. loadingTask.promise.then(
  170. function(pdf) {
  171. console.log("PDF loaded");
  172.  
  173. // Fetch the first page
  174. var pageNumber = 1;
  175. pdf.getPage(pageNumber).then(function(page) {
  176. console.log("Page loaded");
  177.  
  178. var scale = 0.25;
  179. var viewport = page.getViewport(scale);
  180.  
  181. // Prepare canvas using PDF page dimensions
  182. var canvas = newImg;
  183. var context = canvas.getContext("2d");
  184. canvas.height = 230;
  185. canvas.width = 160;
  186.  
  187. // Render PDF page into canvas context
  188. var renderContext = {
  189. canvasContext: context,
  190. viewport: viewport
  191. };
  192. var renderTask = page.render(renderContext);
  193. renderTask.promise.then(function() {
  194. console.log("Page rendered");
  195. });
  196. });
  197. },
  198. function(reason) {
  199. // PDF loading error
  200. console.error(reason);
  201. }
  202. );
  203.  
  204. //newImg = document.createElement('a');
  205. //newImg.href = file;
  206. //newImg.innerHTML = "Download";
  207. updateNode.append(newImg);
  208. } else {
  209. newImg = document.createElement("img");
  210. newImg.src = file;
  211. newImg.width = 160;
  212. updateNode.append(newImg);
  213. }
  214. filesDone += 1;
  215. if (filesDone === numFiles) {
  216. iconElement.classList.remove("lds-circle");
  217. }
  218. };
  219. });
  220. }
  221. }
  222. });
  223. }
  224. })
  225. .then(function() {});
  226. };
  227. }
  228.  
  229. function doIncomplete() {
  230. var xpath = "//tr[td[text()='Active (in progress)']]/td[2]/a";
  231. var result = document.evaluate(
  232. xpath,
  233. document,
  234. null,
  235. XPathResult.ANY_TYPE,
  236. null
  237. );
  238. var node,
  239. nodes = [];
  240. while ((node = result.iterateNext())) {
  241. nodes.push(node);
  242. //console.log(node.href);
  243. fetchAndUpdate(node);
  244. }
  245. }
  246.  
  247. function clickAllImageButtons() {
  248. var node;
  249. for (node of document.getElementsByClassName("image-icon-button")) {
  250. node.click();
  251. }
  252. }
  253.  
  254. function downloadAllPDFs() {
  255. var node;
  256. for (node of document.getElementsByClassName("pdf-icon-button")) {
  257. node.click();
  258. }
  259. }
  260.  
  261. function doCompletePDFButtons() {
  262. var xpath =
  263. "//tr[td[text()='Active (complete)' or text()='Complete']]/td[2]/a";
  264. var result = document.evaluate(
  265. xpath,
  266. document,
  267. null,
  268. XPathResult.ANY_TYPE,
  269. null
  270. );
  271. //var result = document.evaluate(xpath,document.cloneNode(true))
  272. var node,
  273. nodes = [];
  274. while ((node = result.iterateNext())) {
  275. nodes.push(node);
  276. //console.log(node.href);
  277. }
  278. for (node of nodes) {
  279. generatePDF(node);
  280. generatePDF(node, true);
  281. fetchHealthForm(node);
  282. }
  283. }
  284.  
  285. function generatePDF(node, justHealth = false) {
  286. const onlyHealth = justHealth;
  287. var updateNode = node;
  288. var pdfIcon = document.createElement("div");
  289. var iconElement = document.createElement("i");
  290. if (onlyHealth) {
  291. pdfIcon.classList.add(
  292. "pure-button",
  293. "pure-button-pdf",
  294. "pure-button-large",
  295. "health-icon-button"
  296. );
  297. iconElement.classList.add("fas", "fa-notes-medical", "fa-1x", "pdfIcon");
  298. } else {
  299. pdfIcon.classList.add(
  300. "pure-button",
  301. "pure-button-pdf",
  302. "pure-button-large",
  303. "pdf-icon-button"
  304. );
  305. iconElement.classList.add("fas", "fa-file-pdf", "fa-1x", "pdfIcon");
  306. }
  307.  
  308. pdfIcon.append(iconElement);
  309. var studentName,
  310. processName = "";
  311.  
  312. updateNode.parentElement.parentElement.children[0].append(pdfIcon);
  313.  
  314. pdfIcon.onclick = async function() {
  315. iconElement.classList.add("lds-circle");
  316. const bigPdf = await PDFLib.PDFDocument.create();
  317. var allHTML = [];
  318.  
  319. const bigFetch = await fetch(node.href);
  320. var body = await bigFetch.text();
  321. body = clearBody(body);
  322. //console.log(body);
  323. var parser = new DOMParser();
  324. var doc = parser.parseFromString(body, "text/html");
  325. var xpath;
  326.  
  327. studentName = doc.getElementsByClassName("fn")[0].innerText.trim();
  328. if (onlyHealth) {
  329. processName =
  330. "HEALTH " + doc.getElementById("page-header").innerText.trim();
  331. xpath =
  332. "//li[contains(@class,'task')][div/h5[contains(text(),'Health' ) or contains(text(), 'Medi')]]";
  333. } else {
  334. processName = doc.getElementById("page-header").innerText.trim();
  335. xpath = "//li[contains(@class,'task')]";
  336. }
  337.  
  338. var result = document.evaluate(
  339. xpath,
  340. doc,
  341. null,
  342. XPathResult.ANY_TYPE,
  343. null
  344. );
  345.  
  346. var numDone = 0;
  347. var task;
  348. while ((task = result.iterateNext())) {
  349. var taskUri = task.dataset.href;
  350. var formUri = taskUri.replace("task-details", "form");
  351. let headers = new Headers({
  352. Accept: "application/json",
  353. "Content-Type": "application/json",
  354. "X-Requested-With": "XMLHttpRequest"
  355. });
  356.  
  357. var taskNum = parseInt(taskUri.match(/task=([0-9]+)/)[1]);
  358. console.log(taskNum);
  359. var fetchResponse = await fetch(formUri, { method: "GET", headers: headers });
  360. var myJson = await fetchResponse.json();
  361. console.log(myJson);
  362. var jsonHTML = myJson.Message.html;
  363. jsonHTML = jsonHTML.replace(/form-section/g, "form-section-off");
  364. jsonHTML = jsonHTML.replace(
  365. /<ul class/g,
  366. '<ul style="display:none;" class'
  367. );
  368. console.log(jsonHTML);
  369. //var files = jsonHTML.match(/<a href="(\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*)">/g);
  370. var files = jsonHTML.match(
  371. /\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*/g
  372. );
  373. if (files) {
  374. for (var file of files) {
  375. var fileResponse = await fetch(file);
  376. var blob = await fileResponse.blob();
  377. console.log(blob.type);
  378. const blobArrayBuffer = await readBlobAsArrayBuffer(blob);
  379. //blob.arrayBuffer().then(async function(myBuffer){
  380. if (blob.type === "application/pdf") {
  381. const pdf = await PDFLib.PDFDocument.load(
  382. blobArrayBuffer
  383. );
  384. console.log(pdf);
  385. const numPages = pdf.getPages().length;
  386.  
  387. const copiedPages = await bigPdf.copyPages(
  388. pdf,
  389. Array.from(Array(numPages).keys())
  390. );
  391. copiedPages.forEach(page => {
  392. bigPdf.addPage(page);
  393. });
  394. } else if (blob.type === "image/jpeg") {
  395. // Embed the JPG image bytes and PNG image bytes
  396. const jpgImage = await bigPdf.embedJpg(blobArrayBuffer);
  397.  
  398. // Get the width/height of the JPG image scaled down to 25% of its original size
  399. const jpgDims = jpgImage.scale(0.5);
  400.  
  401. // Add a blank page to the document
  402. const page = bigPdf.addPage([
  403. jpgDims.width,
  404. jpgDims.height
  405. ]);
  406.  
  407. // Draw the JPG image in the center of the page
  408. page.drawImage(jpgImage, {
  409. x: page.getWidth() / 2 - jpgDims.width / 2,
  410. y: page.getHeight() / 2 - jpgDims.height / 2,
  411. width: jpgDims.width,
  412. height: jpgDims.height
  413. });
  414. } else if (blob.type === "image/png") {
  415. // Embed the JPG image bytes and PNG image bytes
  416. const jpgImage = await bigPdf.embedPng(blobArrayBuffer);
  417.  
  418. // Get the width/height of the JPG image scaled down to 25% of its original size
  419. const jpgDims = jpgImage.scale(0.5);
  420.  
  421. // Add a blank page to the document
  422. const page = bigPdf.addPage([
  423. jpgDims.width,
  424. jpgDims.height
  425. ]);
  426.  
  427. // Draw the JPG image in the center of the page
  428. page.drawImage(jpgImage, {
  429. x: page.getWidth() / 2 - jpgDims.width / 2,
  430. y: page.getHeight() / 2 - jpgDims.height / 2,
  431. width: jpgDims.width,
  432. height: jpgDims.height
  433. });
  434. }
  435. //bigPdf.addPage(pdf);
  436.  
  437. // console.log(pdf);
  438.  
  439. //var objectURL = URL.createObjectURL(myBlob);
  440. //let reader = new FileReader();
  441. //reader.readAsDataURL(myBlob);
  442. //reader.onload = function() {
  443. // console.log(myBlob);
  444. //allHTML += "<embed src=\""+reader.result+"\" width=\"850\" height=\"1100\" class=\"page-break\" type=\"application/pdf\">";
  445. //numDone += 1;
  446. // pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 3) )).toString() + "%";
  447.  
  448. //};
  449. }
  450. }
  451. console.log(files);
  452. var jsonHeader = myJson.Message.header;
  453. allHTML[taskNum] = "<h1>" + jsonHeader + "</h1>" + jsonHTML;
  454. // numDone += 1;
  455. // pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 3))).toString() + "%";
  456.  
  457. // <a href="/workflows/processes/5d0a73db7b86eb6fe20f6092/5d1a18b97b86eb0a7532e0f9/5d1a18b97b86eb39037d417d/get-file?id=5d372340a814e42a0d1872e1">
  458. }
  459.  
  460. html2pdf()
  461. .set({
  462. html2canvas: { scale: 2 },
  463. pagebreak: {
  464. before: ".page-break",
  465. avoid: ["div", "h1", ".form-section-offs"]
  466. }
  467. })
  468. .from('<div style="padding:100px;">' + allHTML.join(" ") + "</div>")
  469. .output("datauristring")
  470. .then(async function(pdfAsString) {
  471. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 2))).toString() + "%";
  472. const htmlPdf = await PDFLib.PDFDocument.load(pdfAsString);
  473. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1.5))).toString() + "%";
  474. const numPages = bigPdf.getPages().length;
  475. const copiedPages = await htmlPdf.copyPages(
  476. bigPdf,
  477. Array.from(Array(numPages).keys())
  478. );
  479. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1.2))).toString() + "%";
  480. copiedPages.forEach(page => {
  481. htmlPdf.addPage(page);
  482. });
  483. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1))).toString() + "%";
  484. //const pdfUrl = URL.createObjectURL(
  485. // new Blob([await htmlPdf.save()], { type: 'application/pdf' }),
  486. //);
  487.  
  488. saveAs(
  489. new Blob([await htmlPdf.save()]),
  490. studentName + " - " + processName + ".pdf"
  491. );
  492. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / totalTasks)).toString() + "%";
  493. iconElement.classList.remove("lds-circle");
  494. //pdfButtonText.innerHTML = "Saved";
  495. //pdfButtonProgress.innerHTML = "";
  496. //htmlPdf.save();
  497. });
  498.  
  499. }
  500.  
  501. //saveAs(await htmlPdf.save(), "Form.pdf");
  502. //window.open(pdfUrl, '_blank');
  503. //htmlPdf.save();
  504. // });
  505. // console.log(allHTML);
  506. }
  507.  
  508.  
  509. (async function() {
  510. "use strict";
  511. var newStyle = document.createElement("style");
  512. newStyle.innerHTML = `
  513. .pill {
  514. background-color: #fff;
  515. padding: .5em;
  516. border-radius: 5px;
  517. display: inline-block;
  518. cursor: default;
  519. margin-top: 1em;
  520. font-size: 8pt;
  521. }
  522. .pure-button-pdf { color: #eb6841; background: #fff; padding: 0.1em;}
  523. .pdfIcon { margin-left:2px; margin-right:2px;}
  524. .lds-circle { display: inline-block; transform: translateZ(1px); }
  525. .lds-circle { display: inline-block; animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; }
  526. @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); } }
  527. `;
  528. document.getElementsByTagName("head")[0].append(newStyle);
  529.  
  530. var showFormsButton = document.createElement("button");
  531. showFormsButton.onclick = clickAllImageButtons;
  532. showFormsButton.innerHTML =
  533. '<i class="far fa-images"></i> Show Thumbnails for All Uploads';
  534. showFormsButton.classList.add("pure-button");
  535. document.getElementById("page-header").append(showFormsButton);
  536.  
  537. var allPDFsButton = document.createElement("button");
  538. allPDFsButton.onclick = downloadAllPDFs;
  539. allPDFsButton.innerHTML =
  540. '<i class="far fa-file-pdf"></i> Download all Completed as PDFs';
  541. allPDFsButton.classList.add("pure-button");
  542. document.getElementById("page-header").append(allPDFsButton);
  543.  
  544. doIncomplete();
  545. doCompletePDFButtons();
  546. })();