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]
]