From f221326f4783f91027572141e33d353a60eb8731 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 6 Mar 2022 16:34:48 +0100 Subject: [PATCH] Added Ape Keys Popup (#2642) * added disabled field to simple popup input * inverted disabled logic * fixed initial value completely not working in simple popups * loading ape keys into the snapshot * added type for ape keys * only vertical resize for textareas * added before init function added can close parameter correclty handling textearas * storing active popup in a variable * fixed click handler * hiding text element if string is empty * updated ape keys types * added click handler to open ape keys popup * added simple poopups for generating, editing and deleting ape keys * added ape keys popup * updated ape key type * ape keys is optional * not getting ape keys by default * added function to get ape keys * refactor * using last used on property --- .../src/scripts/ape/endpoints/ape-keys.ts | 5 +- frontend/src/scripts/ape/types/ape.d.ts | 3 +- frontend/src/scripts/db.ts | 22 ++ frontend/src/scripts/pages/settings.ts | 13 +- frontend/src/scripts/popups/ape-keys-popup.ts | 127 +++++++++ frontend/src/scripts/popups/simple-popups.ts | 244 +++++++++++++++++- frontend/src/scripts/types/types.d.ts | 13 + frontend/src/styles/inputs.scss | 4 + frontend/src/styles/popups.scss | 106 ++++++++ frontend/static/index.html | 40 +++ 10 files changed, 557 insertions(+), 20 deletions(-) create mode 100644 frontend/src/scripts/popups/ape-keys-popup.ts diff --git a/frontend/src/scripts/ape/endpoints/ape-keys.ts b/frontend/src/scripts/ape/endpoints/ape-keys.ts index aa17b10b4..660d5a7bd 100644 --- a/frontend/src/scripts/ape/endpoints/ape-keys.ts +++ b/frontend/src/scripts/ape/endpoints/ape-keys.ts @@ -14,10 +14,9 @@ export default function getApeKeysEndpoints( async function update( apeKeyId: string, - name: string, - enabled: boolean + updates: { name?: string; enabled?: boolean } ): Ape.EndpointData { - const payload = { name, enabled }; + const payload = { ...updates }; return await apeClient.patch(`${BASE_PATH}/${apeKeyId}`, { payload }); } diff --git a/frontend/src/scripts/ape/types/ape.d.ts b/frontend/src/scripts/ape/types/ape.d.ts index b1315f844..e3dc287cf 100644 --- a/frontend/src/scripts/ape/types/ape.d.ts +++ b/frontend/src/scripts/ape/types/ape.d.ts @@ -136,8 +136,7 @@ declare namespace Ape { generate: (name: string, enabled: boolean) => EndpointData; update: ( apeKeyId: string, - name: string, - enabled: boolean + updates: { name?: string; enabled?: boolean } ) => EndpointData; delete: (apeKeyId: string) => EndpointData; }; diff --git a/frontend/src/scripts/db.ts b/frontend/src/scripts/db.ts index c52ea24c3..fdf0a88f1 100644 --- a/frontend/src/scripts/db.ts +++ b/frontend/src/scripts/db.ts @@ -182,6 +182,28 @@ export async function getUserResults(): Promise { return true; } } + +export async function getUserApeKeys(): Promise< + MonkeyTypes.ApeKeys | undefined +> { + const user = firebase.auth().currentUser; + if (user == null) return undefined; + if (dbSnapshot === null) return undefined; + if (dbSnapshot.apeKeys !== undefined) { + return dbSnapshot.apeKeys; + } else { + const response = await Ape.apeKeys.get(); + + if (response.status !== 200) { + Notifications.add("Error getting ape keys: " + response.message, -1); + return undefined; + } + + dbSnapshot.apeKeys = response.data as MonkeyTypes.ApeKeys; + return dbSnapshot.apeKeys; + } +} + export async function getUserHighestWpm( mode: M, mode2: MonkeyTypes.Mode2, diff --git a/frontend/src/scripts/pages/settings.ts b/frontend/src/scripts/pages/settings.ts index e4fe7e831..9b0fde931 100644 --- a/frontend/src/scripts/pages/settings.ts +++ b/frontend/src/scripts/pages/settings.ts @@ -12,6 +12,7 @@ import * as ImportExportSettingsPopup from "../popups/import-export-settings-pop import * as CustomThemePopup from "../popups/custom-theme-popup"; import * as ConfigEvent from "../observables/config-event"; import * as ActivePage from "../states/active-page"; +import * as ApeKeysPopup from "../popups/ape-keys-popup"; import Page from "./page"; type SettingsGroups = { @@ -948,10 +949,14 @@ $("#shareCustomThemeButton").click(() => { ); }); -$(".pageSettings .sectionGroupTitle").click((e) => { +$(".pageSettings .sectionGroupTitle").on("click", (e) => { toggleSettingsGroup($(e.currentTarget).attr("group") as string); }); +$(".pageSettings .section.apeKeys #showApeKeysPopup").on("click", () => { + ApeKeysPopup.show(); +}); + $(".pageSettings .section.customBackgroundSize .inputAndButton .save").on( "click", () => { @@ -982,7 +987,7 @@ $(".pageSettings .section.customLayoutfluid .inputAndButton .save").on( $( ".pageSettings .section.customLayoutfluid .inputAndButton input" ).val() as MonkeyTypes.CustomLayoutFluidSpaces - ).then(bool => { + ).then((bool) => { if (bool) { Notifications.add("Custom layoutfluid saved", 1); } @@ -997,8 +1002,8 @@ $(".pageSettings .section.customLayoutfluid .inputAndButton .input").keypress( $( ".pageSettings .section.customLayoutfluid .inputAndButton input" ).val() as MonkeyTypes.CustomLayoutFluidSpaces - ).then(bool => { - if (bool) { + ).then((bool) => { + if (bool) { Notifications.add("Custom layoutfluid saved", 1); } }); diff --git a/frontend/src/scripts/popups/ape-keys-popup.ts b/frontend/src/scripts/popups/ape-keys-popup.ts new file mode 100644 index 000000000..255339168 --- /dev/null +++ b/frontend/src/scripts/popups/ape-keys-popup.ts @@ -0,0 +1,127 @@ +import * as DB from "../db"; +import Ape from "../ape"; +import * as Loader from "../elements/loader"; +import * as Notifications from "../elements/notifications"; + +function refreshList(): void { + const data = DB.getSnapshot().apeKeys; + if (!data) return; + const table = $("#apeKeysPopupWrapper table tbody"); + table.empty(); + const apeKeyIds = Object.keys(data); + if (apeKeyIds.length === 0) { + table.append( + "No keys found" + ); + return; + } + apeKeyIds.forEach((apeKeyId) => { + const key = data[apeKeyId] as MonkeyTypes.ApeKey; + table.append(` + + +
+ ${ + key.enabled + ? `` + : `` + } +
+ + ${key.name} + ${moment(key.createdOn).format("DD MMM YYYY HH:mm")} + ${moment(key.modifiedOn).format("DD MMM YYYY HH:mm")} + ${ + key.lastUsedOn === -1 + ? "-" + : moment(key.lastUsedOn).format("DD MMM YYYY HH:mm") + } + +
+
+ +
+
+ +
+
+ + + `); + }); +} + +export function hide(): void { + if (!$("#apeKeysPopupWrapper").hasClass("hidden")) { + $("#apeKeysPopupWrapper") + .stop(true, true) + .css("opacity", 1) + .animate( + { + opacity: 0, + }, + 100, + () => { + $("#apeKeysPopupWrapper").addClass("hidden"); + } + ); + } +} + +//show the popup +export async function show(): Promise { + if ($("#apeKeysPopupWrapper").hasClass("hidden")) { + Loader.show(); + await DB.getUserApeKeys(); + Loader.hide(); + refreshList(); + $("#apeKeysPopupWrapper") + .stop(true, true) + .css("opacity", 0) + .removeClass("hidden") + .animate( + { + opacity: 1, + }, + 100, + () => { + $("#apeKeysPopup textarea").focus().select(); + } + ); + } +} + +$("#apeKeysPopupWrapper").on("mousedown", (e) => { + if ($(e.target).attr("id") === "apeKeysPopupWrapper") { + hide(); + } +}); + +$("#apeKeysPopup .generateApeKey").on("click", () => { + hide(); +}); + +$(document).on("click", "#apeKeysPopup table .keyButtons .button", () => { + hide(); +}); + +$(document).on("click", "#apeKeysPopup table .icon-button", async (e) => { + const keyId = $(e.target).closest("tr").attr("keyId") as string; + const snap = DB.getSnapshot(); + const key = snap.apeKeys?.[keyId]; + if (!key || !snap.apeKeys) return; + Loader.show(); + const response = await Ape.apeKeys.update(keyId, { enabled: !key.enabled }); + Loader.hide(); + if (response.status !== 200) { + return Notifications.add("Failed to update key: " + response.message, -1); + } + snap.apeKeys[keyId].enabled = !key.enabled; + DB.setSnapshot(snap); + refreshList(); + if (key.enabled) { + Notifications.add("Key active", 1); + } else { + Notifications.add("Key inactive", 1); + } +}); diff --git a/frontend/src/scripts/popups/simple-popups.ts b/frontend/src/scripts/popups/simple-popups.ts index c44abdb81..91e8f69da 100644 --- a/frontend/src/scripts/popups/simple-popups.ts +++ b/frontend/src/scripts/popups/simple-popups.ts @@ -6,14 +6,18 @@ import * as UpdateConfig from "../config"; import * as Loader from "../elements/loader"; import * as Notifications from "../elements/notifications"; import * as Settings from "../pages/settings"; +import * as ApeKeysPopup from "../popups/ape-keys-popup"; type Input = { placeholder: string; type?: string; initVal: string; hidden?: boolean; + disabled?: boolean; }; +let activePopup: SimplePopup | null = null; + export const list: { [key: string]: SimplePopup } = {}; class SimplePopup { parameters: string[]; @@ -26,7 +30,9 @@ class SimplePopup { text: string; buttonText: string; execFn: (thisPopup: SimplePopup, ...params: string[]) => void | Promise; + beforeInitFn: (thisPopup: SimplePopup) => void; beforeShowFn: (thisPopup: SimplePopup) => void; + canClose: boolean; constructor( id: string, type: string, @@ -38,6 +44,7 @@ class SimplePopup { thisPopup: SimplePopup, ...params: string[] ) => void | Promise, + beforeInitFn: (thisPopup: SimplePopup) => void, beforeShowFn: (thisPopup: SimplePopup) => void ) { this.parameters = []; @@ -51,7 +58,9 @@ class SimplePopup { this.wrapper = $("#simplePopupWrapper"); this.element = $("#simplePopup"); this.buttonText = buttonText; + this.beforeInitFn = (thisPopup): void => beforeInitFn(thisPopup); this.beforeShowFn = (thisPopup): void => beforeShowFn(thisPopup); + this.canClose = true; } reset(): void { this.element.html(` @@ -78,6 +87,12 @@ class SimplePopup { el.find(".button").text(this.buttonText); } + if (this.text === "") { + el.find(".text").addClass("hidden"); + } else { + el.find(".text").removeClass("hidden"); + } + // } } @@ -90,7 +105,7 @@ class SimplePopup { { if (input.type) { - el.find(".inputs").append(` + if (input.type === "textarea") { + el.find(".inputs").append(` + + `); + } else { + el.find(".inputs").append(` - `); + `); + } } else { el.find(".inputs").append(` `); @@ -132,6 +161,7 @@ class SimplePopup { } exec(): void { + if (!this.canClose) return; const vals: string[] = []; $.each($("#simplePopup input"), (_, el) => { vals.push($(el).val() as string); @@ -141,9 +171,11 @@ class SimplePopup { } show(parameters: string[] = []): void { + activePopup = this; this.parameters = parameters; - this.beforeShowFn(this); + this.beforeInitFn(this); this.init(); + this.beforeShowFn(this); this.wrapper .stop(true, true) .css("opacity", 0) @@ -154,6 +186,8 @@ class SimplePopup { } hide(): void { + if (!this.canClose) return; + activePopup = null; this.wrapper .stop(true, true) .css("opacity", 1) @@ -165,6 +199,7 @@ class SimplePopup { } export function hide(): void { + if (activePopup) return activePopup.hide(); $("#simplePopupWrapper") .stop(true, true) .css("opacity", 1) @@ -176,6 +211,7 @@ export function hide(): void { $("#simplePopupWrapper").mousedown((e) => { if ($(e.target).attr("id") === "simplePopupWrapper") { + if (activePopup) return activePopup.hide(); $("#simplePopupWrapper") .stop(true, true) .css("opacity", 1) @@ -266,6 +302,9 @@ list["updateEmail"] = new SimplePopup( thisPopup.buttonText = ""; thisPopup.text = "Password authentication is not enabled"; } + }, + (_thisPopup) => { + // } ); @@ -338,6 +377,9 @@ list["updateName"] = new SimplePopup( thisPopup.inputs[0].hidden = true; thisPopup.buttonText = "Reauthenticate to update"; } + }, + (_thisPopup) => { + // } ); @@ -400,6 +442,9 @@ list["updatePassword"] = new SimplePopup( thisPopup.buttonText = ""; thisPopup.text = "Password authentication is not enabled"; } + }, + (_thisPopup) => { + // } ); @@ -449,6 +494,9 @@ list["addPasswordAuth"] = new SimplePopup( }, () => { // + }, + (_thisPopup) => { + // } ); @@ -526,6 +574,9 @@ list["deleteAccount"] = new SimplePopup( thisPopup.inputs = []; thisPopup.buttonText = "Reauthenticate to delete"; } + }, + (_thisPopup) => { + // } ); @@ -569,6 +620,9 @@ list["clearTagPb"] = new SimplePopup( }, (thisPopup) => { thisPopup.text = `Are you sure you want to clear PB for tag ${thisPopup.parameters[1]}?`; + }, + (_thisPopup) => { + // } ); @@ -585,6 +639,9 @@ list["applyCustomFont"] = new SimplePopup( }, () => { // + }, + (_thisPopup) => { + // } ); @@ -643,6 +700,9 @@ list["resetPersonalBests"] = new SimplePopup( thisPopup.inputs = []; thisPopup.buttonText = "Reauthenticate to reset"; } + }, + (_thisPopup) => { + // } ); @@ -661,6 +721,9 @@ list["resetSettings"] = new SimplePopup( }, () => { // + }, + (_thisPopup) => { + // } ); @@ -689,6 +752,151 @@ list["unlinkDiscord"] = new SimplePopup( }, () => { // + }, + (_thisPopup) => { + // + } +); + +list["generateApeKey"] = new SimplePopup( + "generateApeKey", + "text", + "Generate new key", + [ + { + placeholder: "Name", + initVal: "", + }, + ], + "", + "Generate", + async (_thisPopup, name) => { + Loader.show(); + const response = await Ape.apeKeys.generate(name, false); + Loader.hide(); + + if (response.status !== 200) { + return Notifications.add( + "Failed to generate key: " + response.message, + -1 + ); + } else { + const data = response.data; + list["viewApeKey"].show([data.apeKey]); + const snap = DB.getSnapshot(); + if (snap.apeKeys) { + snap.apeKeys[data.apeKeyId] = data.apeKeyDetails; + DB.setSnapshot(snap); + } + } + }, + () => { + // + }, + (_thisPopup) => { + // + } +); + +list["viewApeKey"] = new SimplePopup( + "viewApeKey", + "text", + "Ape Key", + [ + { + type: "textarea", + disabled: true, + placeholder: "Key", + initVal: "", + }, + ], + "This is your new Ape Key. Please keep it safe. You will only see it once!", + "Close", + (_thisPopup) => { + ApeKeysPopup.show(); + }, + (_thisPopup) => { + _thisPopup.inputs[0].initVal = _thisPopup.parameters[0]; + }, + (_thisPopup) => { + _thisPopup.canClose = false; + $("#simplePopup textarea").css("height", "110px"); + $("#simplePopup .button").addClass("hidden"); + setTimeout(() => { + _thisPopup.canClose = true; + $("#simplePopup .button").removeClass("hidden"); + }, 3000); + } +); + +list["deleteApeKey"] = new SimplePopup( + "deleteApeKey", + "text", + "Delete Ape Key", + [], + "Are you sure?", + "Delete", + async (_thisPopup) => { + Loader.show(); + const response = await Ape.apeKeys.delete(_thisPopup.parameters[0]); + Loader.hide(); + + if (response.status !== 200) { + return Notifications.add("Failed to delete key: " + response.message, -1); + } + + Notifications.add("Key deleted", 1); + const snap = DB.getSnapshot(); + if (snap.apeKeys) { + delete snap.apeKeys[_thisPopup.parameters[0]]; + DB.setSnapshot(snap); + } + ApeKeysPopup.show(); + }, + (_thisPopup) => { + // + }, + (_thisPopup) => { + // + } +); + +list["editApeKey"] = new SimplePopup( + "editApeKey", + "text", + "Edit Ape Key", + [ + { + placeholder: "Name", + initVal: "", + }, + ], + "", + "Edit", + async (_thisPopup, input) => { + Loader.show(); + const response = await Ape.apeKeys.update(_thisPopup.parameters[0], { + name: input, + }); + Loader.hide(); + + if (response.status !== 200) { + return Notifications.add("Failed to update key: " + response.message, -1); + } + + Notifications.add("Key updated", 1); + const snap = DB.getSnapshot(); + if (snap.apeKeys) { + snap.apeKeys[_thisPopup.parameters[0]].name = input; + DB.setSnapshot(snap); + } + ApeKeysPopup.show(); + }, + (_thisPopup) => { + // + }, + (_thisPopup) => { + // } ); @@ -726,6 +934,20 @@ $(".pageSettings #deleteAccount").on("click", () => { list["deleteAccount"].show(); }); +$("#apeKeysPopup .generateApeKey").on("click", () => { + list["generateApeKey"].show(); +}); + +$(document).on("click", "#apeKeysPopup table tbody tr .button.delete", (e) => { + const keyId = $(e.target).closest("tr").attr("keyId") as string; + list["deleteApeKey"].show([keyId]); +}); + +$(document).on("click", "#apeKeysPopup table tbody tr .button.edit", (e) => { + const keyId = $(e.target).closest("tr").attr("keyId") as string; + list["editApeKey"].show([keyId]); +}); + $(document).on( "click", ".pageSettings .section.fontFamily .button.custom", diff --git a/frontend/src/scripts/types/types.d.ts b/frontend/src/scripts/types/types.d.ts index e50b328dd..afd9c5898 100644 --- a/frontend/src/scripts/types/types.d.ts +++ b/frontend/src/scripts/types/types.d.ts @@ -241,6 +241,18 @@ declare namespace MonkeyTypes { hash?: string; } + type ApeKey = { + name: string; + enabled: boolean; + createdOn: number; + modifiedOn: number; + lastUsedOn: number; + }; + + interface ApeKeys { + [key: string]: ApeKey; + } + interface Config { theme: string; themeLight: string; @@ -403,6 +415,7 @@ declare namespace MonkeyTypes { quoteMod?: boolean; discordId?: string; config?: Config; + apeKeys?: ApeKeys; } type PartialRecord = { diff --git a/frontend/src/styles/inputs.scss b/frontend/src/styles/inputs.scss index a60fac5a4..55f9a4dad 100644 --- a/frontend/src/styles/inputs.scss +++ b/frontend/src/styles/inputs.scss @@ -10,6 +10,10 @@ textarea { font-family: var(--font); } +textarea { + resize: vertical; +} + input[type="range"] { -webkit-appearance: none; padding: 0; diff --git a/frontend/src/styles/popups.scss b/frontend/src/styles/popups.scss index 5fc36c310..9f8fedd8f 100644 --- a/frontend/src/styles/popups.scss +++ b/frontend/src/styles/popups.scss @@ -522,6 +522,112 @@ } } +#apeKeysPopup { + background: var(--bg-color); + border-radius: var(--roundness); + padding: 2rem; + display: grid; + gap: 1rem; + width: 1000px; + max-width: calc(100vw - 4rem); + // height: 100%; + // max-height: 40rem; + min-height: 18rem; + overflow-y: scroll; + grid-template-rows: max-content auto; + align-items: baseline; + gap: 1rem; + + .top { + display: grid; + grid-template-columns: 1fr auto; + .title { + font-size: 1.5rem; + color: var(--sub-color); + } + .button { + padding: 0.4rem 2rem; + } + } + + .icon-button { + justify-content: center; + } + + .keyButtons { + display: grid; + grid-auto-flow: column; + gap: 1rem; + .button { + width: 3rem; + } + } + + table { + width: 100%; + border-spacing: 0; + border-collapse: collapse; + + tr td:first-child { + text-align: center; + } + + tr.me { + td { + color: var(--main-color); + // font-weight: 900; + } + } + + td { + padding: 0.5rem 0.5rem; + } + + thead { + color: var(--sub-color); + font-size: 0.75rem; + + td { + padding: 0.5rem; + background: var(--bg-color); + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 99; + } + } + + tbody { + color: var(--text-color); + + tr:nth-child(odd) td { + background: rgba(0, 0, 0, 0.1); + } + } + + tfoot { + td { + padding: 1rem 0.5rem; + position: -webkit-sticky; + position: sticky; + bottom: -5px; + background: var(--bg-color); + color: var(--main-color); + z-index: 4; + } + } + + tr { + td:first-child { + padding-left: 1rem; + } + td:last-child { + padding-right: 1rem; + } + } + } +} + #quoteApprovePopup { background: var(--bg-color); border-radius: var(--roundness); diff --git a/frontend/static/index.html b/frontend/static/index.html index c0b3c2c8d..08f887c22 100644 --- a/frontend/static/index.html +++ b/frontend/static/index.html @@ -551,6 +551,30 @@
Submit
+ +
+

ape keys

+
+ Generate Ape Keys to access certain API endpoints. +
+
+
+ open +
+
+

reset settings