diff --git a/engine/app/assets/modules/charts.js b/engine/app/assets/modules/charts.js new file mode 100644 index 000000000..11dd05466 --- /dev/null +++ b/engine/app/assets/modules/charts.js @@ -0,0 +1,29 @@ +function renderCharts(animate) { + const charts = document.querySelectorAll('.chart'); + + for (let i = 0; i < charts.length; i++) { + const chartEl = charts[i]; + const chartData = JSON.parse(chartEl.dataset.json); + + const ctx = chartEl.getContext('2d'); + const chart = new Chart(ctx, { + type: 'line', + data: { + labels: chartData.labels, + datasets: chartData.datasets + }, + options: { + animation: animate, + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true + } + } + } + }); + } +} + +export { renderCharts as default }; diff --git a/engine/app/assets/modules/document_ready.js b/engine/app/assets/modules/document_ready.js new file mode 100644 index 000000000..2e0cd010f --- /dev/null +++ b/engine/app/assets/modules/document_ready.js @@ -0,0 +1,7 @@ +export default function documentReady(callback) { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +} diff --git a/engine/app/assets/modules/poller.js b/engine/app/assets/modules/poller.js new file mode 100644 index 000000000..9cbe667a8 --- /dev/null +++ b/engine/app/assets/modules/poller.js @@ -0,0 +1,94 @@ +/*jshint esversion: 6, strict: false */ +import renderCharts from "charts"; + +// NOTE: this file is a bit disorganized. Please do not use it as a template for how to organize a JS module. + +const DEFAULT_POLL_INTERVAL_SECONDS = 30; +const MINIMUM_POLL_INTERVAL = 1000; + +function getStorage(key) { + const value = localStorage.getItem('good_job-' + key); + + if (value === 'true') { + return true; + } else if (value === 'false') { + return false; + } else { + return value; + } +} + +function setStorage(key, value) { + localStorage.setItem('good_job-' + key, value); +} + +function updatePageContent(newContent) { + const domParser = new DOMParser(); + const parsedDOM = domParser.parseFromString(newContent, "text/html"); + + const newElements = parsedDOM.querySelectorAll('[data-gj-poll-replace]'); + + for (let i = 0; i < newElements.length; i++) { + const newEl = newElements[i]; + const oldEl = document.getElementById(newEl.id); + + if (oldEl) { + oldEl.replaceWith(newEl); + } + } + + renderCharts(false); +} + +function refreshPage() { + fetch(window.location.href) + .then(resp => resp.text()) + .then(updatePageContent); +} + +const Poller = { + start: () => { + Poller.updateSettings(); + Poller.pollUpdates(); + + const checkbox = document.querySelector('input[name="toggle-poll"]'); + checkbox.addEventListener('change', Poller.togglePoll) + }, + + togglePoll: (event) => { + console.log("toggle"); + Poller.pollEnabled = event.currentTarget.checked; + setStorage('pollEnabled', Poller.pollEnabled); + }, + + updateSettings: () => { + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + + if (urlParams.has('poll')) { + const parsedInterval = (parseInt(urlParams.get('poll')) || DEFAULT_POLL_INTERVAL_SECONDS) * 1000; + Poller.pollInterval = Math.max(parsedInterval, MINIMUM_POLL_INTERVAL); + setStorage('pollInterval', Poller.pollInterval); + + Poller.pollEnabled = true; + } else { + Poller.pollInterval = getStorage('pollInterval') || (DEFAULT_POLL_INTERVAL_SECONDS * 1000); + Poller.pollEnabled = getStorage('pollEnabled') || false; + } + + document.getElementById('toggle-poll').checked = Poller.pollEnabled; + }, + + pollUpdates: () => { + setTimeout(() => { + if (Poller.pollEnabled === true) { + refreshPage(); + Poller.pollUpdates(); + } else { + Poller.pollUpdates(); + } + }, Poller.pollInterval); + }, +}; + +export { Poller as default }; diff --git a/engine/app/assets/scripts.js b/engine/app/assets/scripts.js index 9818164dd..74abfb7de 100644 --- a/engine/app/assets/scripts.js +++ b/engine/app/assets/scripts.js @@ -1,133 +1,11 @@ /*jshint esversion: 6, strict: false */ -const GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS = 30; -const GOOD_JOB_MINIMUM_POLL_INTERVAL = 1000; -const GoodJob = { - // Register functions to execute when the DOM is ready - ready: (callback) => { - if (document.readyState !== "loading") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } - }, +import renderCharts from "charts"; +import documentReady from "document_ready" +import Poller from "poller"; +import start from "./modules/poller"; - init: () => { - GoodJob.updateSettings(); - GoodJob.addListeners(); - GoodJob.pollUpdates(); - GoodJob.renderCharts(true); - }, - - addListeners: () => { - const gjActionEls = document.querySelectorAll('[data-gj-action]'); - - for (let i = 0; i < gjActionEls.length; i++) { - const el = gjActionEls[i]; - const [eventName, func] = el.dataset.gjAction.split('#'); - - el.addEventListener(eventName, GoodJob[func]); - } - }, - - updateSettings: () => { - const queryString = window.location.search; - const urlParams = new URLSearchParams(queryString); - - // live poll interval and enablement - if (urlParams.has('poll')) { - const parsedInterval = (parseInt(urlParams.get('poll')) || GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS) * 1000; - GoodJob.pollInterval = Math.max(parsedInterval, GOOD_JOB_MINIMUM_POLL_INTERVAL); - GoodJob.setStorage('pollInterval', GoodJob.pollInterval); - - GoodJob.pollEnabled = true; - } else { - GoodJob.pollInterval = GoodJob.getStorage('pollInterval') || (GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS * 1000); - GoodJob.pollEnabled = GoodJob.getStorage('pollEnabled') || false; - } - - document.getElementById('toggle-poll').checked = GoodJob.pollEnabled; - }, - - togglePoll: (ev) => { - GoodJob.pollEnabled = ev.currentTarget.checked; - GoodJob.setStorage('pollEnabled', GoodJob.pollEnabled); - }, - - pollUpdates: () => { - setTimeout(() => { - if (GoodJob.pollEnabled === true) { - fetch(window.location.href) - .then(resp => resp.text()) - .then(GoodJob.updateContent) - .finally(GoodJob.pollUpdates); - } else { - GoodJob.pollUpdates(); - } - }, GoodJob.pollInterval); - }, - - updateContent: (newContent) => { - const domParser = new DOMParser(); - const parsedDOM = domParser.parseFromString(newContent, "text/html"); - - const newElements = parsedDOM.querySelectorAll('[data-gj-poll-replace]'); - - for (let i = 0; i < newElements.length; i++) { - const newEl = newElements[i]; - const oldEl = document.getElementById(newEl.id); - - if (oldEl) { - oldEl.replaceWith(newEl); - } - } - - GoodJob.renderCharts(false); - }, - - renderCharts: (animate) => { - const charts = document.querySelectorAll('.chart'); - - for (let i = 0; i < charts.length; i++) { - const chartEl = charts[i]; - const chartData = JSON.parse(chartEl.dataset.json); - - const ctx = chartEl.getContext('2d'); - const chart = new Chart(ctx, { - type: 'line', - data: { - labels: chartData.labels, - datasets: chartData.datasets - }, - options: { - animation: animate, - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - beginAtZero: true - } - } - } - }); - } - }, - - getStorage: (key) => { - const value = localStorage.getItem('good_job-' + key); - - if (value === 'true') { - return true; - } else if (value === 'false') { - return false; - } else { - return value; - } - }, - - setStorage: (key, value) => { - localStorage.setItem('good_job-' + key, value); - } -}; - -GoodJob.ready(GoodJob.init); +documentReady(function() { + renderCharts(); + Poller.start(); +}); diff --git a/engine/app/assets/vendor/es_module_shims.js b/engine/app/assets/vendor/es_module_shims.js new file mode 100644 index 000000000..797406b48 --- /dev/null +++ b/engine/app/assets/vendor/es_module_shims.js @@ -0,0 +1 @@ +(function(){const noop=()=>{};const e=document.querySelector("script[type=esms-options]");const t=e?JSON.parse(e.innerHTML):{};Object.assign(t,self.esmsInitOptions||{});let r=!!t.shimMode;const s=globalHook(r&&t.onimport);const a=globalHook(r&&t.resolve);let n=t.fetch?globalHook(t.fetch):fetch;const i=t.meta?globalHook(shimModule&&t.meta):noop;const c=t.skip?new RegExp(t.skip):null;let f=t.nonce;const te=t.mapOverrides;if(!f){const e=document.querySelector("script[nonce]");e&&(f=e.nonce||e.getAttribute("nonce"))}const re=globalHook(t.onerror||noop);const se=t.onpolyfill?globalHook(t.onpolyfill):()=>console.log("%c^^ Module TypeError above is polyfilled and can be ignored ^^","font-weight:900;color:#391");const{revokeBlobURLs:ae,noLoadEventRetriggers:ne,enforceIntegrity:ie}=t;function globalHook(e){return"string"===typeof e?self[e]:e}const oe=Array.isArray(t.polyfillEnable)?t.polyfillEnable:[];const ce=oe.includes("css-modules");const le=oe.includes("json-modules");function setShimMode(){r=true}const fe=!navigator.userAgentData&&!!navigator.userAgent.match(/Edge\/\d+\.\d+/);const ue=document.baseURI;function createBlob(e,t="text/javascript"){return URL.createObjectURL(new Blob([e],{type:t}))}const eoop=e=>setTimeout((()=>{throw e}));const throwError=e=>{(window.reportError||window.safari&&console.error||eoop)(e),void re(e)};function fromParent(e){return e?` imported from ${e}`:""}const de=/\\/g;function isURL(e){if(-1===e.indexOf(":"))return false;try{new URL(e);return true}catch(e){return false}}function resolveUrl(e,t){return resolveIfNotPlainOrUrl(e,t)||(isURL(e)?e:resolveIfNotPlainOrUrl("./"+e,t))}function resolveIfNotPlainOrUrl(e,t){const r=t.indexOf("?",-1===t.indexOf("#")?t.indexOf("#"):t.length);-1!==r&&(t=t.slice(0,r));-1!==e.indexOf("\\")&&(e=e.replace(de,"/"));if("/"===e[0]&&"/"===e[1])return t.slice(0,t.indexOf(":")+1)+e;if("."===e[0]&&("/"===e[1]||"."===e[1]&&("/"===e[2]||2===e.length&&(e+="/"))||1===e.length&&(e+="/"))||"/"===e[0]){const r=t.slice(0,t.indexOf(":")+1);let s;if("/"===t[r.length+1])if("file:"!==r){s=t.slice(r.length+2);s=s.slice(s.indexOf("/")+1)}else s=t.slice(8);else s=t.slice(r.length+("/"===t[r.length]));if("/"===e[0])return t.slice(0,t.length-s.length-1)+e;const a=s.slice(0,s.lastIndexOf("/")+1)+e;const n=[];let i=-1;for(let e=0;e "${e[n]}" does not resolve`)}}let pe;window.addEventListener("error",(e=>pe=e));function dynamicImportScript(e,{errUrl:t=e}={}){pe=void 0;const r=createBlob(`import*as m from'${e}';self._esmsi=m`);const s=Object.assign(document.createElement("script"),{type:"module",src:r});s.setAttribute("nonce",f);s.setAttribute("noshim","");const a=new Promise(((e,a)=>{s.addEventListener("error",cb);s.addEventListener("load",cb);function cb(n){document.head.removeChild(s);if(self._esmsi){e(self._esmsi,ue);self._esmsi=void 0}else{a(!(n instanceof Event)&&n||pe&&pe.error||new Error(`Error loading or executing the graph of ${t} (check the console for ${r}).`));pe=void 0}}}));document.head.appendChild(s);return a}let be=dynamicImportScript;const he=dynamicImportScript(createBlob("export default u=>import(u)")).then((e=>{e&&(be=e.default);return!!e}),noop);let ke=false;let me=false;let we=false;let ge=false;let ve=false;const ye=Promise.resolve(he).then((e=>{if(e){ve=true;return Promise.all([be(createBlob("import.meta")).then((()=>we=true),noop),ce&&be(createBlob('import"data:text/css,{}"assert{type:"css"}')).then((()=>me=true),noop),le&&be(createBlob('import"data:text/json,{}"assert{type:"json"}')).then((()=>ke=true),noop),new Promise((e=>{self._$s=r=>{document.head.removeChild(t);r&&(ge=true);delete self._$s;e()};const t=document.createElement("iframe");t.style.display="none";t.srcdoc=`