Almascript - Alma Start Process List Helper

Show what isn't done and display uploaded files.

当前为 2019-08-05 提交的版本,查看 最新版本

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