From 4d365802a172538fd3d723757d324555e931cd48 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 24 Apr 2022 17:05:04 +0200 Subject: [PATCH] Added popup to share test settings through url (#2870) * added package for compressing strings * added popup to share test settings * added function to parse test settings from url * returning if no settings were applied * removed pointless try catches * added type for shared setings instead of casting * small refactor * unnecessary ?? --- frontend/package-lock.json | 11 ++ frontend/package.json | 1 + .../scripts/controllers/account-controller.ts | 1 + .../src/scripts/elements/commandline-lists.ts | 9 ++ .../popups/share-test-settings-popup.ts | 146 ++++++++++++++++++ frontend/src/scripts/utils/url-handler.ts | 107 ++++++++++++- frontend/src/styles/popups.scss | 20 +++ frontend/static/html/popups.html | 96 ++++++++++++ 8 files changed, 385 insertions(+), 6 deletions(-) create mode 100644 frontend/src/scripts/popups/share-test-settings-popup.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b2f8a3016..fa23c7ad6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,6 +21,7 @@ "firebase": "9.6.0", "howler": "2.2.3", "html2canvas": "1.4.1", + "lz-ts": "1.1.2", "object-hash": "3.0.0", "remove-files-webpack-plugin": "1.5.0", "stemmer": "2.0.0", @@ -8194,6 +8195,11 @@ "node": ">=10" } }, + "node_modules/lz-ts": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lz-ts/-/lz-ts-1.1.2.tgz", + "integrity": "sha512-ye8sVndmvzs46cPgX1Yjlk3o/Sueu0VHn253rKpsWiK2/bAbsVkD7DEJiaueiPfbZTi17GLRPkv3W5O3BUNd2g==" + }, "node_modules/madge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/madge/-/madge-5.0.1.tgz", @@ -20833,6 +20839,11 @@ "yallist": "^4.0.0" } }, + "lz-ts": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lz-ts/-/lz-ts-1.1.2.tgz", + "integrity": "sha512-ye8sVndmvzs46cPgX1Yjlk3o/Sueu0VHn253rKpsWiK2/bAbsVkD7DEJiaueiPfbZTi17GLRPkv3W5O3BUNd2g==" + }, "madge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/madge/-/madge-5.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9554b2c56..ea3a0a1a0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -64,6 +64,7 @@ "firebase": "9.6.0", "howler": "2.2.3", "html2canvas": "1.4.1", + "lz-ts": "1.1.2", "object-hash": "3.0.0", "remove-files-webpack-plugin": "1.5.0", "stemmer": "2.0.0", diff --git a/frontend/src/scripts/controllers/account-controller.ts b/frontend/src/scripts/controllers/account-controller.ts index 4dc3a1ae1..b544b5068 100644 --- a/frontend/src/scripts/controllers/account-controller.ts +++ b/frontend/src/scripts/controllers/account-controller.ts @@ -302,6 +302,7 @@ const authListener = Auth.onAuthStateChanged(async function (user) { } URLHandler.loadCustomThemeFromUrl(); + URLHandler.loadTestSettingsFromUrl(); if (/challenge_.+/g.test(window.location.pathname)) { Notifications.add( "Challenge links temporarily disabled. Please use the command line to load the challenge manually", diff --git a/frontend/src/scripts/elements/commandline-lists.ts b/frontend/src/scripts/elements/commandline-lists.ts index f7ef87e62..9b735d8ce 100644 --- a/frontend/src/scripts/elements/commandline-lists.ts +++ b/frontend/src/scripts/elements/commandline-lists.ts @@ -19,6 +19,7 @@ import * as PaceCaret from "../test/pace-caret"; import * as TestInput from "../test/test-input"; import * as ModesNotice from "../elements/modes-notice"; import * as ConfigEvent from "../observables/config-event"; +import * as ShareTestSettingsPopup from "../popups/share-test-settings-popup"; import { Auth } from "../firebase"; export let current: MonkeyTypes.CommandsGroup[] = []; @@ -3311,6 +3312,14 @@ export const defaultCommands: MonkeyTypes.CommandsGroup = { visible: false, subgroup: commandsMonkeyPowerLevel, }, + { + id: "shareTestSettings", + display: "Share test settings", + icon: "fa-share", + exec: async (): Promise => { + ShareTestSettingsPopup.show(); + }, + }, { id: "clearSwCache", display: "Clear SW cache", diff --git a/frontend/src/scripts/popups/share-test-settings-popup.ts b/frontend/src/scripts/popups/share-test-settings-popup.ts new file mode 100644 index 000000000..1a54a3f17 --- /dev/null +++ b/frontend/src/scripts/popups/share-test-settings-popup.ts @@ -0,0 +1,146 @@ +import Config from "../config"; +import { randomQuote } from "../test/test-words"; +import { getMode2 } from "../utils/misc"; +import * as CustomText from "../test/custom-text"; +import { compressToURI } from "lz-ts"; + +function getCheckboxValue(checkbox: string): boolean { + return $(`#shareTestSettingsPopupWrapper label.${checkbox} input`).prop( + "checked" + ); +} + +type SharedTestSettings = [ + MonkeyTypes.Mode | null, + MonkeyTypes.Mode2 | null, + MonkeyTypes.CustomText | null, + boolean | null, + boolean | null, + string | null, + MonkeyTypes.Difficulty | null, + string | null +]; + +function updateURL(): void { + const baseUrl = location.origin + "?testSettings="; + const settings: SharedTestSettings = [ + null, + null, + null, + null, + null, + null, + null, + null, + ]; + + if (getCheckboxValue("mode")) { + settings[0] = Config.mode; + } + + if (getCheckboxValue("mode2")) { + settings[1] = getMode2( + Config, + randomQuote + ) as MonkeyTypes.Mode2; + } + + if (getCheckboxValue("customText")) { + settings[2] = { + text: CustomText.text, + isWordRandom: CustomText.isWordRandom, + isTimeRandom: CustomText.isTimeRandom, + word: CustomText.word, + time: CustomText.time, + delimiter: CustomText.delimiter, + }; + } + + if (getCheckboxValue("punctuation")) { + settings[3] = Config.punctuation; + } + + if (getCheckboxValue("numbers")) { + settings[4] = Config.numbers; + } + + if (getCheckboxValue("language")) { + settings[5] = Config.language; + } + + if (getCheckboxValue("difficulty")) { + settings[6] = Config.difficulty; + } + + if (getCheckboxValue("funbox")) { + settings[7] = Config.funbox; + } + + const compressed = compressToURI(JSON.stringify(settings)); + + const url = baseUrl + compressed; + $(`#shareTestSettingsPopupWrapper textarea.url`).val(url); + if (url.length > 2000) { + $(`#shareTestSettingsPopupWrapper .tooLongWarning`).removeClass("hidden"); + } else { + $(`#shareTestSettingsPopupWrapper .tooLongWarning`).addClass("hidden"); + } +} + +function updateSubgroups(): void { + if (getCheckboxValue("mode")) { + $(`#shareTestSettingsPopupWrapper .subgroup`).removeClass("hidden"); + } else { + $(`#shareTestSettingsPopupWrapper .subgroup`).addClass("hidden"); + } +} + +export function show(): void { + if ($("#shareTestSettingsPopupWrapper").hasClass("hidden")) { + updateURL(); + updateSubgroups(); + $("#shareTestSettingsPopupWrapper") + .stop(true, true) + .css("opacity", 0) + .removeClass("hidden") + .animate({ opacity: 1 }, 100); + } +} + +export async function hide(): Promise { + if (!$("#shareTestSettingsPopupWrapper").hasClass("hidden")) { + $("#shareTestSettingsPopupWrapper") + .stop(true, true) + .css("opacity", 1) + .animate( + { + opacity: 0, + }, + 100, + () => { + $("#shareTestSettingsPopupWrapper").addClass("hidden"); + } + ); + } +} + +$(`#shareTestSettingsPopupWrapper label input`).on("change", () => { + updateURL(); + updateSubgroups(); +}); + +$("#shareTestSettingsPopupWrapper").on("mousedown", (e) => { + if ($(e.target).attr("id") === "shareTestSettingsPopupWrapper") { + hide(); + } +}); + +$(document).on("keydown", (event) => { + if ( + event.key === "Escape" && + !$("#shareTestSettingsPopupWrapper").hasClass("hidden") + ) { + hide(); + event.preventDefault(); + } +}); diff --git a/frontend/src/scripts/utils/url-handler.ts b/frontend/src/scripts/utils/url-handler.ts index a21fd8cb0..3df472679 100644 --- a/frontend/src/scripts/utils/url-handler.ts +++ b/frontend/src/scripts/utils/url-handler.ts @@ -1,18 +1,18 @@ import * as Misc from "./misc"; import Config, * as UpdateConfig from "../config"; import * as Notifications from "../elements/notifications"; +import { decompressFromURI } from "lz-ts"; +import * as QuoteSearchPopup from "../popups/quote-search-popup"; +import * as ManualRestart from "../test/manual-restart-tracker"; +import * as CustomText from "../test/custom-text"; +import { restart as restartTest } from "../test/test-logic"; export function loadCustomThemeFromUrl(): void { const getValue = Misc.findGetParameter("customTheme"); if (getValue === null) return; const urlEncoded = getValue.split(","); - let base64decoded = null; - try { - base64decoded = JSON.parse(atob(getValue) ?? ""); - } catch (e) { - // - } + const base64decoded = JSON.parse(atob(getValue) ?? ""); let colorArray = []; if (Array.isArray(urlEncoded) && urlEncoded.length === 9) { @@ -39,3 +39,98 @@ export function loadCustomThemeFromUrl(): void { UpdateConfig.setCustomThemeColors(oldCustomThemeColors); } } + +type SharedTestSettings = [ + MonkeyTypes.Mode | null, + MonkeyTypes.Mode2 | null, + MonkeyTypes.CustomText | null, + boolean | null, + boolean | null, + string | null, + MonkeyTypes.Difficulty | null, + string | null +]; + +export function loadTestSettingsFromUrl(): void { + const getValue = Misc.findGetParameter("testSettings"); + if (getValue === null) return; + + const de: SharedTestSettings = JSON.parse(decompressFromURI(getValue) ?? ""); + + const applied: { [key: string]: string } = {}; + + if (de[0]) { + UpdateConfig.setMode(de[0], true); + applied["mode"] = de[0]; + } + + if (de[1]) { + if (Config.mode === "time") { + UpdateConfig.setTimeConfig(parseInt(de[1], 10), true); + } else if (Config.mode === "words") { + UpdateConfig.setWordCount(parseInt(de[1], 10), true); + } else if (Config.mode === "quote") { + UpdateConfig.setQuoteLength(-2, false); + QuoteSearchPopup.setSelectedId(parseInt(de[1], 10)); + ManualRestart.set(); + } + applied["mode2"] = de[1]; + } + + if (de[2]) { + const customTextSettings = de[2]; + CustomText.setText(customTextSettings["text"]); + CustomText.setIsTimeRandom(customTextSettings["isTimeRandom"]); + CustomText.setIsWordRandom(customTextSettings["isWordRandom"]); + if (customTextSettings["isTimeRandom"]) { + CustomText.setWord(customTextSettings["time"]); + } + if (customTextSettings["isWordRandom"]) { + CustomText.setTime(customTextSettings["word"]); + } + CustomText.setDelimiter(customTextSettings["delimiter"]); + applied["custom text settings"] = ""; + } + + if (de[3]) { + UpdateConfig.setPunctuation(de[3], true); + applied["punctuation"] = de[3] ? "on" : "off"; + } + + if (de[4]) { + UpdateConfig.setNumbers(de[4], true); + applied["numbers"] = de[4] ? "on" : "off"; + } + + if (de[5]) { + UpdateConfig.setLanguage(de[5], true); + applied["language"] = de[5]; + } + + if (de[6]) { + UpdateConfig.setDifficulty(de[6], true); + applied["difficulty"] = de[6]; + } + + if (de[7]) { + UpdateConfig.setFunbox(de[7], true); + applied["funbox"] = de[7]; + } + + restartTest(); + + let appliedString = ""; + + Object.keys(applied).forEach((setKey) => { + const set = applied[setKey]; + appliedString += `${setKey}${set ? ": " + set : ""}
`; + }); + + if (appliedString !== "") { + Notifications.add( + "Settings applied from URL:

" + appliedString, + 1, + 10 + ); + } +} diff --git a/frontend/src/styles/popups.scss b/frontend/src/styles/popups.scss index bf8148232..eac6a1cdb 100644 --- a/frontend/src/styles/popups.scss +++ b/frontend/src/styles/popups.scss @@ -399,6 +399,26 @@ } } +#shareTestSettingsPopup { + background: var(--bg-color); + border-radius: var(--roundness); + padding: 2rem; + display: grid; + gap: 1rem; + width: 500px; + .title { + font-size: 1.5rem; + color: var(--sub-color); + } + .subgroup { + padding-left: 2rem; + } + .tooLongWarning { + font-size: 0.75rem; + color: var(--error-color); + } +} + #pbTablesPopupWrapper #pbTablesPopup { .title { color: var(--text-color); diff --git a/frontend/static/html/popups.html b/frontend/static/html/popups.html index 34f00ab46..f12081ac5 100644 --- a/frontend/static/html/popups.html +++ b/frontend/static/html/popups.html @@ -2,6 +2,102 @@
+ +