diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts index a69779afa..d4b18ffc3 100644 --- a/backend/src/api/controllers/user.ts +++ b/backend/src/api/controllers/user.ts @@ -1,4 +1,4 @@ -import _ from "lodash"; +import _, { eq } from "lodash"; import * as UserDAL from "../../dal/user"; import MonkeyError from "../../utils/error"; import Logger from "../../utils/logger"; @@ -160,9 +160,14 @@ export async function getUser( const agentLog = buildAgentLog(req); Logger.logToDb("user_data_requested", agentLog, uid); + let inboxUnreadSize = 0; + if (req.ctx.configuration.users.inbox.enabled) { + inboxUnreadSize = _.filter(userInfo.inbox, { read: false }).length; + } + const userData = { ...getRelevantUserInfo(userInfo), - inboxUnreadSize: _.filter(userInfo.inbox, { read: false }).length, + inboxUnreadSize: inboxUnreadSize, }; return new MonkeyResponse("User data retrieved", userData); @@ -520,7 +525,10 @@ export async function getInbox( const inbox = await UserDAL.getInbox(uid); - return new MonkeyResponse("Inbox retrieved", inbox); + return new MonkeyResponse("Inbox retrieved", { + inbox, + maxMail: req.ctx.configuration.users.inbox.maxMail, + }); } export async function updateInbox( diff --git a/backend/src/dal/user.ts b/backend/src/dal/user.ts index 32e03b116..5a5f857f2 100644 --- a/backend/src/dal/user.ts +++ b/backend/src/dal/user.ts @@ -818,7 +818,8 @@ function buildRewardUpdates( if (reward.type === "xp") { totalXp += isNaN(reward.item) ? 0 : reward.item; } else if (reward.type === "badge") { - newBadges.push(reward.item); + const item = _.omit(reward.item, "selected"); + newBadges.push(item); } }); diff --git a/frontend/src/styles/core.scss b/frontend/src/styles/core.scss index 7c5bb5fbc..a7c4695fa 100644 --- a/frontend/src/styles/core.scss +++ b/frontend/src/styles/core.scss @@ -213,8 +213,9 @@ key { } .pageLoading { + height: 100%; display: grid; - justify-content: center; + align-items: center; } .pageLoading, @@ -356,7 +357,7 @@ key { color: var(--sub-color); cursor: pointer; transition: background 0.125s, color 0.125s; - padding: 0.5rem; + padding: 0.5em; border-radius: var(--roundness); text-align: center; user-select: none; @@ -364,16 +365,16 @@ key { align-content: center; height: min-content; height: -moz-min-content; - line-height: 1.25rem; + line-height: 1.25em; appearance: none; border: none; font-family: inherit; - font-size: 1rem; + font-size: 1em; width: max-content; width: -moz-max-content; display: grid; grid-auto-flow: column; - gap: 0.25rem; + gap: 0.25em; text-decoration: none; .fas, diff --git a/frontend/src/styles/login.scss b/frontend/src/styles/login.scss index dd22c673c..3ae3b3620 100644 --- a/frontend/src/styles/login.scss +++ b/frontend/src/styles/login.scss @@ -4,6 +4,7 @@ gap: 1rem; justify-content: space-around; align-items: center; + height: 100%; .side { display: grid; diff --git a/frontend/src/styles/nav.scss b/frontend/src/styles/nav.scss index 1020cc335..0225f77f2 100644 --- a/frontend/src/styles/nav.scss +++ b/frontend/src/styles/nav.scss @@ -46,6 +46,7 @@ } .level { + transition: 0.125s; width: max-content; font-size: 0.65rem; line-height: 1rem; @@ -78,7 +79,7 @@ position: absolute; left: 100%; margin-left: 0.5rem; - top: 0; + top: 0.2rem; transform: translateY(-50%); font-size: 0.75rem; color: var(--main-color); @@ -97,6 +98,9 @@ } } } + &:hover .level { + background-color: var(--text-color); + } } } @@ -128,7 +132,7 @@ } #top { - grid-template-areas: "logo menu config"; + grid-template-areas: "logo menu"; line-height: 2.3rem; font-size: 2.3rem; /* text-align: center; */ @@ -136,7 +140,7 @@ padding: 0 5px; display: grid; grid-auto-flow: column; - grid-template-columns: auto 1fr auto; + grid-template-columns: auto 1fr; z-index: 2; align-items: center; gap: 0.5rem; @@ -168,13 +172,13 @@ font-size: 0.65rem; line-height: 0.65rem; color: var(--sub-color); - transition: 0.25s; + transition: color 0.25s; } position: relative; font-size: 2rem; - margin-bottom: 0.4rem; + margin-top: -0.4rem; font-family: "Lexend Deca"; - transition: 0.25s; + transition: color 0.25s; } white-space: nowrap; user-select: none; diff --git a/frontend/src/styles/popups.scss b/frontend/src/styles/popups.scss index 2021961f4..85ded4ea1 100644 --- a/frontend/src/styles/popups.scss +++ b/frontend/src/styles/popups.scss @@ -1324,3 +1324,164 @@ overflow-y: scroll; } } + +#alertsPopupWrapper { + padding: 0; + justify-content: end; + z-index: 99999999; + // padding: 2rem; + #alertsPopup { + background: var(--bg-color); + width: calc(350px + 2rem); + right: 0; + // height: calc(100vh - 4rem); + height: 100%; + top: 0; + padding: 2rem calc(1rem - 7px) 2rem 1rem; // -7px for the scrollbar + // padding: 1rem; + // border-radius: var(--roundness); + overflow: hidden; + margin-right: -10rem; + border-radius: var(--roundness) 0 0 var(--roundness); + + .separator { + background-color: var(--sub-alt-color); + height: 0.25rem; + width: 100%; + border-radius: calc(var(--roundness) / 2); + } + + .scrollWrapper { + padding: 0 1rem 0 1rem; + overflow-y: scroll; + display: grid; + gap: 2rem; + align-content: baseline; + height: 100%; + } + .accountAlerts > .title, + .notificationHistory > .title, + .psas > .title { + font-size: 1.25rem; + margin-bottom: 1rem; + color: var(--sub-color); + user-select: none; + } + .list { + display: grid; + gap: 1rem; + .nothing { + width: 100%; + color: var(--text-color); + font-size: 0.75rem; + text-align: center; + margin: 2rem 0; + } + .preloader { + width: 100%; + color: var(--main-color); + text-align: center; + font-size: 1rem; + margin: 2rem 0; + } + .item { + display: grid; + grid-template-areas: "indicator title buttons" "indicator body buttons"; + grid-template-columns: 0.25rem auto 1rem; + gap: 0.25rem 0.5rem; + .indicator { + grid-area: indicator; + background-color: var(--sub-alt-color); + width: 0.25rem; + height: 100%; + border-radius: calc(var(--roundness) / 2); + transition: 0.125s; + &.main { + background-color: var(--main-color); + } + &.error { + background-color: var(--error-color); + } + &.sub { + background-color: var(--sub-color); + } + } + .title { + grid-area: title; + font-size: 0.75rem; + color: var(--sub-color); + } + .body { + grid-area: body; + font-size: 0.75rem; + color: var(--text-color); + transition: 0.125s; + opacity: 1; + } + .buttons { + grid-area: buttons; + width: 100%; + display: grid; + grid-auto-flow: row; + gap: 0.5rem; + opacity: 0; + transition: 0.125s; + align-items: center; + align-content: center; + .button { + font-size: 0.75rem; + padding: 0.5em; + line-height: 1.25em; + height: 100%; + display: grid; + } + } + &:hover { + .buttons { + opacity: 1; + } + .body { + opacity: 1; + } + } + } + } + .psas .list .item { + grid-template-areas: "indicator body"; + grid-template-columns: 0.25rem auto; + .body { + opacity: 1; + } + } + .notificationHistory .list .item { + grid-template-areas: "indicator title" "indicator body"; + grid-template-columns: 0.25rem auto; + .title { + font-size: 0.75rem; + color: var(--sub-color); + } + .body { + opacity: 1; + } + } + .accountAlerts { + .title { + display: grid; + grid-template-columns: 1fr auto; + } + .list .item { + grid-template-areas: "indicator timestamp buttons" "indicator title buttons" "indicator body buttons"; + .timestamp { + grid-area: timestamp; + font-size: 0.6rem; + color: var(--sub-color); + opacity: 0.5; + } + .rewards { + overflow: hidden; + margin-top: 0.35rem; + } + } + } + } +} diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index 030028fe1..42811aa0f 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -916,6 +916,117 @@ } } +#mobileTestConfig { + font-size: 0.75rem; + padding: 0.25rem 1rem; + background: var(--sub-alt-color); + border-radius: var(--roundness); + width: max-content; + justify-self: center; + display: none; + height: max-content; +} + +#testConfig { + display: grid; + font-size: 0.75rem; + gap: 0.5rem; + grid-auto-flow: column; + height: max-content; + width: 100%; + justify-content: space-around; + transition: 0.125s; + margin-bottom: 1rem; + .puncAndNum { + // transition: 0.25s cubic-bezier(0.37, 0, 0.63, 1); + overflow: hidden; + max-width: 15rem; + opacity: 1; + } + .row { + display: flex; + // padding: 0 0.5rem; + background: var(--sub-alt-color); + border-radius: var(--roundness); + // width: max-content; + } + .spacer { + height: auto; + width: 0.25rem; + border-radius: calc(var(--roundness) / 2); + background: var(--bg-color); + margin: 0.5rem 0; + transition: 0.25s; + &.scrolled { + width: 0; + margin: auto 0; + } + } + + .wordCount, + .time { + .textButton { + line-height: 1rem; + span { + height: 1.1em; + } + } + } + + .mode, + .time, + .wordCount, + .puncAndNum, + .quoteLength, + .customText, + .zen { + display: grid; + grid-auto-flow: column; + + .textButton { + transition: 0.125s; + padding: 0.75rem 0.5rem 0.7rem 0.5rem; + + &:first-child { + padding: 0.75rem 0.5rem 0.7rem 1rem; + } + &:last-child { + padding: 0.75rem 1rem 0.7rem 0.5rem; + } + &:only-child { + padding: 0.75rem 1rem 0.7rem 1rem; + } + } + } + .time, + .wordCount, + .customText, + .zen, + .quoteLength, + .puncAndNum { + // width: 100%; + justify-content: center; + overflow: hidden; + } + .customText { + display: grid; + } + &.focus { + opacity: 0; + } +} + +.pageTest { + height: 100%; + display: grid; + grid-template-rows: 1fr auto 1fr; +} + +#menu { + width: 100%; + grid-template-columns: auto auto auto 1fr auto; +} + .pageTest { #wordsWrapper { position: relative; diff --git a/frontend/src/styles/z_media-queries.scss b/frontend/src/styles/z_media-queries.scss index e18157e01..48f36ed2f 100644 --- a/frontend/src/styles/z_media-queries.scss +++ b/frontend/src/styles/z_media-queries.scss @@ -159,6 +159,29 @@ } @media only screen and (max-width: 900px) { + #testConfig { + .mode, + .time, + .wordCount, + .puncAndNum, + .customText, + .zen, + .quoteLength { + .textButton { + padding: 0.75rem 0.3rem 0.7rem 0.3rem; + + &:first-child { + padding: 0.75rem 0.3rem 0.7rem 0.6rem; + } + &:last-child { + padding: 0.75rem 0.6rem 0.7rem 0.3rem; + } + &:only-child { + padding: 0.75rem 0.6rem 0.7rem 0.6rem; + } + } + } + } .ad.ad-h { display: none; } @@ -280,32 +303,97 @@ } @media only screen and (max-width: 800px) { + #testConfig { + .spacer { + display: none; + } + .row { + display: grid; + justify-items: center; + padding: 0.25rem 1rem; + } + .mode, + .time, + .wordCount, + .puncAndNum, + .customText, + .zen, + .quoteLength { + .textButton { + padding: 0.5rem 0.75rem 0.45rem 0.75rem; + + &:first-child { + padding: 0.5rem 0.75rem 0.45rem 0.75rem; + } + &:last-child { + padding: 0.5rem 0.75rem 0.45rem 0.75rem; + } + &:only-child { + padding: 0.5rem 0.75rem 0.45rem 0.75rem; + } + } + } + } + + #top { + .logo { + .text { + font-size: 1.9rem !important; + } + .bottom { + font-size: 1.9rem; + line-height: 1.9rem; + margin-top: 0; + } + } + #menu { + gap: 0.1rem; + .textButton { + padding: 0.25rem; + font-size: 0.8rem; + line-height: 0.8rem; + &.account { + margin-top: 0.05rem; + .text { + display: none; + } + .avatar { + width: 1rem; + height: 1rem; + } + .level { + line-height: 0.8rem; + } + } + } + } + } .pageSettings .settingsGroup.quickNav .links { grid-auto-flow: unset; grid-template-columns: 1fr 1fr 1fr; justify-items: center; } #centerContent { - #top { - grid-template-areas: - "logo config" - "menu config"; - grid-template-columns: auto auto; - .logo { - margin-bottom: 0; - } - } + // #top { + // grid-template-areas: + // "logo config" + // "menu config"; + // grid-template-columns: auto auto; + // .logo { + // margin-bottom: 0; + // } + // } - #menu { - gap: 0.5rem; - font-size: 0.8rem; - line-height: 0.8rem; - margin-top: -0.5rem; + // #menu { + // gap: 0.5rem; + // font-size: 0.8rem; + // line-height: 0.8rem; + // margin-top: -0.5rem; - .textButton { - padding: 0.25rem; - } - } + // .textButton { + // padding: 0.25rem; + // } + // } } #contactPopupWrapper #contactPopup .buttons { @@ -640,6 +728,9 @@ } } } + #testModesNotice { + font-size: 0.75rem; + } #cookiePopupWrapper { #cookiePopup, .extensionMessage { @@ -728,27 +819,15 @@ } #top { - align-items: self-end; .logo { - .icon { - width: 1.5rem !important; - } + grid-template-columns: auto; .text { - font-size: 1.5rem !important; - margin-bottom: 0.3rem !important; - } - .bottom { - font-size: 1.75rem; - line-height: 1.75rem; - margin-top: 0; - } - .top { display: none; } } #menu { .textButton { - padding: 0; + // padding: 0; } } } @@ -770,7 +849,7 @@ } #centerContent { #top { - grid-template-columns: 1fr auto; + // grid-template-columns: 1fr auto; .desktopConfig { display: none; } @@ -778,6 +857,7 @@ display: block; } } + gap: 1rem; padding: 1rem; } #middle { @@ -806,6 +886,16 @@ } } +@media only screen and (max-width: 450px) { + #testConfig { + display: none; + } + + #mobileTestConfig { + display: block; + } +} + @media only screen and (max-width: 400px) { #top .config { grid-gap: 0.25rem; @@ -888,6 +978,16 @@ } } +@media (pointer: coarse) and (max-width: 800px) { + #testConfig { + display: none; + } + + #mobileTestConfig { + display: block; + } +} + @media (hover: none) and (pointer: coarse) { #commandLineMobileButton { display: block !important; diff --git a/frontend/src/ts/account/sign-out-button.ts b/frontend/src/ts/account/sign-out-button.ts deleted file mode 100644 index 4c85d266e..000000000 --- a/frontend/src/ts/account/sign-out-button.ts +++ /dev/null @@ -1,46 +0,0 @@ -export function show(): void { - // $(".signOut").removeClass("hidden").css("opacity", 1); - $(".signOut") - .stop(true, true) - .removeClass("hidden") - .css({ - opacity: 0, - transition: "0s", - }) - .animate( - { - opacity: 1, - }, - 125, - () => { - $(".signOut").css({ transition: "0.25s" }); - } - ); -} - -export function hide(): void { - $(".signOut") - .stop(true, true) - .css({ - opacity: 1, - transition: "0s", - }) - .animate( - { - opacity: 0, - }, - 125, - () => { - $(".signOut").css({ transition: "0.25s" }); - $(".signOut").addClass("hidden"); - } - ); - // $(".signOut").css("opacity", 0).addClass("hidden"); -} - -// $("#liveWpm").removeClass("hidden").css("opacity", 0).animate( -// { -// opacity: Config.timerOpacity, -// }, -// 125 -// ); diff --git a/frontend/src/ts/ape/endpoints/users.ts b/frontend/src/ts/ape/endpoints/users.ts index 1f88083dd..67ff8e79e 100644 --- a/frontend/src/ts/ape/endpoints/users.ts +++ b/frontend/src/ts/ape/endpoints/users.ts @@ -192,4 +192,19 @@ export default class Users { }, }); } + + async getInbox(): Promise { + return await this.httpClient.get(`${BASE_PATH}/inbox`); + } + + async updateInbox(options: { + mailIdsToDelete?: string[]; + mailIdsToMarkRead?: string[]; + }): Promise { + const payload = { + mailIdsToDelete: options.mailIdsToDelete, + mailIdsToMarkRead: options.mailIdsToMarkRead, + }; + return await this.httpClient.patch(`${BASE_PATH}/inbox`, { payload }); + } } diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts index dded736a8..641a4801a 100644 --- a/frontend/src/ts/config.ts +++ b/frontend/src/ts/config.ts @@ -96,11 +96,6 @@ export function setNumbers(numb: boolean, nosave?: boolean): boolean { numb = false; } config.numbers = numb; - if (!config.numbers) { - $("#top .config .numbersMode .textButton").removeClass("active"); - } else { - $("#top .config .numbersMode .textButton").addClass("active"); - } saveToLocalStorage("numbers", nosave); ConfigEvent.dispatch("numbers", config.numbers); @@ -115,11 +110,6 @@ export function setPunctuation(punc: boolean, nosave?: boolean): boolean { punc = false; } config.punctuation = punc; - if (!config.punctuation) { - $("#top .config .punctuationMode .textButton").removeClass("active"); - } else { - $("#top .config .punctuationMode .textButton").addClass("active"); - } saveToLocalStorage("punctuation", nosave); ConfigEvent.dispatch("punctuation", config.punctuation); @@ -982,15 +972,8 @@ export function setTimeConfig( const newTime = isNaN(time) || time < 0 ? DefaultConfig.time : time; - $("#top .config .time .textButton").removeClass("active"); - - const timeCustom = ![15, 30, 60, 120].includes(newTime) ? "custom" : newTime; - config.time = newTime; - $("#top .config .time .textButton[timeConfig='" + timeCustom + "']").addClass( - "active" - ); saveToLocalStorage("time", nosave); ConfigEvent.dispatch("time", config.time); @@ -1035,12 +1018,6 @@ export function setQuoteLength( } } // if (!nosave) setMode("quote", nosave); - $("#top .config .quoteLength .textButton").removeClass("active"); - config.quoteLength.forEach((ql) => { - $( - "#top .config .quoteLength .textButton[quoteLength='" + ql + "']" - ).addClass("active"); - }); saveToLocalStorage("quoteLength", nosave); ConfigEvent.dispatch("quoteLength", config.quoteLength); @@ -1056,17 +1033,8 @@ export function setWordCount( const newWordCount = wordCount < 0 || wordCount > 100000 ? DefaultConfig.words : wordCount; - $("#top .config .wordCount .textButton").removeClass("active"); - - const wordCustom = ![10, 25, 50, 100, 200].includes(newWordCount) - ? "custom" - : newWordCount; - config.words = newWordCount; - $( - "#top .config .wordCount .textButton[wordCount='" + wordCustom + "']" - ).addClass("active"); saveToLocalStorage("words", nosave); ConfigEvent.dispatch("words", config.words); diff --git a/frontend/src/ts/constants/default-snapshot.ts b/frontend/src/ts/constants/default-snapshot.ts index e26b81eb3..861caa452 100644 --- a/frontend/src/ts/constants/default-snapshot.ts +++ b/frontend/src/ts/constants/default-snapshot.ts @@ -27,5 +27,6 @@ export const defaultSnap: MonkeyTypes.Snapshot = { addedAt: 0, filterPresets: [], xp: 0, + inboxUnreadSize: 0, streak: 0, }; diff --git a/frontend/src/ts/controllers/account-controller.ts b/frontend/src/ts/controllers/account-controller.ts index 46cfb1411..f0f3b655f 100644 --- a/frontend/src/ts/controllers/account-controller.ts +++ b/frontend/src/ts/controllers/account-controller.ts @@ -20,6 +20,7 @@ import * as TagController from "./tag-controller"; import * as ResultTagsPopup from "../popups/result-tags-popup"; import * as URLHandler from "../utils/url-handler"; import * as Account from "../pages/account"; +import * as Alerts from "../elements/alerts"; import { EmailAuthProvider, GoogleAuthProvider, @@ -238,8 +239,9 @@ export async function loadUser(user: UserType): Promise { if ((await getDataAndInit()) === false) { signOut(); } - const { discordId, discordAvatar, xp } = DB.getSnapshot(); + const { discordId, discordAvatar, xp, inboxUnreadSize } = DB.getSnapshot(); AccountButton.update(xp, discordId, discordAvatar); + Alerts.setBellButtonColored(inboxUnreadSize > 0); // var displayName = user.displayName; // var email = user.email; // var emailVerified = user.emailVerified; @@ -249,6 +251,8 @@ export async function loadUser(user: UserType): Promise { // var providerData = user.providerData; LoginPage.hidePreloader(); + $("#top .signInOut .icon").html(``); + // showFavouriteThemesAtTheTop(); if (TestLogic.notSignedInLastResult !== null) { @@ -274,8 +278,12 @@ const authListener = Auth.onAuthStateChanged(async function (user) { const hash = window.location.hash; console.log(`auth state changed, user ${user ? true : false}`); if (user) { + $("#top .signInOut .icon").html( + `` + ); await loadUser(user); } else { + $("#top .signInOut .icon").html(``); if (window.location.pathname == "/account") { window.history.replaceState("", "", "/login"); } @@ -472,6 +480,9 @@ export function signOut(): void { DB.setSnapshot(defaultSnap); $(".pageLogin .button").removeClass("disabled"); $(".pageLogin input").prop("disabled", false); + $("#top .signInOut .icon").html( + `` + ); hideFavoriteQuoteLength(); }) .catch(function (error) { @@ -643,8 +654,12 @@ $(".pageLogin .login .button.signInWithGoogle").on("click", () => { // signInWithGitHub(); // }); -$(".signOut").on("click", () => { - signOut(); +$("#top .signInOut").on("click", () => { + if (Auth.currentUser) { + signOut(); + } else { + navigate("/login"); + } }); $(".pageLogin .register input").keyup((e) => { diff --git a/frontend/src/ts/controllers/badge-controller.ts b/frontend/src/ts/controllers/badge-controller.ts index 46b1ed59e..26d472a9e 100644 --- a/frontend/src/ts/controllers/badge-controller.ts +++ b/frontend/src/ts/controllers/badge-controller.ts @@ -120,3 +120,7 @@ export function getHTMLById( badge.icon ? `` : "" }${noText ? "" : `
${badge.name}
`}`; } + +export function getById(id: number): MonkeyTypes.UserBadge { + return badges[id]; +} diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index 327aa5a5f..1aa952eac 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -96,6 +96,7 @@ export async function initSnapshot(): Promise< snap.addedAt = userData.addedAt; snap.inventory = userData.inventory; snap.xp = userData.xp ?? 0; + snap.inboxUnreadSize = userData.inboxUnreadSize ?? 0; snap.streak = userData?.streak?.length ?? 0; if (userData.lbMemory?.time15 || userData.lbMemory?.time60) { @@ -815,6 +816,17 @@ export function addXp(xp: number): void { setSnapshot(snapshot); } +export function addBadge(badge: MonkeyTypes.Badge): void { + const snapshot = getSnapshot(); + if (snapshot.inventory === undefined) { + snapshot.inventory = { + badges: [], + }; + } + snapshot.inventory.badges.push(badge); + setSnapshot(snapshot); +} + export function setStreak(streak: number): void { const snapshot = getSnapshot(); snapshot.streak = streak; diff --git a/frontend/src/ts/elements/account-button.ts b/frontend/src/ts/elements/account-button.ts index b3ad5aa0c..0357a53da 100644 --- a/frontend/src/ts/elements/account-button.ts +++ b/frontend/src/ts/elements/account-button.ts @@ -143,7 +143,7 @@ export async function update( export async function updateXpBar( currentXp: number, addedXp: number, - breakdown: Record + breakdown?: Record ): Promise { skipBreakdown = false; const startingLevel = Misc.getLevel(currentXp); @@ -176,8 +176,12 @@ export async function updateXpBar( async function animateXpBreakdown( addedXp: number, - breakdown: Record + breakdown?: Record ): Promise { + if (!breakdown) { + $("#menu .xpBar .xpGain").text(`+${addedXp}`); + return; + } const delay = 1000; let total = 0; const xpGain = $("#menu .xpBar .xpGain"); diff --git a/frontend/src/ts/elements/alerts.ts b/frontend/src/ts/elements/alerts.ts new file mode 100644 index 000000000..ed12c550f --- /dev/null +++ b/frontend/src/ts/elements/alerts.ts @@ -0,0 +1,371 @@ +import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict"; +import Ape from "../ape"; +import { Auth } from "../firebase"; +import * as AccountButton from "../elements/account-button"; +import * as DB from "../db"; +import * as NotificationEvent from "../observables/notification-event"; +import * as BadgeController from "../controllers/badge-controller"; +import * as Notifications from "../elements/notifications"; + +let accountAlerts: MonkeyTypes.MonkeyMail[] = []; +let maxMail = 0; +let mailToMarkRead: string[] = []; +let mailToDelete: string[] = []; + +export function hide(): void { + if (!$("#alertsPopupWrapper").hasClass("hidden")) { + setBellButtonColored(false); + + let mailUpdatedPromiseResolve: (value?: unknown) => void; + const mailUpdatedPromise = new Promise((resolve) => { + mailUpdatedPromiseResolve = resolve; + }); + + const badgesClaimed: string[] = []; + let totalXpClaimed = 0; + if (mailToMarkRead.length > 0 || mailToDelete.length > 0) { + Ape.users + .updateInbox({ + mailIdsToMarkRead: + mailToMarkRead.length > 0 ? mailToMarkRead : undefined, + mailIdsToDelete: mailToDelete.length > 0 ? mailToDelete : undefined, + }) + .then(async (updateResponse) => { + const status = (await updateResponse).status; + const message = (await updateResponse).message; + if (status !== 200) { + Notifications.add(`Failed to update inbox: ${message}`, -1); + return; + } else { + const rewardsClaimed = accountAlerts + .filter((ie) => { + return ie.rewards.length > 0 && mailToMarkRead.includes(ie.id); + }) + .map((ie) => ie.rewards) + .reduce(function (a, b) { + return a.concat(b); + }, []); + + for (const r of rewardsClaimed) { + if (r.type === "xp") { + totalXpClaimed += r.item as number; + } else if (r.type === "badge") { + const badge = BadgeController.getById(r.item.id); + badgesClaimed.push(badge.name); + DB.addBadge(r.item); + } + } + } + mailUpdatedPromiseResolve(); + }); + } + + $("#alertsPopup").animate( + { + marginRight: "-10rem", + }, + 100, + "easeInCubic" + ); + $("#alertsPopupWrapper") + .stop(true, true) + .css("opacity", 1) + .animate( + { + opacity: 0, + }, + 100, + () => { + mailUpdatedPromise.then(() => { + if (badgesClaimed.length > 0) { + Notifications.add( + `New badge${ + badgesClaimed.length > 1 ? "s" : "" + } unlocked: ${badgesClaimed.join(", ")}`, + 1, + 5, + "Reward", + "gift" + ); + } + if (totalXpClaimed > 0) { + const snapxp = DB.getSnapshot().xp; + AccountButton.updateXpBar(snapxp, totalXpClaimed); + DB.addXp(totalXpClaimed); + } + }); + $("#alertsPopupWrapper").addClass("hidden"); + } + ); + } +} + +export async function show(): Promise { + if ($("#alertsPopupWrapper").hasClass("hidden")) { + $("#alertsPopup").css("marginRight", "-10rem").animate( + { + marginRight: 0, + }, + 100, + "easeOutCubic" + ); + + if (Auth.currentUser) { + $("#alertsPopup .accountAlerts").removeClass("hidden"); + $("#alertsPopup .separator.accountSeparator").removeClass("hidden"); + $("#alertsPopup .accountAlerts .list").html(` +
`); + } else { + $("#alertsPopup .accountAlerts").addClass("hidden"); + $("#alertsPopup .separator.accountSeparator").addClass("hidden"); + } + + accountAlerts = []; + mailToDelete = []; + mailToMarkRead = []; + + $("#alertsPopupWrapper") + .stop(true, true) + .css("opacity", 0) + .removeClass("hidden") + .animate( + { + opacity: 1, + }, + 100, + () => { + if (Auth.currentUser) { + getAccountAlerts(); + } + } + ); + } +} + +async function getAccountAlerts(): Promise { + const inboxResponse = await Ape.users.getInbox(); + + $("#alertsPopup .accountAlerts .list").empty(); + + if (inboxResponse.status === 503) { + $("#alertsPopup .accountAlerts .list").html(` +
+ Account inboxes are temporarily unavailable. +
+ `); + return; + } else if (inboxResponse.status !== 200) { + $("#alertsPopup .accountAlerts .list").html(` +
+ Error getting inbox: ${inboxResponse.message} Please try again later. +
+ `); + return; + } + const inboxData = inboxResponse.data as { + inbox: MonkeyTypes.MonkeyMail[]; + maxMail: number; + }; + + accountAlerts = inboxData.inbox; + + if (accountAlerts.length === 0) { + $("#alertsPopup .accountAlerts .list").html(` +
+ Nothing to show +
+ `); + return; + } + + maxMail = inboxData.maxMail; + + updateInboxSize(); + + for (const ie of accountAlerts) { + if (!ie.read && ie.rewards.length == 0) { + mailToMarkRead.push(ie.id); + } + + let rewardsString = ""; + + if (ie.rewards.length > 0 && ie.read === false) { + rewardsString = `
+ + ${ie.rewards.length} +
`; + } + + $("#alertsPopup .accountAlerts .list").append(` + +
+
+
${formatDistanceToNowStrict( + new Date(ie.timestamp) + )} ago
+
${ie.subject}
+
+ ${ie.body}\n\n${rewardsString} +
+
+ ${ + ie.rewards.length > 0 && ie.read === false + ? `
` + : `` + } + ${ + (ie.rewards.length > 0 && ie.read === true) || + ie.rewards.length == 0 + ? `
` + : `` + } +
+
+ + `); + } +} + +export function addPSA(message: string, level: number): void { + if ($("#alertsPopup .psas .list .nothing").length > 0) { + $("#alertsPopup .psas .list").empty(); + } + + let levelClass = ""; + if (level === -1) { + levelClass = "error"; + } else if (level === 1) { + levelClass = "main"; + } else if (level === 0) { + levelClass = "sub"; + } + $("#alertsPopup .psas .list").prepend(` +
+
+
+ ${message} +
+
+ `); +} + +function addNotification( + message: string, + level: number, + customTitle?: string +): void { + if ($("#alertsPopup .notificationHistory .list .nothing").length > 0) { + $("#alertsPopup .notificationHistory .list").empty(); + } + + let title = "Notice"; + let levelClass = "sub"; + if (level === -1) { + levelClass = "error"; + title = "Error"; + } else if (level === 1) { + levelClass = "main"; + title = "Success"; + } + + if (customTitle) { + title = customTitle; + } + + $("#alertsPopup .notificationHistory .list").prepend(` +
+
+
${title}
+
+ ${message} +
+
+ `); + + if ($("#alertsPopup .notificationHistory .list").length > 25) { + $("#alertsPopup .notificationHistory .list .item:last").remove(); + } +} + +export function setBellButtonColored(tf: boolean): void { + if (tf) { + $("#top #menu .showAlerts").addClass("active"); + } else { + $("#top #menu .showAlerts").removeClass("active"); + } +} + +function updateInboxSize(): void { + $("#alertsPopup .accountAlerts .title .right").text( + `${accountAlerts.length}/${maxMail}` + ); +} + +$("#top #menu .showAlerts").on("click", () => { + show(); +}); + +$("#alertsPopupWrapper").on("mousedown", (e) => { + if ($(e.target).attr("id") === "alertsPopupWrapper") { + hide(); + } +}); + +$("#alertsPopup .accountAlerts .list").on( + "click", + ".item .buttons .deleteAlert", + (e) => { + const id = $(e.currentTarget).closest(".item").attr("data-id") as string; + mailToDelete.push(id); + $(e.currentTarget).closest(".item").remove(); + accountAlerts = accountAlerts.filter((ie) => ie.id !== id); + if (accountAlerts.length === 0) { + $("#alertsPopup .accountAlerts .list").html(` +
+ Nothing to show +
+ `); + } + updateInboxSize(); + } +); + +$("#alertsPopup .accountAlerts .list").on( + "click", + ".item .buttons .markReadAlert", + (e) => { + const id = $(e.currentTarget).closest(".item").attr("data-id") as string; + mailToMarkRead.push(id); + const item = $(e.currentTarget).closest(".item"); + + item.find(".indicator").removeClass("main"); + item.find(".buttons").empty(); + item + .find(".buttons") + .append( + `
` + ); + item.find(".rewards").animate( + { + opacity: 0, + height: 0, + marginTop: 0, + }, + 250, + "easeOutCubic", + () => { + item.find(".rewards").remove(); + } + ); + } +); + +$(document).on("keydown", (e) => { + if (e.key === "Escape" && !$("#alertsPopupWrapper").hasClass("hidden")) { + hide(); + } +}); + +NotificationEvent.subscribe((message, level, customTitle) => { + addNotification(message, level, customTitle); +}); diff --git a/frontend/src/ts/elements/notifications.ts b/frontend/src/ts/elements/notifications.ts index 8117c97a9..5ab64790b 100644 --- a/frontend/src/ts/elements/notifications.ts +++ b/frontend/src/ts/elements/notifications.ts @@ -1,6 +1,8 @@ import { debounce } from "throttle-debounce"; import * as Misc from "../utils/misc"; import * as BannerEvent from "../observables/banner-event"; +// import * as Alerts from "./alerts"; +import * as NotificationEvent from "../observables/notification-event"; function updateMargin(): void { console.log("updating margin"); @@ -231,7 +233,8 @@ export function add( closeCallback?: () => void, allowHTML?: boolean ): void { - // notificationHistory.push( + NotificationEvent.dispatch(message, level, customTitle); + new Notification( "notification", message, @@ -242,7 +245,6 @@ export function add( closeCallback, allowHTML ).show(); - // ); } export function addBanner( @@ -253,7 +255,6 @@ export function addBanner( closeCallback?: () => void, allowHTML?: boolean ): void { - // notificationHistory.push( new Notification( "banner", message, @@ -264,7 +265,6 @@ export function addBanner( closeCallback, allowHTML ).show(); - // ); } const debouncedMarginUpdate = debounce(100, updateMargin); diff --git a/frontend/src/ts/elements/psa.ts b/frontend/src/ts/elements/psa.ts index 05f527e6b..ba8e3a0f3 100644 --- a/frontend/src/ts/elements/psa.ts +++ b/frontend/src/ts/elements/psa.ts @@ -2,6 +2,7 @@ import Ape from "../ape"; import { secondsToString } from "../utils/misc"; import * as Notifications from "./notifications"; import format from "date-fns/format"; +import * as Alerts from "./alerts"; function clearMemory(): void { window.localStorage.setItem("confirmedPSAs", JSON.stringify([])); @@ -64,10 +65,6 @@ export async function show(): Promise { } const localmemory = getMemory(); latest.forEach((psa) => { - if (localmemory.includes(psa._id) && (psa.sticky ?? false) === false) { - return; - } - if (psa.date) { const dateObj = new Date(psa.date); const diff = psa.date - Date.now(); @@ -90,6 +87,12 @@ export async function show(): Promise { ); } + Alerts.addPSA(psa.message, psa.level ?? -1); + + if (localmemory.includes(psa._id) && (psa.sticky ?? false) === false) { + return; + } + Notifications.addBanner( psa.message, psa.level, diff --git a/frontend/src/ts/observables/notification-event.ts b/frontend/src/ts/observables/notification-event.ts new file mode 100644 index 000000000..ea4fdf9cd --- /dev/null +++ b/frontend/src/ts/observables/notification-event.ts @@ -0,0 +1,26 @@ +type SubscribeFunction = ( + message: string, + level: number, + customTitle?: string +) => void; + +const subscribers: SubscribeFunction[] = []; + +export function subscribe(fn: SubscribeFunction): void { + subscribers.push(fn); +} + +export function dispatch( + message: string, + level: number, + customTitle?: string +): void { + subscribers.forEach((fn) => { + try { + fn(message, level, customTitle); + } catch (e) { + console.error("Notification event subscriber threw an error"); + console.error(e); + } + }); +} diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 00f5b6faf..bc75eaa52 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -8,12 +8,10 @@ import * as AllTimeStats from "../account/all-time-stats"; import * as PbTables from "../account/pb-tables"; import * as LoadingPage from "./loading"; import * as Focus from "../test/focus"; -import * as SignOutButton from "../account/sign-out-button"; import * as TodayTracker from "../test/today-tracker"; import * as Notifications from "../elements/notifications"; import Page from "./page"; import * as Misc from "../utils/misc"; -import * as ActivePage from "../states/active-page"; import * as Profile from "../elements/profile"; import format from "date-fns/format"; @@ -842,9 +840,6 @@ function fillContent(): void { applyHistorySmoothing(); ChartController.accountActivity.updateColors(); LoadingPage.updateBar(100, true); - setTimeout(() => { - if (ActivePage.get() == "account") SignOutButton.show(); - }, 125); Focus.set(false); Misc.swapElements( $(".pageAccount .preloader"), @@ -1092,14 +1087,13 @@ export const page = new Page( $(".page.pageAccount"), "/account", async () => { - SignOutButton.hide(); + // }, async () => { reset(); }, async () => { await update(); - // SignOutButton.show(); }, async () => { // diff --git a/frontend/src/ts/pages/test.ts b/frontend/src/ts/pages/test.ts index ba3724a96..e20d14fe5 100644 --- a/frontend/src/ts/pages/test.ts +++ b/frontend/src/ts/pages/test.ts @@ -2,7 +2,6 @@ import Config from "../config"; import * as TestStats from "../test/test-stats"; import * as TestUI from "../test/test-ui"; import * as ManualRestart from "../test/manual-restart-tracker"; -import * as TestConfig from "../test/test-config"; import * as TestLogic from "../test/test-logic"; import * as Funbox from "../test/funbox"; import Page from "./page"; @@ -15,7 +14,6 @@ export const page = new Page( async () => { TestLogic.restart(); Funbox.clear(); - TestConfig.hide(); $("#wordsInput").focusout(); }, async () => { @@ -23,7 +21,6 @@ export const page = new Page( }, async () => { updateTestPageAds(false); - TestConfig.show(); TestStats.resetIncomplete(); ManualRestart.set(); TestLogic.restart({ diff --git a/frontend/src/ts/popups/custom-test-duration-popup.ts b/frontend/src/ts/popups/custom-test-duration-popup.ts index 6d485b87f..a25a178e1 100644 --- a/frontend/src/ts/popups/custom-test-duration-popup.ts +++ b/frontend/src/ts/popups/custom-test-duration-popup.ts @@ -139,7 +139,7 @@ $("#customTestDurationPopup .button").on("click", () => { apply(); }); -$(document).on("click", "#top .config .time .textButton", (e) => { +$(document).on("click", "#testConfig .time .textButton", (e) => { const mode = $(e.currentTarget).attr("timeConfig"); if (mode == "custom") { show(); diff --git a/frontend/src/ts/popups/custom-text-popup.ts b/frontend/src/ts/popups/custom-text-popup.ts index 88e2b8457..09bbd22e5 100644 --- a/frontend/src/ts/popups/custom-text-popup.ts +++ b/frontend/src/ts/popups/custom-text-popup.ts @@ -203,7 +203,7 @@ $(document).on("click", `${popup} .wordfilter`, () => { WordFilterPopup.show(); }); -$(document).on("click", "#top .config .customText .textButton", () => { +$(document).on("click", "#testConfig .customText .textButton", () => { show(); }); diff --git a/frontend/src/ts/popups/custom-word-amount-popup.ts b/frontend/src/ts/popups/custom-word-amount-popup.ts index 433899884..e0d4a00bd 100644 --- a/frontend/src/ts/popups/custom-word-amount-popup.ts +++ b/frontend/src/ts/popups/custom-word-amount-popup.ts @@ -71,7 +71,7 @@ $("#customWordAmountPopup .button").on("click", () => { apply(); }); -$(document).on("click", "#top .config .wordCount .textButton", (e) => { +$(document).on("click", "#testConfig .wordCount .textButton", (e) => { const wrd = $(e.currentTarget).attr("wordCount"); if (wrd == "custom") { show(); diff --git a/frontend/src/ts/popups/mobile-test-config-popup.ts b/frontend/src/ts/popups/mobile-test-config-popup.ts index 558dad496..518ca8434 100644 --- a/frontend/src/ts/popups/mobile-test-config-popup.ts +++ b/frontend/src/ts/popups/mobile-test-config-popup.ts @@ -89,7 +89,7 @@ $("#mobileTestConfigPopupWrapper").on("click", (e) => { } }); -$("#top .mobileConfig").on("click", () => { +$("#mobileTestConfig").on("click", () => { showPopup(); }); diff --git a/frontend/src/ts/popups/quote-search-popup.ts b/frontend/src/ts/popups/quote-search-popup.ts index 2f9f7d6c3..271bc1f6c 100644 --- a/frontend/src/ts/popups/quote-search-popup.ts +++ b/frontend/src/ts/popups/quote-search-popup.ts @@ -394,7 +394,7 @@ $(document).on("click", "#toggleShowFavorites", (e) => { searchForQuotes(); }); -$(document).on("click", "#top .config .quoteLength .textButton", (e) => { +$(document).on("click", "#testConfig .quoteLength .textButton", (e) => { const len = $(e.currentTarget).attr("quoteLength") ?? (0 as number); if (len == -2) { show(); diff --git a/frontend/src/ts/test/focus.ts b/frontend/src/ts/test/focus.ts index 0f55b4d4c..5957985fc 100644 --- a/frontend/src/ts/test/focus.ts +++ b/frontend/src/ts/test/focus.ts @@ -12,6 +12,7 @@ export function set(foc: boolean, withCursor = false): void { $("#bottom").addClass("focus"); if (!withCursor) $("body").css("cursor", "none"); $("#middle").addClass("focus"); + $("#testConfig").addClass("focus"); $("#bannerCenter").addClass("focus"); $("#capsWarning").addClass("focus"); $("#ad-vertical-right-wrapper").addClass("focus"); @@ -25,6 +26,7 @@ export function set(foc: boolean, withCursor = false): void { $("#bottom").removeClass("focus"); $("body").css("cursor", "default"); $("#middle").removeClass("focus"); + $("#testConfig").removeClass("focus"); $("#bannerCenter").removeClass("focus"); $("#capsWarning").removeClass("focus"); $("#app").removeClass("focus"); diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index c46f47a08..2db7c8320 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -15,6 +15,7 @@ import * as Notifications from "../elements/notifications"; import * as Loader from "../elements/loader"; import QuotesController from "../controllers/quotes-controller"; import * as AdController from "../controllers/ad-controller"; +import * as TestConfig from "./test-config"; import { Chart } from "chart.js"; import { Auth } from "../firebase"; @@ -711,6 +712,8 @@ export async function update( .animate({ scrollTop: 0 }, 250); } + TestConfig.hide(); + Misc.swapElements( $("#typingTest"), $("#result"), diff --git a/frontend/src/ts/test/test-config.ts b/frontend/src/ts/test/test-config.ts index 995b0d648..707e93344 100644 --- a/frontend/src/ts/test/test-config.ts +++ b/frontend/src/ts/test/test-config.ts @@ -1,5 +1,5 @@ import * as ConfigEvent from "../observables/config-event"; -import * as Misc from "../utils/misc"; +// import * as Misc from "../utils/misc"; // export function show() { // $("#top .config").removeClass("hidden").css("opacity", 1); @@ -10,159 +10,312 @@ import * as Misc from "../utils/misc"; // } export function show(): void { - $("#top .config") - .css("transition", "unset") - .stop(true, true) - .removeClass("hidden") - .css("opacity", 0) - .animate( - { - opacity: 1, - }, - 125, - () => { - $("#top .config").css("transition", "0.125s"); - } - ); + $("#testConfig").removeClass("invisible"); } export function hide(): void { - $("#top .config") - .css("transition", "unset") - .stop(true, true) - .css("opacity", 1) - .animate( - { - opacity: 0, - }, - 125, - () => { - $("#top .config").addClass("hidden").css("transition", "0.125s"); - } - ); + $("#testConfig").addClass("invisible"); } -export function update( +export async function update( previous: MonkeyTypes.Mode, current: MonkeyTypes.Mode -): void { +): Promise { if (previous === current) return; - $("#top .config .mode .textButton").removeClass("active"); - $("#top .config .mode .textButton[mode='" + current + "']").addClass( - "active" - ); + $("#testConfig .mode .textButton").removeClass("active"); + $("#testConfig .mode .textButton[mode='" + current + "']").addClass("active"); - if (current == "time") { - // $("#top .config .wordCount").addClass("hidden"); - // $("#top .config .time").removeClass("hidden"); - // $("#top .config .customText").addClass("hidden"); - $("#top .config .punctuationMode").removeClass("disabled"); - $("#top .config .numbersMode").removeClass("disabled"); - // $("#top .config .puncAndNum").removeClass("disabled"); - // $("#top .config .punctuationMode").removeClass("hidden"); - // $("#top .config .numbersMode").removeClass("hidden"); - // $("#top .config .quoteLength").addClass("hidden"); - } else if (current == "words") { - // $("#top .config .wordCount").removeClass("hidden"); - // $("#top .config .time").addClass("hidden"); - // $("#top .config .customText").addClass("hidden"); - $("#top .config .punctuationMode").removeClass("disabled"); - $("#top .config .numbersMode").removeClass("disabled"); - // $("#top .config .puncAndNum").removeClass("disabled"); - // $("#top .config .punctuationMode").removeClass("hidden"); - // $("#top .config .numbersMode").removeClass("hidden"); - // $("#top .config .quoteLength").addClass("hidden"); - } else if (current == "custom") { - // $("#top .config .wordCount").addClass("hidden"); - // $("#top .config .time").addClass("hidden"); - // $("#top .config .customText").removeClass("hidden"); - $("#top .config .punctuationMode").removeClass("disabled"); - $("#top .config .numbersMode").removeClass("disabled"); - // $("#top .config .puncAndNum").removeClass("disabled"); - // $("#top .config .punctuationMode").removeClass("hidden"); - // $("#top .config .numbersMode").removeClass("hidden"); - // $("#top .config .quoteLength").addClass("hidden"); - } else if (current == "quote") { - // $("#top .config .wordCount").addClass("hidden"); - // $("#top .config .time").addClass("hidden"); - // $("#top .config .customText").addClass("hidden"); - $("#top .config .punctuationMode").addClass("disabled"); - $("#top .config .numbersMode").addClass("disabled"); - // $("#top .config .puncAndNum").addClass("disabled"); - // $("#top .config .punctuationMode").removeClass("hidden"); - // $("#top .config .numbersMode").removeClass("hidden"); - // $("#result .stats .source").removeClass("hidden"); - // $("#top .config .quoteLength").removeClass("hidden"); - } else if (current == "zen") { - // $("#top .config .wordCount").addClass("hidden"); - // $("#top .config .time").addClass("hidden"); - // $("#top .config .customText").addClass("hidden"); - // $("#top .config .punctuationMode").addClass("hidden"); - // $("#top .config .numbersMode").addClass("hidden"); - // $("#top .config .quoteLength").addClass("hidden"); - } + // if (current == "time") { + // $("#testConfig .punctuationMode").removeClass("hidden"); + // $("#testConfig .numbersMode").removeClass("hidden"); + // $("#testConfig .leftSpacer").removeClass("hidden"); + // } else if (current == "words") { + // $("#testConfig .punctuationMode").removeClass("hidden"); + // $("#testConfig .numbersMode").removeClass("hidden"); + // $("#testConfig .leftSpacer").removeClass("hidden"); + // } else if (current == "custom") { + // $("#testConfig .punctuationMode").removeClass("hidden"); + // $("#testConfig .numbersMode").removeClass("hidden"); + // $("#testConfig .leftSpacer").removeClass("hidden"); + // } else if (current == "quote") { + // $("#testConfig .punctuationMode").addClass("hidden"); + // $("#testConfig .numbersMode").addClass("hidden"); + // $("#testConfig .leftSpacer").addClass("hidden"); + // } else if (current == "zen") { + // // + // } const submenu = { time: "time", words: "wordCount", custom: "customText", quote: "quoteLength", - zen: "", + zen: "zen", }; const animTime = 250; + const puncAndNumVisible = { + time: true, + words: true, + custom: true, + quote: false, + zen: false, + }; + + if ( + puncAndNumVisible[previous] == false && + puncAndNumVisible[current] == true + ) { + //show + + $("#testConfig .leftSpacer").removeClass("scrolled"); + $("#testConfig .puncAndNum") + .css({ + opacity: 0, + maxWidth: 0, + }) + .animate( + { + opacity: 1, + maxWidth: "14rem", + }, + animTime, + "easeInOutSine" + ); + } else if ( + puncAndNumVisible[previous] == true && + puncAndNumVisible[current] == false + ) { + //hide + $("#testConfig .leftSpacer").addClass("scrolled"); + $("#testConfig .puncAndNum") + .css({ + opacity: 1, + maxWidth: "14rem", + }) + .animate( + { + opacity: 0, + maxWidth: "0", + }, + animTime, + "easeInOutSine" + ); + } + if (current == "zen") { - $(`#top .config .${submenu[previous]}`).animate( - { - opacity: 0, - }, - animTime / 2, - () => { - $(`#top .config .${submenu[previous]}`).addClass("hidden"); - } - ); - $(`#top .config .puncAndNum`).animate( - { - opacity: 0, - }, - animTime / 2, - () => { - $(`#top .config .puncAndNum`).addClass("invisible"); - } - ); - return; + $("#testConfig .rightSpacer").addClass("scrolled"); + } else { + $("#testConfig .rightSpacer").removeClass("scrolled"); } - if (previous == "zen") { - setTimeout(() => { - $(`#top .config .${submenu[current]}`).removeClass("hidden"); - $(`#top .config .${submenu[current]}`) - .css({ opacity: 0 }) - .animate( - { - opacity: 1, - }, - animTime / 2 - ); - $(`#top .config .puncAndNum`).removeClass("invisible"); - $(`#top .config .puncAndNum`) - .css({ opacity: 0 }) - .animate( - { - opacity: 1, - }, - animTime / 2 - ); - }, animTime / 2); - return; - } + // const currentWidth = Math.round( + // document.querySelector("#testConfig .row")?.getBoundingClientRect().width ?? + // 0 + // ); - Misc.swapElements( - $("#top .config ." + submenu[previous]), - $("#top .config ." + submenu[current]), - animTime + // if (puncAndNumVisible[current]) { + // $("#testConfig .punctuationMode").removeClass("hidden"); + // $("#testConfig .numbersMode").removeClass("hidden"); + // $("#testConfig .leftSpacer").removeClass("hidden"); + // } else { + // $("#testConfig .punctuationMode").addClass("hidden"); + // $("#testConfig .numbersMode").addClass("hidden"); + // $("#testConfig .leftSpacer").addClass("hidden"); + // } + + // if (current == "zen") { + // $("#testConfig .rightSpacer").addClass("hidden"); + // } else { + // $("#testConfig .rightSpacer").removeClass("hidden"); + // } + + const previousWidth = Math.round( + document + .querySelector(`#testConfig .${submenu[previous]}`) + ?.getBoundingClientRect().width ?? 0 ); + + $(`#testConfig .${submenu[previous]}`).addClass("hidden"); + + $(`#testConfig .${submenu[current]}`).removeClass("hidden"); + + const currentWidth = Math.round( + document + .querySelector(`#testConfig .${submenu[current]}`) + ?.getBoundingClientRect().width ?? 0 + ); + + $(`#testConfig .${submenu[previous]}`).removeClass("hidden"); + + $(`#testConfig .${submenu[current]}`).addClass("hidden"); + + const widthDifference = currentWidth - previousWidth; + + const widthStep = widthDifference / 2; + + $(`#testConfig .${submenu[previous]}`) + .css({ + opacity: 1, + width: previousWidth, + }) + .animate( + { + width: previousWidth + widthStep, + opacity: 0, + }, + animTime / 2, + "easeInSine", + () => { + $(`#testConfig .${submenu[previous]}`) + .css({ + opacity: 1, + width: "unset", + }) + .addClass("hidden"); + $(`#testConfig .${submenu[current]}`) + .css({ + opacity: 0, + width: previousWidth + widthStep, + }) + .removeClass("hidden") + .animate( + { + opacity: 1, + width: currentWidth, + }, + animTime / 2, + "easeOutSine", + () => { + $(`#testConfig .${submenu[current]}`).css("width", "unset"); + } + ); + } + ); + + // $(`#testConfig .${submenu[current]}`) + // .css({ + // opacity: 0, + // maxWidth: previousWidth, + // }) + // .removeClass("hidden") + // .animate( + // { + // maxWidth: currentWidth, + // opacity: 1, + // }, + // 250, + // () => { + // $(`#testConfig .${submenu[current]}`).css({ + // opacity: 1, + // maxWidth: "unset", + // }); + // } + // ); + + // const newWidth = Math.round( + // document.querySelector("#testConfig .row")?.getBoundingClientRect().width ?? + // 0 + // ); + + // console.log(submenu[current], animTime, newWidth, currentWidth); + + // if (current == "zen") { + // $(`#testConfig .${submenu[previous]}`).animate( + // { + // opacity: 0, + // }, + // animTime / 2, + // () => { + // $(`#testConfig .${submenu[previous]}`).addClass("hidden"); + // } + // ); + // $(`#testConfig .puncAndNum`).animate( + // { + // opacity: 0, + // }, + // animTime / 2, + // () => { + // $(`#testConfig .puncAndNum`).addClass("hidden"); + // } + // ); + // return; + // } + + // if (previous == "zen") { + // setTimeout(() => { + // $(`#testConfig .${submenu[current]}`).removeClass("hidden"); + // $(`#testConfig .${submenu[current]}`) + // .css({ opacity: 0 }) + // .animate( + // { + // opacity: 1, + // }, + // animTime / 2 + // ); + // $(`#testConfig .puncAndNum`).removeClass("hidden"); + // $(`#testConfig .puncAndNum`) + // .css({ opacity: 0 }) + // .animate( + // { + // opacity: 1, + // }, + // animTime / 2 + // ); + // }, animTime / 2); + // return; + // } + + // Misc.swapElements( + // $("#testConfig ." + submenu[previous]), + // $("#testConfig ." + submenu[current]), + // animTime + // ); +} + +export function updateExtras( + key: string, + value: MonkeyTypes.ConfigValues +): void { + if (key == "time") { + $("#testConfig .time .textButton").removeClass("active"); + const timeCustom = ![15, 30, 60, 120].includes(value as number) + ? "custom" + : value; + $( + "#testConfig .time .textButton[timeConfig='" + timeCustom + "']" + ).addClass("active"); + } else if (key == "words") { + $("#testConfig .wordCount .textButton").removeClass("active"); + + const wordCustom = ![10, 25, 50, 100, 200].includes(value as number) + ? "custom" + : value; + + $( + "#testConfig .wordCount .textButton[wordCount='" + wordCustom + "']" + ).addClass("active"); + } else if (key == "quoteLength") { + $("#testConfig .quoteLength .textButton").removeClass("active"); + (value as MonkeyTypes.QuoteLength[]).forEach((ql) => { + $( + "#testConfig .quoteLength .textButton[quoteLength='" + ql + "']" + ).addClass("active"); + }); + } else if (key == "numbers") { + if (!value) { + $("#testConfig .numbersMode.textButton").removeClass("active"); + } else { + $("#testConfig .numbersMode.textButton").addClass("active"); + } + } else if (key == "punctuation") { + if (!value) { + $("#testConfig .punctuationMode.textButton").removeClass("active"); + } else { + $("#testConfig .punctuationMode.textButton").addClass("active"); + } + } } export function showFavoriteQuoteLength(): void { @@ -179,5 +332,11 @@ ConfigEvent.subscribe((eventKey, eventValue, _nosave, eventPreviousValue) => { eventPreviousValue as MonkeyTypes.Mode, eventValue as MonkeyTypes.Mode ); + } else if ( + ["time", "quoteLength", "words", "numbers", "punctuation"].includes( + eventKey + ) + ) { + updateExtras(eventKey, eventValue); } }); diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index b36ac82ed..4b61bc5f3 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -54,6 +54,7 @@ import objectHash from "object-hash"; import * as AnalyticsController from "../controllers/analytics-controller"; import { Auth } from "../firebase"; import * as AdController from "../controllers/ad-controller"; +import * as TestConfig from "./test-config"; let failReason = ""; const koInputVisual = document.getElementById("koInputVisual") as HTMLElement; @@ -553,6 +554,7 @@ export function restart(options = {} as RestartOptions): void { AdController.updateTestPageAds(false); Focus.set(false); } + TestConfig.show(); TestUI.focusWords(); $("#monkey .fast").stop(true, true).css("opacity", 0); $("#monkey").stop(true, true).css({ animationDuration: "0s" }); @@ -1851,7 +1853,17 @@ $(document).on("keypress", "#restartTestButtonWithSameWordset", (event) => { } }); -$(document).on("click", "#top .config .wordCount .textButton", (e) => { +$(document).on("click", "#testConfig .mode .textButton", (e) => { + if (TestUI.testRestarting) return; + if ($(e.currentTarget).hasClass("active")) return; + const mode = ($(e.currentTarget).attr("mode") ?? "time") as MonkeyTypes.Mode; + if (mode === undefined) return; + UpdateConfig.setMode(mode); + ManualRestart.set(); + restart(); +}); + +$(document).on("click", "#testConfig .wordCount .textButton", (e) => { if (TestUI.testRestarting) return; const wrd = $(e.currentTarget).attr("wordCount") ?? "15"; if (wrd != "custom") { @@ -1861,7 +1873,7 @@ $(document).on("click", "#top .config .wordCount .textButton", (e) => { } }); -$(document).on("click", "#top .config .time .textButton", (e) => { +$(document).on("click", "#testConfig .time .textButton", (e) => { if (TestUI.testRestarting) return; const mode = $(e.currentTarget).attr("timeConfig") ?? "10"; if (mode != "custom") { @@ -1871,7 +1883,7 @@ $(document).on("click", "#top .config .time .textButton", (e) => { } }); -$(document).on("click", "#top .config .quoteLength .textButton", (e) => { +$(document).on("click", "#testConfig .quoteLength .textButton", (e) => { if (TestUI.testRestarting) return; let len: MonkeyTypes.QuoteLength | MonkeyTypes.QuoteLength[] = < MonkeyTypes.QuoteLength @@ -1886,30 +1898,20 @@ $(document).on("click", "#top .config .quoteLength .textButton", (e) => { } }); -$(document).on("click", "#top .config .punctuationMode .textButton", () => { +$(document).on("click", "#testConfig .punctuationMode.textButton", () => { if (TestUI.testRestarting) return; UpdateConfig.setPunctuation(!Config.punctuation); ManualRestart.set(); restart(); }); -$(document).on("click", "#top .config .numbersMode .textButton", () => { +$(document).on("click", "#testConfig .numbersMode.textButton", () => { if (TestUI.testRestarting) return; UpdateConfig.setNumbers(!Config.numbers); ManualRestart.set(); restart(); }); -$(document).on("click", "#top .config .mode .textButton", (e) => { - if (TestUI.testRestarting) return; - if ($(e.currentTarget).hasClass("active")) return; - const mode = ($(e.currentTarget).attr("mode") ?? "time") as MonkeyTypes.Mode; - if (mode === undefined) return; - UpdateConfig.setMode(mode); - ManualRestart.set(); - restart(); -}); - $("#practiseWordsPopup .button.missed").on("click", () => { PractiseWords.hidePopup(); PractiseWords.init(true, false); diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 6a49144bb..01fe9c180 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -252,6 +252,7 @@ export async function screenshot(): Promise { } function revertScreenshot(): void { + $("#testConfig").removeClass("invisible"); $("#ad-result-wrapper").removeClass("hidden"); $("#ad-result-small-wrapper").removeClass("hidden"); $("#notificationCenter").removeClass("hidden"); @@ -287,6 +288,16 @@ export async function screenshot(): Promise { ); } $(".pageTest .buttons").addClass("hidden"); + $("#testConfig").addClass("invisible"); + $("#notificationCenter").addClass("hidden"); + $("#commandLineMobileButton").addClass("hidden"); + $(".pageTest .loginTip").addClass("hidden"); + $("noscript").addClass("hidden"); + $("#nocss").addClass("hidden"); + $("#ad-result-wrapper").addClass("hidden"); + $("#ad-result-small-wrapper").addClass("hidden"); + if (revertCookie) $("#cookiePopupWrapper").addClass("hidden"); + const src = $("#result"); const sourceX = src.offset()?.left ?? 0; /*X position from div#target*/ const sourceY = src.offset()?.top ?? 0; /*Y position from div#target*/ @@ -296,14 +307,6 @@ export async function screenshot(): Promise { const sourceHeight = ( src.outerHeight(true) ); /*clientHeight/offsetHeight from div#target*/ - $("#notificationCenter").addClass("hidden"); - $("#commandLineMobileButton").addClass("hidden"); - $(".pageTest .loginTip").addClass("hidden"); - $("noscript").addClass("hidden"); - $("#nocss").addClass("hidden"); - $("#ad-result-wrapper").addClass("hidden"); - $("#ad-result-small-wrapper").addClass("hidden"); - if (revertCookie) $("#cookiePopupWrapper").addClass("hidden"); try { const paddingX = Misc.convertRemToPixels(2); const paddingY = Misc.convertRemToPixels(2); diff --git a/frontend/src/ts/types/types.d.ts b/frontend/src/ts/types/types.d.ts index 22ce784ac..d6d62f09c 100644 --- a/frontend/src/ts/types/types.d.ts +++ b/frontend/src/ts/types/types.d.ts @@ -473,6 +473,7 @@ declare namespace MonkeyTypes { addedAt: number; filterPresets: ResultFilters[]; xp: number; + inboxUnreadSize: number; streak: number; } @@ -739,4 +740,30 @@ declare namespace MonkeyTypes { color?: string; customStyle?: string; } + + interface MonkeyMail { + id: string; + subject: string; + body: string; + timestamp: number; + read: boolean; + rewards: AllRewards[]; + } + + interface Reward { + type: string; + item: T; + } + + interface XpReward extends Reward { + type: "xp"; + item: number; + } + + interface BadgeReward extends Reward { + type: "badge"; + item: Badge; + } + + type AllRewards = XpReward | BadgeReward; } diff --git a/frontend/static/html/pages/settings.html b/frontend/static/html/pages/settings.html index 8f3594b09..e0f6e099f 100644 --- a/frontend/static/html/pages/settings.html +++ b/frontend/static/html/pages/settings.html @@ -1443,7 +1443,12 @@

font size

Change the font size of the test words.
- +
diff --git a/frontend/static/html/pages/test.html b/frontend/static/html/pages/test.html index 724781dbf..0e8ddc186 100644 --- a/frontend/static/html/pages/test.html +++ b/frontend/static/html/pages/test.html @@ -1,4 +1,122 @@ diff --git a/frontend/static/html/popups.html b/frontend/static/html/popups.html index 0a0df0c7b..54ef68ad3 100644 --- a/frontend/static/html/popups.html +++ b/frontend/static/html/popups.html @@ -1,3 +1,36 @@ + + diff --git a/frontend/static/html/top.html b/frontend/static/html/top.html index 52a5d4ea1..b828ba9e6 100644 --- a/frontend/static/html/top.html +++ b/frontend/static/html/top.html @@ -99,6 +99,7 @@
+
+ +
+ +
+
+ +
+ +
+
- - - + --> diff --git a/frontend/static/main.html b/frontend/static/main.html index e107c8d55..4002051db 100644 --- a/frontend/static/main.html +++ b/frontend/static/main.html @@ -39,7 +39,7 @@