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.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 = '//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.  
  157.  
  158. };
  159. filesDone += 1;
  160. if (filesDone === numFiles ) {
  161. iconElement.classList.remove('lds-circle');
  162. }
  163.  
  164. });
  165. }
  166. }
  167.  
  168.  
  169.  
  170.  
  171. });
  172. }
  173. }).then(function() {
  174.  
  175. });
  176. }
  177. }
  178.  
  179. function doIncomplete() {
  180. var xpath = "//tr[td[text()='Active (in progress)']]/td[2]/a";
  181. var result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
  182. var node, nodes = [];
  183. while (node = result.iterateNext()) {
  184. nodes.push(node);
  185. //console.log(node.href);
  186. fetchAndUpdate(node);
  187. }
  188. }
  189.  
  190. function doComplete() {
  191. var node;
  192. for (node of document.getElementsByClassName("image-icon-button") ) {
  193. node.click();
  194. }
  195. }
  196.  
  197. function doCompletePDFButtons() {
  198. var xpath = "//tr[td[text()='Active (complete)' or 'Complete']]/td[2]/a";
  199. var result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
  200. //var result = document.evaluate(xpath,document.cloneNode(true))
  201. var node, nodes = [];
  202. while (node = result.iterateNext()) {
  203. nodes.push(node);
  204. //console.log(node.href);
  205.  
  206. }
  207. for (node of nodes) {
  208.  
  209. generatePDF(node);
  210. fetchHealthForm(node);
  211. }
  212. }
  213.  
  214.  
  215. function generatePDF(node) {
  216. var updateNode = node;
  217. var pdfIcon = document.createElement('div');
  218. pdfIcon.classList.add('pure-button', 'pure-button-pdf', 'pure-button-large', 'pdf-icon-button');
  219. var iconElement = document.createElement('i');
  220. iconElement.classList.add('fas','fa-file-pdf','fa-1x', 'pdfIcon');
  221. pdfIcon.append(iconElement);
  222. var studentName, processName = '';
  223.  
  224. updateNode.parentElement.parentElement.children[0].append(pdfIcon);
  225.  
  226. pdfIcon.onclick = async function() {
  227.  
  228. iconElement.classList.add('lds-circle');
  229. const bigPdf = await PDFLib.PDFDocument.create();
  230. var allHTML = [];
  231.  
  232. fetch(node.href).then(function(response) { return response.text(); }).then(async function(body) {
  233. //console.log(body);
  234. var parser = new DOMParser();
  235. var doc = parser.parseFromString(body, "text/html");
  236. var xpath = "//li[contains(@class,'task')]";
  237. var result = document.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null);
  238. studentName = doc.getElementsByClassName("fn")[0].innerText;
  239. processName = doc.getElementById("page-header").innerText;
  240.  
  241.  
  242. var numDone = 0;
  243. var task;
  244. while (task = result.iterateNext()) {
  245. var taskUri = task.dataset.href;
  246. var formUri = taskUri.replace("task-details","form");
  247. let headers = new Headers({
  248. "Accept" : "application/json",
  249. "Content-Type" : "application/json",
  250. "X-Requested-With": "XMLHttpRequest"
  251. });
  252.  
  253. var taskNum = parseInt(taskUri.match(/task=([0-9]+)/)[1]);
  254. console.log(taskNum);
  255. await fetch(formUri, {method: "GET", headers: headers})
  256. .then(function(response) {
  257. return response.json();
  258. })
  259. .then(function(myJson) {
  260. console.log(myJson);
  261. var jsonHTML = myJson.Message.html;
  262. jsonHTML = jsonHTML.replace(/form-section/g,"form-section-off");
  263. jsonHTML = jsonHTML.replace(/<ul class/g,"<ul style=\"display:none;\" class");
  264. console.log(jsonHTML);
  265. //var files = jsonHTML.match(/<a href="(\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*)">/g);
  266. var files = jsonHTML.match(/\/workflows\/processes\/.*\/get-file\?id=[a-zA-z0-9]*/g);
  267. if (files) {
  268.  
  269. for (var file of files) {
  270. fetch(file).then(function(response) {
  271.  
  272. return response.blob(); }
  273. ).then(async function(blob) {
  274. console.log(blob.type);
  275. let reader = new FileReader();
  276. reader.readAsArrayBuffer(blob);
  277. reader.onload = async function() {
  278.  
  279. //blob.arrayBuffer().then(async function(myBuffer){
  280. if (blob.type === "application/pdf") {
  281. const pdf = await PDFLib.PDFDocument.load(reader.result);
  282. console.log(pdf);
  283. const numPages = pdf.getPages().length;
  284.  
  285. const copiedPages = await bigPdf.copyPages(pdf, Array.from(Array(numPages).keys()));
  286. copiedPages.forEach((page) => {
  287. bigPdf.addPage(page);
  288. });
  289. }
  290. else if (blob.type === "image/jpeg") {
  291.  
  292. // Embed the JPG image bytes and PNG image bytes
  293. const jpgImage = await bigPdf.embedJpg(reader.result)
  294.  
  295. // Get the width/height of the JPG image scaled down to 25% of its original size
  296. const jpgDims = jpgImage.scale(.5)
  297.  
  298. // Add a blank page to the document
  299. const page = bigPdf.addPage([jpgDims.width, jpgDims.height])
  300.  
  301. // Draw the JPG image in the center of the page
  302. page.drawImage(jpgImage, {
  303. x: page.getWidth() / 2 - jpgDims.width / 2,
  304. y: page.getHeight() / 2 - jpgDims.height / 2,
  305. width: jpgDims.width,
  306. height: jpgDims.height,
  307. });
  308.  
  309. }
  310. else if (blob.type === "image/png") {
  311.  
  312. // Embed the JPG image bytes and PNG image bytes
  313. const jpgImage = await bigPdf.embedPng(reader.result)
  314.  
  315. // Get the width/height of the JPG image scaled down to 25% of its original size
  316. const jpgDims = jpgImage.scale(.5)
  317.  
  318. // Add a blank page to the document
  319. const page = bigPdf.addPage([jpgDims.width, jpgDims.height])
  320.  
  321. // Draw the JPG image in the center of the page
  322. page.drawImage(jpgImage, {
  323. x: page.getWidth() / 2 - jpgDims.width / 2,
  324. y: page.getHeight() / 2 - jpgDims.height / 2,
  325. width: jpgDims.width,
  326. height: jpgDims.height,
  327. });
  328.  
  329. }
  330. //bigPdf.addPage(pdf);
  331.  
  332. // console.log(pdf);
  333.  
  334. //var objectURL = URL.createObjectURL(myBlob);
  335. //let reader = new FileReader();
  336. //reader.readAsDataURL(myBlob);
  337. //reader.onload = function() {
  338. // console.log(myBlob);
  339. //allHTML += "<embed src=\""+reader.result+"\" width=\"850\" height=\"1100\" class=\"page-break\" type=\"application/pdf\">";
  340. //numDone += 1;
  341. // pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 3) )).toString() + "%";
  342.  
  343. //};
  344.  
  345. };
  346.  
  347. });
  348. }
  349. }
  350. console.log(files);
  351. var jsonHeader = myJson.Message.header;
  352. allHTML[taskNum] = "<h1>"+jsonHeader+"</h1>"+jsonHTML;
  353. // numDone += 1;
  354. // pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 3))).toString() + "%";
  355.  
  356. // <a href="/workflows/processes/5d0a73db7b86eb6fe20f6092/5d1a18b97b86eb0a7532e0f9/5d1a18b97b86eb39037d417d/get-file?id=5d372340a814e42a0d1872e1">
  357.  
  358.  
  359.  
  360. });
  361. }
  362.  
  363. }).then(function() {
  364.  
  365. html2pdf().set({
  366. html2canvas: { scale: 2 },
  367. pagebreak: { before: '.page-break', avoid: ['div','h1','.form-section-offs'] }
  368. }).from("<div style=\"padding:100px;\">"+allHTML.join(" ")+"</div>").output('datauristring').then(async function (pdfAsString) {
  369. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 2))).toString() + "%";
  370. const htmlPdf = await PDFLib.PDFDocument.load(pdfAsString);
  371. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1.5))).toString() + "%";
  372. const numPages = bigPdf.getPages().length;
  373. const copiedPages = await htmlPdf.copyPages(bigPdf, Array.from(Array(numPages).keys()));
  374. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1.2))).toString() + "%";
  375. copiedPages.forEach((page) => {
  376. htmlPdf.addPage(page);
  377. });
  378. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / (totalTasks + 1))).toString() + "%";
  379. //const pdfUrl = URL.createObjectURL(
  380. // new Blob([await htmlPdf.save()], { type: 'application/pdf' }),
  381. //);
  382.  
  383. saveAs(new Blob([await htmlPdf.save()]), studentName+" - "+processName+".pdf");
  384. //pdfButtonProgress.innerHTML = parseInt(100 * (numDone / totalTasks)).toString() + "%";
  385. iconElement.classList.remove('lds-circle');
  386. //pdfButtonText.innerHTML = "Saved";
  387. //pdfButtonProgress.innerHTML = "";
  388. //htmlPdf.save();
  389. });
  390. //saveAs(await htmlPdf.save(), "Form.pdf");
  391. //window.open(pdfUrl, '_blank');
  392. //htmlPdf.save();
  393. // });
  394. // console.log(allHTML);
  395.  
  396. });
  397. }
  398. }
  399.  
  400.  
  401.  
  402.  
  403. (async function() {
  404. 'use strict';
  405. var newStyle = document.createElement('style');
  406. newStyle.innerHTML = `
  407. .pill {
  408. background-color: #fff;
  409. padding: .5em;
  410. border-radius: 5px;
  411. display: inline-block;
  412. cursor: default;
  413. margin-top: 1em;
  414. font-size: 8pt;
  415. }
  416. .pure-button-pdf { color: #eb6841; background: #fff; padding: 0.1em;}
  417. .pdfIcon { margin-left:2px; margin-right:2px;}
  418. .lds-circle { display: inline-block; transform: translateZ(1px); }
  419. .lds-circle { display: inline-block; animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; }
  420. @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); } }
  421. `;
  422. document.getElementsByTagName('head')[0].append(newStyle);
  423.  
  424. var showFormsButton = document.createElement('button');
  425. showFormsButton.onclick = doComplete;
  426. showFormsButton.innerHTML = "Show Thumbnails for All Uploads";
  427. document.getElementById('page-header').append(showFormsButton);
  428.  
  429.  
  430.  
  431.  
  432. doIncomplete();
  433. doCompletePDFButtons();
  434.  
  435. })();