diff --git a/index.html b/index.html index 2dc586f..7f74e83 100644 --- a/index.html +++ b/index.html @@ -68,6 +68,7 @@

Announcement

+ @@ -236,6 +237,14 @@

Announcement

4

+
+ +
+ + + +
+
@@ -345,7 +354,7 @@

- +
@@ -354,8 +363,8 @@

Faisal N - (@faisalnjs) - Commits after 5/21/2024 including Set, Matrix, and FRQ input modes, make-up mode, banner - images, authentication, syncing

+ (@faisalnjs) - Commits after 5/21/2024 including Set, Matrix, and FRQ input modes, make-up mode, Live + Drawings, banner images, authentication, syncing

khui0 - Original design and development, logo image

=6.0" @@ -1322,6 +1329,28 @@ "node": ">= 0.4" } }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2335,10 +2364,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", @@ -2670,6 +2699,34 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2946,6 +3003,35 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -3426,6 +3512,11 @@ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "optional": true }, + "@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3641,12 +3732,11 @@ } }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "decimal.js": { @@ -3693,6 +3783,23 @@ "gopd": "^1.2.0" } }, + "engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" + }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -4344,10 +4451,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "nanoid": { "version": "3.3.11", @@ -4548,6 +4654,26 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + } + }, + "socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4715,6 +4841,17 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "requires": {} + }, + "xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index a5e4d73..580998e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "virtual-clicker", "private": true, - "version": "5.0.0", + "version": "5.2.0", "type": "module", "scripts": { "dev": "vite --host", @@ -25,6 +25,7 @@ "dependencies": { "bootstrap-icons": "^1.11.3", "mathlive": "^0.104.0", - "vite-plugin-package-version": "^1.1.0" + "vite-plugin-package-version": "^1.1.0", + "socket.io-client": "^4.7.2" } } diff --git a/public/store/backdrop/rocket-league-2.png b/public/store/backdrop/rocket-league-2.png new file mode 100644 index 0000000..62d366f Binary files /dev/null and b/public/store/backdrop/rocket-league-2.png differ diff --git a/public/store/backdrop/rocket-league-ice.png b/public/store/backdrop/rocket-league-ice.png new file mode 100644 index 0000000..bb41ae5 Binary files /dev/null and b/public/store/backdrop/rocket-league-ice.png differ diff --git a/public/store/backdrop/rocket-league-synthwave.png b/public/store/backdrop/rocket-league-synthwave.png new file mode 100644 index 0000000..e9b544d Binary files /dev/null and b/public/store/backdrop/rocket-league-synthwave.png differ diff --git a/public/store/backdrop/rocket-league.png b/public/store/backdrop/rocket-league.png new file mode 100644 index 0000000..0e95329 Binary files /dev/null and b/public/store/backdrop/rocket-league.png differ diff --git a/public/store/thumb/rocket-league-2.png b/public/store/thumb/rocket-league-2.png new file mode 100644 index 0000000..3a817ac Binary files /dev/null and b/public/store/thumb/rocket-league-2.png differ diff --git a/public/store/thumb/rocket-league-ice.png b/public/store/thumb/rocket-league-ice.png new file mode 100644 index 0000000..5db0747 Binary files /dev/null and b/public/store/thumb/rocket-league-ice.png differ diff --git a/public/store/thumb/rocket-league-synthwave.png b/public/store/thumb/rocket-league-synthwave.png new file mode 100644 index 0000000..b708ca1 Binary files /dev/null and b/public/store/thumb/rocket-league-synthwave.png differ diff --git a/public/store/thumb/rocket-league.png b/public/store/thumb/rocket-league.png new file mode 100644 index 0000000..56655fa Binary files /dev/null and b/public/store/thumb/rocket-league.png differ diff --git a/src/clicker/clicker.js b/src/clicker/clicker.js index 6129528..720d825 100644 --- a/src/clicker/clicker.js +++ b/src/clicker/clicker.js @@ -10,6 +10,7 @@ import { unixToTimeString } from "/src/modules/time.js"; import { getExtendedPeriod } from "/src/periods/periods"; import { convertLatexToAsciiMath, convertLatexToMarkup, renderMathInElement } from "mathlive"; import extendedSchedule from "/src/periods/extendedSchedule.json"; +import initDraw from '/src/modules/draw.js'; ``; function safeParseJSON(str) { @@ -30,7 +31,7 @@ function safeParseJSON(str) { } try { - const domain = ((window.location.hostname.search('click') != -1) || (window.location.hostname.search('127') != -1)) ? 'https://api.check.vssfalcons.com' : `http://${document.domain}:5000`; + const domain = ((window.location.hostname.search('click') != -1) || (window.location.hostname.search('127') != -1)) ? `https://${(window.location.hostname.search('beta') != -1) ? 'beta.' : ''}api.check.vssfalcons.com` : `http://${document.domain}:5000`; var period = document.getElementById("period-input").value; const questionInput = document.getElementById("question-input"); const answerInput = document.getElementById("answer-input"); @@ -47,8 +48,8 @@ try { let highestDataElement = null; let restoredSetType = ""; var history = []; - let historyIndex = 0; + var drawLoaded = false; if (!storage.get("makeUpDate")) storage.set("makeUpDate", null); @@ -91,6 +92,7 @@ try { if (document.querySelector('[data-logout]')) document.querySelector('[data-logout]').addEventListener('click', () => auth.logout(init)); // Set default answer mode answerMode("input"); + ui.setButtonSelectValue(document.getElementById("answer-mode-selector"), "input"); document.getElementById("code-input").value = ''; document.querySelectorAll("span.code").forEach((element) => { element.innerHTML = ''; @@ -456,6 +458,7 @@ try { ui.reportBugModal(null, String(error.stack)); } }); + if (window.__drawInstance && typeof window.__drawInstance.destroy === 'function') window.__drawInstance.destroy(); storage.set("code", input); init(); // Close all modals @@ -480,6 +483,7 @@ try { ui.reportBugModal(null, String(error.stack)); } }); + if (window.__drawInstance && typeof window.__drawInstance.destroy === 'function') window.__drawInstance.destroy(); storage.set("code", input); init(); // Update URL parameters with seat code @@ -494,47 +498,52 @@ try { // Update elements with new seat code async function updateCode() { ui.updateTitles(); - try { - if (!(await auth.bulkLoad(["history"], storage.get("code"), storage.get("password")))) return; - await storage.idbReady; - const bulkLoad = (await storage.idbGet('cache')) || storage.get("cache") || {}; - ui.toast(`Welcome back${bulkLoad.name ? `, ${bulkLoad.name}` : ''}!`, 3000, "success", "bi bi-key"); - history = bulkLoad.history || []; - var course = bulkLoad.course || {}; - if (document.querySelector('.alert')) { - var clicker_announcement = JSON.parse(course.clicker_announcement || '{}'); - if ((clicker_announcement.image || clicker_announcement.title || clicker_announcement.content || clicker_announcement.link) && (clicker_announcement.expires ? new Date(`${clicker_announcement.expires}T${extendedSchedule[parseInt(storage.get("code").slice(0, 1))][1]}:00`) > new Date() : true)) { - document.querySelector('.alert').removeAttribute('hidden'); - document.querySelector('.alert').classList = `alert ${clicker_announcement.layout || ''}`; - if (clicker_announcement.image) { - document.querySelector('.alert img').removeAttribute('hidden'); - document.querySelector('.alert img').src = clicker_announcement.image; - } else { - document.querySelector('.alert img').setAttribute('hidden', ''); - } - document.querySelector('.alert h3').innerText = clicker_announcement.title || 'Announcement'; - if (clicker_announcement.content) { - document.querySelector('.alert p').removeAttribute('hidden'); - document.querySelector('.alert p').innerText = clicker_announcement.content; - } else { - document.querySelector('.alert p').setAttribute('hidden', ''); - } - if (clicker_announcement.link) { - document.querySelector('.alert button').removeAttribute('hidden'); - document.querySelector('.alert button').innerHTML = `${clicker_announcement.linkTitle || 'Go'} `; - document.querySelector('.alert button').addEventListener('click', () => { - window.open(clicker_announcement.link, '_blank'); - }); + if (!auth.continueWithoutAPI) { + try { + if (!(await auth.bulkLoad(["course", "history"], storage.get("code"), storage.get("password")))) return; + await storage.idbReady; + const bulkLoad = (await storage.idbGet('cache')) || storage.get("cache") || {}; + ui.toast(`Welcome back${bulkLoad.name ? `, ${bulkLoad.name}` : ''}!`, 3000, "success", "bi bi-key"); + history = bulkLoad.history || []; + var course = bulkLoad.course || {}; + if (document.querySelector('.alert')) { + var clicker_announcement = JSON.parse(course.clicker_announcement || '{}'); + if ((clicker_announcement.image || clicker_announcement.title || clicker_announcement.content || clicker_announcement.link) && (clicker_announcement.expires ? new Date(`${clicker_announcement.expires}T${extendedSchedule[parseInt(storage.get("code").slice(0, 1))][1]}:00`) > new Date() : true)) { + document.querySelector('.alert').removeAttribute('hidden'); + document.querySelector('.alert').classList = `alert ${clicker_announcement.layout || ''}`; + if (clicker_announcement.image) { + document.querySelector('.alert img').removeAttribute('hidden'); + document.querySelector('.alert img').src = clicker_announcement.image; + } else { + document.querySelector('.alert img').setAttribute('hidden', ''); + } + document.querySelector('.alert h3').innerText = clicker_announcement.title || 'Announcement'; + if (clicker_announcement.content) { + document.querySelector('.alert p').removeAttribute('hidden'); + document.querySelector('.alert p').innerText = clicker_announcement.content; + } else { + document.querySelector('.alert p').setAttribute('hidden', ''); + } + if (clicker_announcement.link) { + document.querySelector('.alert button').removeAttribute('hidden'); + document.querySelector('.alert button').innerHTML = `${clicker_announcement.linkTitle || 'Go'} `; + document.querySelector('.alert button').addEventListener('click', () => { + window.open(clicker_announcement.link, '_blank'); + }); + } else { + document.querySelector('.alert button').setAttribute('hidden', ''); + document.querySelector('.alert button').removeEventListener('click', () => { }); + } } else { - document.querySelector('.alert button').setAttribute('hidden', ''); - document.querySelector('.alert button').removeEventListener('click', () => { }); + document.querySelector('.alert').setAttribute('hidden', ''); } - } else { - document.querySelector('.alert').setAttribute('hidden', ''); } + } catch (error) { + console.error(error); } - } catch (error) { - console.error(error); + } else { + document.querySelector('#answer-mode-selector [data-value="draw"]').remove(); + document.querySelector('[data-answer-mode="draw"]').remove(); } // Update history feed try { @@ -591,7 +600,7 @@ try { ]); } // Render Theme Store - themes.renderStore() + themes.renderStore(domain) .catch(error => { if (storage.get("developer")) { alert(`Error @ clicker.js: ${error.message}`); @@ -924,6 +933,7 @@ try { }; } else { answerMode("input"); + ui.setButtonSelectValue(document.getElementById("answer-mode-selector"), "input"); const choice = item.answer.match(/^CHOICE ([A-E])$/); if (!choice) { answerInput.value = item.answer; @@ -1024,6 +1034,7 @@ try { document.getElementById("answer-mode-selector").addEventListener("input", (e) => { const mode = e.detail; answerMode(mode); + document.getElementById("submit-button").removeAttribute("hidden"); if (mode === "input") { answerLabel.setAttribute("for", "answer-input"); } else if (mode === "math") { @@ -1034,6 +1045,16 @@ try { answerLabel.setAttribute("for", "matrix"); } else if (mode === "frq") { answerLabel.setAttribute("for", "frq-input"); + } else if (mode === "draw") { + answerLabel.setAttribute("for", "draw-input"); + if (!drawLoaded) { + window.__drawInstance = initDraw(domain); + drawLoaded = true; + } else { + if (window.__drawInstance && typeof window.__drawInstance.destroy === 'function') window.__drawInstance.destroy(); + window.__drawInstance = initDraw(domain); + } + document.getElementById("submit-button").setAttribute("hidden", ""); } }); diff --git a/src/design.css b/src/design.css index f1ab91e..b82b838 100644 --- a/src/design.css +++ b/src/design.css @@ -367,7 +367,7 @@ div.ML__keyboard { [data-button-select] > button { height: unset; - min-height: 1.75em; + min-height: 1.75em !important; border-radius: 0.25rem; cursor: pointer; } @@ -417,6 +417,7 @@ div.ML__keyboard { [square] { width: 2.25em !important; min-width: 36px; + min-height: 36px; display: flex; align-items: center; justify-content: center; @@ -449,6 +450,7 @@ div.ML__keyboard { #clicker button { width: inherit; min-width: 36px; + min-height: 36px; } [data-multiple-choice="a"]:hover::before { diff --git a/src/modules/auth.js b/src/modules/auth.js index c16fe6d..f11d8da 100644 --- a/src/modules/auth.js +++ b/src/modules/auth.js @@ -2,9 +2,10 @@ import * as ui from "./ui.js"; import storage from "./storage.js"; import * as themes from "../themes/themes.js"; -const domain = ((window.location.hostname.search('click') != -1) || (window.location.hostname.search('127') != -1)) ? 'https://api.check.vssfalcons.com' : `http://${document.domain}:5000`; +const domain = ((window.location.hostname.search('click') != -1) || (window.location.hostname.search('127') != -1)) ? `https://${(window.location.hostname.search('beta') != -1) ? 'beta.' : ''}api.check.vssfalcons.com` : `http://${document.domain}:5000`; var hasPassword = false; +export var continueWithoutAPI = false; function sortKeys(obj) { return Object.keys(obj).sort().reduce((acc, key) => { @@ -60,6 +61,7 @@ export async function sync(hideWelcome = true, returnFunction = null) { if (!e.message || (e.message && !e.message.includes("."))) ui.view("api-fail"); const continueWithoutAPIButton = document.getElementById("continue-without-api"); continueWithoutAPIButton.addEventListener("click", () => { + continueWithoutAPI = true; ui.view(); returnFunction(); const newContinueWithoutAPIButton = continueWithoutAPIButton.cloneNode(true); diff --git a/src/modules/draw.js b/src/modules/draw.js new file mode 100644 index 0000000..a133908 --- /dev/null +++ b/src/modules/draw.js @@ -0,0 +1,494 @@ +import * as ui from "/src/modules/ui.js"; +import storage from '/src/modules/storage.js'; +import * as themes from '/src/themes/themes.js'; +import { io } from 'socket.io-client'; + +export default function initDraw(domain) { + const wsUrl = `${domain.replace('http', 'ws')}/ws`; + var ws = null; + var isDrawing = false; + var lastStroke = null; + var undoStack = []; + var redoStack = []; + var sendQueue = []; + var sendTimer = null; + var undoQueue = []; + var undoTimer = null; + + const container = document.querySelector('[data-answer-mode="draw"]'); + const undoButton = container.querySelector('[data-action="undo"]'); + const redoButton = container.querySelector('[data-action="redo"]'); + if (!container) return null; + + const canvas = container.querySelector('canvas'); + canvas.style.width = '100%'; + canvas.style.height = '400px'; + canvas.style.padding = '0'; + canvas.style.borderRadius = '0.5rem'; + canvas.style.backgroundColor = themes.getCurrentTheme().surfaceColor; + canvas.width = container.clientWidth; + canvas.height = 400; + canvas.style.touchAction = 'none'; + + const context = canvas.getContext('2d'); + context.lineWidth = 3; + context.lineCap = 'round'; + context.strokeStyle = themes.getCurrentTheme().textColor; + + function currentPosition(e) { + try { + const rect = canvas.getBoundingClientRect(); + return { + x: (e.clientX || (e.touches && e.touches[0].clientX)) - rect.left, + y: (e.clientY || (e.touches && e.touches[0].clientY)) - rect.top + }; + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function start(e) { + try { + e.preventDefault(); + isDrawing = true; + lastStroke = currentPosition(e); + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function generateId() { + try { + return (crypto && crypto.randomUUID) ? crypto.randomUUID() : 'id-' + Math.random().toString(36).slice(2, 10); + } catch (e) { + return 'id-' + Math.random().toString(36).slice(2, 10); + } + } + + function move(e) { + try { + if (!isDrawing) return; + e.preventDefault(); + + const position = currentPosition(e); + context.beginPath(); + context.moveTo(lastStroke.x, lastStroke.y); + context.lineTo(position.x, position.y); + context.stroke(); + + const stroke = { + id: generateId(), + from: lastStroke, + to: position, + width: context.lineWidth + }; + + lastStroke = position; + undoStack.push(stroke); + redoStack.length = 0; + + syncControls(); + queueDraw(stroke); + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function end() { + try { + isDrawing = false; + lastStroke = null; + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function syncControls() { + try { + if (undoButton) undoButton.disabled = !undoStack.length; + if (redoButton) redoButton.disabled = !redoStack.length; + if (!undoStack.length && !redoStack.length) { + container.querySelector('[data-action="clear"]')?.setAttribute('disabled', 'disabled'); + } else { + container.querySelector('[data-action="clear"]')?.removeAttribute('disabled'); + } + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function doUndo() { + try { + if (!undoStack.length) return false; + const stroke = undoStack.pop(); + redoStack.push(stroke); + renderStrokes(undoStack, context); + syncControls(); + if (stroke && stroke.id) { + undoQueue.push(stroke.id); + if (undoTimer) clearTimeout(undoTimer); + undoTimer = setTimeout(flushUndoQueue, 2000); + } + return true; + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function flushUndoQueue() { + try { + if (!undoQueue.length) { + undoTimer = null; + return; + } + if (isDrawing) { + undoTimer = setTimeout(flushUndoQueue, 2000); + return; + } + if (ws && ws.connected) { + const toSend = undoQueue.slice(); + undoQueue = []; + undoTimer = null; + toSend.forEach(id => { + ws.emit('message', { type: 'undo', strokeId: id }); + }); + } else { + undoTimer = setTimeout(flushUndoQueue, 2000); + } + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function doRedo() { + try { + if (!redoStack.length) return false; + const stroke = redoStack.pop(); + undoStack.push(stroke); + renderStrokes(undoStack, context); + syncControls(); + queueDraw(stroke); + return true; + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function queueDraw(stroke) { + try { + if (!stroke) return; + sendQueue.push(stroke); + if (sendTimer) clearTimeout(sendTimer); + sendTimer = setTimeout(flushSendQueue, 2000); + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function flushSendQueue() { + try { + if (!sendQueue.length) { + sendTimer = null; + return; + } + if (isDrawing) { + sendTimer = setTimeout(flushSendQueue, 2000); + return; + } + if (ws && ws.connected) { + const toSend = sendQueue.slice(); + sendQueue = []; + sendTimer = null; + toSend.forEach(st => { + ws.emit('message', { type: 'draw', stroke: st }); + }); + } else { + sendTimer = setTimeout(flushSendQueue, 2000); + } + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function setHold(button, action) { + const step = () => { + try { + const ok = action(); + if (!ok) { + stop(); + return; + } + delay = Math.max(40, delay * 0.85); + timer = setTimeout(step, delay); + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + }; + function start() { + try { + if (!action()) return; + delay = 400; + timer = setTimeout(step, delay); + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + function stop() { + try { + if (timer) { + clearTimeout(timer); + timer = null; + } + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + try { + if (!button) return; + var timer = null; + var delay = 400; + const onPointerDown = (ev) => { + ev.preventDefault(); + start(); + }; + const onPointerUp = stop; + const onPointerLeave = stop; + const onKeyDown = (ev) => { + if ((ev.key === ' ') || (ev.key === 'Enter')) { + ev.preventDefault(); + start(); + } + }; + const onKeyUp = (ev) => { + if ((ev.key === ' ') || (ev.key === 'Enter')) stop(); + }; + button.addEventListener('pointerdown', onPointerDown); + button.addEventListener('pointerup', onPointerUp); + button.addEventListener('pointerleave', onPointerLeave); + button.addEventListener('keydown', onKeyDown); + button.addEventListener('keyup', onKeyUp); + + return function removeHold() { + button.removeEventListener('pointerdown', onPointerDown); + button.removeEventListener('pointerup', onPointerUp); + button.removeEventListener('pointerleave', onPointerLeave); + button.removeEventListener('keydown', onKeyDown); + button.removeEventListener('keyup', onKeyUp); + stop(); + }; + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + function renderStrokes(strokes, context) { + try { + context.clearRect(0, 0, canvas.width, canvas.height); + strokes.forEach(s => { + const stroke = s.stroke || s; + if (!stroke) return; + if (stroke.clear) { + context.clearRect(0, 0, canvas.width, canvas.height); + return; + } + if (stroke.from && stroke.to) { + context.beginPath(); + context.moveTo(stroke.from.x, stroke.from.y); + context.lineTo(stroke.to.x, stroke.to.y); + context.lineWidth = stroke.width || context.lineWidth; + context.strokeStyle = themes.getCurrentTheme().textColor; + context.stroke(); + } + }); + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + } + + try { + canvas.addEventListener('pointerdown', start, { passive: false }); + canvas.addEventListener('pointermove', move, { passive: false }); + canvas.addEventListener('pointerup', end, { passive: false }); + canvas.addEventListener('pointerleave', end, { passive: false }); + + undoButton?.addEventListener('click', (e) => { + e.preventDefault(); + doUndo(); + }); + redoButton?.addEventListener('click', (e) => { + e.preventDefault(); + doRedo(); + }); + undoButton._removeHold = setHold(undoButton, doUndo); + redoButton._removeHold = setHold(redoButton, doRedo); + + container.querySelector('[data-action="clear"]')?.addEventListener('click', () => { + context.clearRect(0, 0, canvas.width, canvas.height); + undoStack.push({ clear: true }); + redoStack.length = 0; + syncControls(); + if (ws && ws.connected) ws.emit('message', { type: 'clear' }); + }); + + if (wsUrl && typeof io !== 'undefined') { + const params = new URLSearchParams({ + role: 'student', + seatCode: storage.get('code') || '', + source: 'clicker', + password: storage.get('password') || '' + }); + ws = io(`${wsUrl}`, { query: Object.fromEntries(params), transports: ['websocket'] }); + ws.on('connect', () => { + (async () => { + try { + const sessionKey = `clicker::${storage.get('code') || 'unknown'}`; + const getStrokes = await fetch(`${domain}/draw/session/${encodeURIComponent(sessionKey)}/strokes${storage.get('password') ? `?seatCode=${encodeURIComponent(storage.get('code') || '')}&password=${encodeURIComponent(storage.get('password'))}` : ''}`); + if (getStrokes.status === 200) { + const strokesJSON = await getStrokes.json(); + if (strokesJSON.strokes && Array.isArray(strokesJSON.strokes) && strokesJSON.strokes.length) { + const normalized = []; + strokesJSON.strokes.forEach(s => { + const st = s.stroke || s; + if (!st) return; + if (st.clear) { + normalized.length = 0; + return; + } + normalized.push(st); + }); + undoStack = normalized.slice(); + redoStack.length = 0; + renderStrokes(undoStack, context); + syncControls(); + } + } + canvas.removeAttribute('disabled'); + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + })(); + }); + ws.on('resetPeriod', (data) => { + try { + const parsed = (typeof data === 'string') ? JSON.parse(data) : data; + if (parsed && String(storage.get('code') || '').startsWith(String(parsed.period))) { + context.clearRect(0, 0, canvas.width, canvas.height); + undoStack.length = 0; + redoStack.length = 0; + syncControls(); + } + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } + }); + } + + let destroy = function () { + if (ws && ws.connected) try { ws.disconnect(); } catch (e) { console.warn('draw ws.disconnect failed', e); } + if (sendTimer) { + clearTimeout(sendTimer); + sendTimer = null; + } + canvas.removeEventListener('pointerdown', start, { passive: false }); + canvas.removeEventListener('pointermove', move, { passive: false }); + canvas.removeEventListener('pointerup', end, { passive: false }); + canvas.removeEventListener('pointerleave', end, { passive: false }); + if (undoButton && undoButton._removeHold) undoButton._removeHold(); + if (redoButton && redoButton._removeHold) redoButton._removeHold(); + }; + + return { canvas, context: context, ws, destroy, _sendQueueSize: () => sendQueue.length }; + } catch (error) { + if (storage.get("developer")) { + alert(`Error @ draw.js: ${error.message}`); + } else { + ui.reportBugModal(null, String(error.stack)); + } + throw error; + } +} diff --git a/src/modules/ui.css b/src/modules/ui.css index 75875dd..f0749ec 100644 --- a/src/modules/ui.css +++ b/src/modules/ui.css @@ -626,3 +626,9 @@ body:has(.topbar) { [data-manual-reset-cache] { margin-top: 10px; } + +canvas[disabled] { + cursor: not-allowed; + opacity: 0.75; + pointer-events: none; +} diff --git a/src/modules/ui.js b/src/modules/ui.js index bb087b6..08bbbc5 100644 --- a/src/modules/ui.js +++ b/src/modules/ui.js @@ -427,7 +427,7 @@ export function view(path = "") { if ((path === 'api-fail') || (path === 'no-course') || (path === 'maintenance-mode')) view(); show(document.querySelector(`[data-modal-page="${pages[0]}"]`), title, buttons); if (path.includes('makeup')) { - document.getElementById("dismiss-makeup-button").innerText = storage.get("makeUpDate") ? "Turn Off Makeup Mode" : "Dismiss"; + document.getElementById("dismiss-makeup-button").innerText = storage.get("makeUpDate") ? "Turn Off Makeup Mode" : "Continue Anyway"; const makeupClickButton = document.getElementById("makeup-click-button"); const newMakeupClickButton = makeupClickButton.cloneNode(true); makeupClickButton.parentNode.replaceChild(newMakeupClickButton, makeupClickButton); @@ -443,14 +443,14 @@ export function view(path = "") { hours = hours ? hours : 12; storage.set("makeUpDate", `${dateParts[1]}/${dateParts[2]}/${dateParts[0]} ${hours}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')} ${ampm}`); view(""); + if (syncPushMakeUpDate) auth.syncPush("makeUpDate"); + updateTitles(); + stopLoader(); } else { storage.set("makeUpDate", null); document.getElementById("date-input").classList.add("attention"); document.getElementById("date-input").focus(); } - if (syncPushMakeUpDate) auth.syncPush("makeUpDate"); - updateTitles(); - stopLoader(); }); const dismissMmakeupClickButton = document.getElementById("dismiss-makeup-button"); const newDismissMmakeupClickButton = dismissMmakeupClickButton.cloneNode(true); diff --git a/src/themes/themes.css b/src/themes/themes.css index 5425e97..8cbf955 100644 --- a/src/themes/themes.css +++ b/src/themes/themes.css @@ -594,3 +594,47 @@ body[data-theme="dune-2"] #header > .virtual-clicker-logo { --error-color: #b90024; background: url(/store/backdrop/chocolate-hearts-2.png) !important; } + +[data-theme="rocket-league"] { + color-scheme: light; + --text-color: #ffffff; + --background-color: #006e37; + --surface-color: #0043b6; + --accent-color: #eb9100; + --accent-text-color: #ffffff; + --error-color: #b90024; + background: url(/store/backdrop/rocket-league.png) !important; +} + +[data-theme="rocket-league-2"] { + color-scheme: light; + --text-color: #ffffff; + --background-color: #a66400; + --surface-color: #003248; + --accent-color: #2567ff; + --accent-text-color: #ffffff; + --error-color: #b90024; + background: url(/store/backdrop/rocket-league-2.png) !important; +} + +[data-theme="rocket-league-ice"] { + color-scheme: light; + --text-color: #fffffb; + --background-color: #8c88a2; + --surface-color: #295486; + --accent-color: #db131f; + --accent-text-color: #2c2c2c; + --error-color: #db131f; + background: url(/store/backdrop/rocket-league-ice.png) !important; +} + +[data-theme="rocket-league-synthwave"] { + color-scheme: dark; + --text-color: #f3e24a; + --background-color: #0d0221; + --surface-color: #0061cc; + --accent-color: #811b80; + --accent-text-color: #2de2e6; + --error-color: #ea1b2d; + background: url(/store/backdrop/rocket-league-synthwave.png) !important; +} diff --git a/src/themes/themes.js b/src/themes/themes.js index fd84709..430181d 100644 --- a/src/themes/themes.js +++ b/src/themes/themes.js @@ -9,6 +9,7 @@ import * as ui from "/src/modules/ui.js"; import storage from "/src/modules/storage.js"; import * as auth from "/src/modules/auth.js"; import Element from "/src/modules/element.js"; +import initDraw from '/src/modules/draw.js'; let selectedTheme = ""; const defaultTheme = { @@ -186,14 +187,14 @@ export function initializeThemeEditor() { }); } -export async function renderStore() { +export async function renderStore(domain) { const store = document.querySelector(`[data-modal-page="store"]`); if (!store) return; store.innerHTML = ""; await storage.idbReady; var initialTheme = storage.get("theme") || "default"; var checks = (await storage.idbGet("cache"))?.checksCount || 0; - document.getElementById("controls-container")?.setAttribute('checks', checks); + if (!auth.continueWithoutAPI) document.getElementById("controls-container")?.setAttribute('checks', checks); var ownedThemes = (await storage.idbGet("cache"))?.ownedThemes || []; if (document.body.getAttribute('data-theme') && !ownedThemes.includes(document.body.getAttribute('data-theme')) && themes.find(theme => theme[0] === document.body.getAttribute('data-theme'))?.[3]) { resetTheme(); @@ -206,7 +207,7 @@ export async function renderStore() { ui.reportBugModal(null, String(error.stack)); } }); - renderStore() + renderStore(domain) .catch(error => { if (storage.get("developer")) { alert(`Error @ themes.js: ${error.message}`); @@ -220,7 +221,7 @@ export async function renderStore() { const checksText = document.createElement("p"); checksText.classList = 'checks-text'; checksText.innerHTML = ` You've got ${checks} Check${checks == 1 ? '' : 's'} available to spend!`; - store.appendChild(checksText); + if (!auth.continueWithoutAPI) store.appendChild(checksText); if (featuredTheme) { const promo = document.createElement("div"); promo.classList = 'promo'; @@ -234,10 +235,13 @@ export async function renderStore() { promoButton.addEventListener("mouseover", () => { initialTheme = document.body.getAttribute('data-theme') || ''; document.body.setAttribute('data-theme', featuredTheme[0] || ''); + initDraw(domain); if (ownedThemes.includes(featuredTheme[0]) || !featuredTheme[3]) { promoButton.textContent = "Apply Theme"; } else if (featuredTheme[4] && featuredTheme[4].length && !featuredTheme[4].some(t => ownedThemes.includes(t))) { promoButton.textContent = "Locked"; + } else if (auth.continueWithoutAPI) { + promoButton.textContent = "API Offline"; } else if (checks >= featuredTheme[3]) { promoButton.textContent = `Purchase for ${featuredTheme[3]} Check${featuredTheme[3] == 1 ? '' : 's'}`; } else { @@ -246,6 +250,7 @@ export async function renderStore() { }); promoButton.addEventListener("mouseout", () => { document.body.setAttribute('data-theme', initialTheme); + initDraw(domain); promoButton.textContent = ownedThemes.includes(featuredTheme[0]) ? "Owned" : "Preview Theme"; }); promoButton.addEventListener("click", async () => { @@ -253,6 +258,7 @@ export async function renderStore() { initialTheme = featuredTheme[0]; storage.set("theme", featuredTheme[0]); document.body.setAttribute('data-theme', featuredTheme[0]); + initDraw(domain); Array.from(store.querySelectorAll('.theme-item.selected')).forEach(el => el.classList.remove('selected')); Array.from(store.querySelectorAll(`.theme-item[data-theme="${featuredTheme[0]}"]`)).forEach(el => el.classList.add('selected')); Array.from(store.querySelectorAll('.theme-item button')).forEach(btn => { @@ -267,7 +273,7 @@ export async function renderStore() { } }); ui.toast(`Applied ${featuredTheme[1] || featuredTheme[0]} theme.`, 2000, "success", "bi bi-check2-circle"); - } else { + } else if (!auth.continueWithoutAPI) { if (featuredTheme[4] && featuredTheme[4].length && !featuredTheme[4].some(t => ownedThemes.includes(t))) { ui.toast(`Cannot purchase ${featuredTheme[1] || featuredTheme[0]} theme. Missing required themes: ${featuredTheme[4].map(t => themes.find(th => th[0] == t)[1] || t).join(', ')}.`, 4000, "error", "bi bi-exclamation-triangle-fill"); return; @@ -311,6 +317,7 @@ export async function renderStore() { checksText.innerHTML = ` You've got ${cache.checksCount} Check${(cache.checksCount == 1) ? '' : 's'} available to spend!`; storage.set("theme", featuredTheme[0]); document.body.setAttribute('data-theme', featuredTheme[0]); + initDraw(domain); await auth.syncPush("theme") .catch(error => { if (storage.get("developer")) { @@ -354,12 +361,15 @@ export async function renderStore() { themeButton.addEventListener("mouseover", () => { initialTheme = document.body.getAttribute('data-theme') || ''; document.body.setAttribute('data-theme', theme[0] || ''); + initDraw(domain); if (themeItem.classList.contains('selected')) { themeButton.textContent = "Applied"; } else if (ownedThemes.includes(theme[0]) || !theme[3]) { themeButton.textContent = "Apply Now"; } else if (theme[4] && theme[4].length && !theme[4].some(t => ownedThemes.includes(t))) { themeButton.textContent = "Locked"; + } else if (auth.continueWithoutAPI) { + themeButton.textContent = "API Offline"; } else if (checks >= theme[3]) { themeButton.textContent = `Purchase for ${theme[3]} Check${theme[3] == 1 ? '' : 's'}`; } else { @@ -368,6 +378,7 @@ export async function renderStore() { }); themeButton.addEventListener("mouseout", () => { document.body.setAttribute('data-theme', initialTheme); + initDraw(domain); themeButton.textContent = themeItem.classList.contains('selected') ? "Applied" : (ownedThemes.includes(theme[0]) ? "Owned" : "Preview"); }); themeButton.addEventListener("click", async () => { @@ -376,6 +387,7 @@ export async function renderStore() { initialTheme = theme[0]; storage.set("theme", theme[0]); document.body.setAttribute('data-theme', theme[0]); + initDraw(domain); Array.from(store.querySelectorAll('.theme-item.selected')).forEach(el => el.classList.remove('selected')); themeItem.classList.add('selected'); Array.from(store.querySelectorAll('.theme-item button')).forEach(btn => { @@ -390,7 +402,7 @@ export async function renderStore() { } }); ui.toast(`Applied ${name} theme.`, 2000, "success", "bi bi-check2-circle"); - } else { + } else if (!auth.continueWithoutAPI) { if (theme[4] && theme[4].length && !theme[4].some(t => ownedThemes.includes(t))) { ui.toast(`Cannot purchase ${name} theme. Missing required themes: ${theme[4].map(t => themes.find(th => th[0] == t)[1] || t).join(', ')}.`, 4000, "error", "bi bi-exclamation-triangle-fill"); return; @@ -434,6 +446,7 @@ export async function renderStore() { checksText.innerHTML = ` You've got ${cache.checksCount} Check${(cache.checksCount == 1) ? '' : 's'} available to spend!`; storage.set("theme", theme[0]); document.body.setAttribute('data-theme', theme[0]); + initDraw(domain); await auth.syncPush("theme") .catch(error => { if (storage.get("developer")) { @@ -481,6 +494,17 @@ export async function renderStore() { store.appendChild(costInfo); } +export function getCurrentTheme() { + return { + textColor: getComputedStyle(document.body).getPropertyValue("--text-color").trim(), + backgroundColor: getComputedStyle(document.body).getPropertyValue("--background-color").trim(), + surfaceColor: getComputedStyle(document.body).getPropertyValue("--surface-color").trim(), + accentColor: getComputedStyle(document.body).getPropertyValue("--accent-color").trim(), + accentTextColor: getComputedStyle(document.body).getPropertyValue("--accent-text-color").trim(), + errorColor: getComputedStyle(document.body).getPropertyValue("--error-color").trim(), + } +} + try { themes.forEach((theme) => { const value = theme[0]; diff --git a/src/themes/themes.json b/src/themes/themes.json index 0fd531f..b6f6412 100644 --- a/src/themes/themes.json +++ b/src/themes/themes.json @@ -51,5 +51,9 @@ ["chocolate-hearts", "Chocolate Hearts", "heart", 80, ["rose-pro"], true], ["chocolate-hearts-2", "Chocolate Hearts 2", "heart", 80, ["rose-pro", "chocolate-hearts"], true], ["neon-red", "Neon Red", "", 0, [], false], - ["strawberry", "Strawberry", "", 0, [], false] + ["strawberry", "Strawberry", "", 0, [], false], + ["rocket-league", "Rocket League", "rocket", 60, [], false], + ["rocket-league-2", "Rocket League 2", "rocket", 70, ["rocket-league"], false], + ["rocket-league-ice", "Rocket League Ice", "rocket", 80, ["rocket-league", "rocket-league-2", "blizzard-pro"], true], + ["rocket-league-synthwave", "Rocket League Synthwave", "rocket", 80, ["rocket-league", "rocket-league-2", "synthwave-pro"], true] ]