diff --git a/gulpfile.js b/gulpfile.js index 801b54fea..72aaaf8dd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -129,6 +129,7 @@ const refactoredSrc = [ "./src/js/popups/custom-text-popup.js", "./src/js/popups/quote-search-popup.js", + "./src/js/popups/rate-quote-popup.js", "./src/js/popups/version-popup.js", "./src/js/popups/support-popup.js", "./src/js/popups/custom-word-amount-popup.js", diff --git a/src/js/db.js b/src/js/db.js index 9bdfaf6fe..86ba559c5 100644 --- a/src/js/db.js +++ b/src/js/db.js @@ -54,6 +54,7 @@ export async function initSnapshot() { started: 0, completed: 0, }, + quoteRatings: undefined, }; let snap = defaultSnap; try { @@ -70,6 +71,7 @@ export async function initSnapshot() { started: userData.startedTests, completed: userData.completedTests, }; + snap.quoteRatings = userData.quoteRatings; snap.favouriteThemes = userData.favouriteThemes === undefined ? [] : userData.favouriteThemes; try { diff --git a/src/js/popups/rate-quote-popup.js b/src/js/popups/rate-quote-popup.js new file mode 100644 index 000000000..a5e902391 --- /dev/null +++ b/src/js/popups/rate-quote-popup.js @@ -0,0 +1,203 @@ +import * as DB from "./db"; +import * as Loader from "./loader"; +import * as Notifications from "./notifications"; +import axiosInstance from "./axios-instance"; + +let rating = 0; + +let currentQuote = null; +let quoteStats = null; + +function reset() { + $(`#rateQuotePopup .quote .text`).text("-"); + $(`#rateQuotePopup .quote .source .val`).text("-"); + $(`#rateQuotePopup .quote .id .val`).text("-"); + $(`#rateQuotePopup .quote .length .val`).text("-"); + $("#rateQuotePopup .ratingCount .val").text("-"); + $("#rateQuotePopup .ratingAverage .val").text("-"); +} + +export function show(quote) { + if ($("#rateQuotePopupWrapper").hasClass("hidden")) { + reset(); + + currentQuote = quote; + rating = 0; + let alreadyRated = DB.getSnapshot().quoteRatings?.[currentQuote.language]?.[ + currentQuote.id + ]; + if (alreadyRated) { + rating = alreadyRated; + } + refreshStars(); + updateData(); + $("#rateQuotePopupWrapper") + .stop(true, true) + .css("opacity", 0) + .removeClass("hidden") + .animate({ opacity: 1 }, 125); + } +} + +function hide() { + if (!$("#rateQuotePopupWrapper").hasClass("hidden")) { + $("#rateQuotePopupWrapper") + .stop(true, true) + .css("opacity", 1) + .animate( + { + opacity: 0, + }, + 100, + (e) => { + $("#rateQuotePopupWrapper").addClass("hidden"); + } + ); + } +} + +function refreshStars(force) { + let limit = force ? parseInt(force) : rating; + $(`#rateQuotePopup .star`).removeClass("active"); + for (let i = 1; i <= limit; i++) { + $(`#rateQuotePopup .star[rating=${i}]`).addClass("active"); + } +} + +function updateData() { + let lengthDesc; + if (currentQuote.group == 0) { + lengthDesc = "short"; + } else if (currentQuote.group == 1) { + lengthDesc = "medium"; + } else if (currentQuote.group == 2) { + lengthDesc = "long"; + } else if (currentQuote.group == 3) { + lengthDesc = "thicc"; + } + $(`#rateQuotePopup .quote .text`).text(currentQuote.text); + $(`#rateQuotePopup .quote .source .val`).text(currentQuote.source); + $(`#rateQuotePopup .quote .id .val`).text(currentQuote.id); + $(`#rateQuotePopup .quote .length .val`).text(lengthDesc); + updateRatingStats(); +} + +export async function getQuoteStats(quote) { + if (quote) currentQuote = quote; + let response; + try { + response = await axiosInstance.get("/quote-ratings/get", { + params: { quoteId: currentQuote.id, language: currentQuote.language }, + }); + } catch (e) { + Loader.hide(); + let msg = e?.response?.data?.message ?? e.message; + Notifications.add("Failed to get quote ratings: " + msg, -1); + return; + } + Loader.hide(); + if (response.status !== 200) { + Notifications.add(response.data.message); + } else { + quoteStats = response.data; + if (quoteStats) { + quoteStats.average = ( + Math.round((quoteStats.totalRating / quoteStats.ratings) * 10) / 10 + ).toFixed(1); + } + return response.data; + } +} + +export function clearQuoteStats() { + quoteStats = undefined; +} + +async function updateRatingStats() { + if (quoteStats === null) await getQuoteStats(); + if (quoteStats === undefined) { + $("#rateQuotePopup .ratingCount .val").text("0"); + $("#rateQuotePopup .ratingAverage .val").text("-"); + } else { + $("#rateQuotePopup .ratingCount .val").text(quoteStats.ratings); + $("#rateQuotePopup .ratingAverage .val").text(quoteStats.average); + } +} + +async function submit() { + if (rating == 0) { + Notifications.add("Please select a rating"); + return; + } + hide(); + let response; + try { + response = await axiosInstance.post("/quote-ratings/submit", { + quoteId: currentQuote.id, + rating: rating, + language: currentQuote.language, + }); + } catch (e) { + Loader.hide(); + let msg = e?.response?.data?.message ?? e.message; + Notifications.add("Failed to submit quote rating: " + msg, -1); + return; + } + Loader.hide(); + if (response.status !== 200) { + Notifications.add(response.data.message); + } else { + let quoteRatings = DB.getSnapshot().quoteRatings; + if (quoteRatings?.[currentQuote.language]?.[currentQuote.id]) { + let oldRating = quoteRatings[currentQuote.language][currentQuote.id]; + let diff = rating - oldRating; + quoteStats.totalRating += diff; + + quoteRatings[currentQuote.language][currentQuote.id] = rating; + Notifications.add("Rating updated", 1); + } else { + if (quoteRatings === undefined) quoteRatings = {}; + if (quoteRatings[currentQuote.language] === undefined) + quoteRatings[currentQuote.language] = {}; + if (quoteRatings[currentQuote.language][currentQuote.id] == undefined) + quoteRatings[currentQuote.language][currentQuote.id] = undefined; + quoteRatings[currentQuote.language][currentQuote.id] = rating; + quoteStats = { + ratings: 1, + totalRating: parseInt(rating), + quoteId: currentQuote.id, + language: currentQuote.language, + }; + Notifications.add("Rating submitted", 1); + } + quoteStats.average = ( + Math.round((quoteStats.totalRating / quoteStats.ratings) * 10) / 10 + ).toFixed(1); + $(".pageTest #result #rateQuoteButton .rating").text(quoteStats.average); + } +} + +$("#rateQuotePopupWrapper").click((e) => { + if ($(e.target).attr("id") === "rateQuotePopupWrapper") { + hide(); + } +}); + +$("#rateQuotePopup .stars .star").hover((e) => { + let ratingHover = $(e.currentTarget).attr("rating"); + refreshStars(ratingHover); +}); + +$("#rateQuotePopup .stars .star").click((e) => { + let ratingHover = $(e.currentTarget).attr("rating"); + rating = ratingHover; +}); + +$("#rateQuotePopup .stars .star").mouseout((e) => { + $(`#rateQuotePopup .star`).removeClass("active"); + refreshStars(); +}); + +$("#rateQuotePopup .submitButton").click((e) => { + submit(); +}); diff --git a/src/js/test/test-logic.js b/src/js/test/test-logic.js index 54d5ae520..334339fa0 100644 --- a/src/js/test/test-logic.js +++ b/src/js/test/test-logic.js @@ -35,6 +35,7 @@ import * as TodayTracker from "./today-tracker"; import * as WeakSpot from "./weak-spot"; import * as Wordset from "./wordset"; import * as ChallengeContoller from "./challenge-controller"; +import * as RateQuotePopup from "./rate-quote-popup"; let glarsesMode = false; @@ -707,6 +708,7 @@ export async function init() { rq.text = rq.text.replace(/( *(\r\n|\r|\n) *)/g, "\n "); rq.text = rq.text.replace(/…/g, "..."); rq.text = rq.text.trim(); + rq.language = Config.language.replace(/_\d*k$/g, ""); setRandomQuote(rq); @@ -849,6 +851,7 @@ export function restart( $("#showWordHistoryButton").removeClass("loaded"); TestUI.focusWords(); Funbox.resetMemoryTimer(); + RateQuotePopup.clearQuoteStats(); TestUI.reset(); @@ -1586,6 +1589,17 @@ export async function finish(difficultyFailed = false) { ) { if (firebase.auth().currentUser != null) { completedEvent.uid = firebase.auth().currentUser.uid; + + let quoteStats = await RateQuotePopup.getQuoteStats(randomQuote); + if (quoteStats !== null) { + $(".pageTest #result #rateQuoteButton .rating").text( + quoteStats.average + ); + } else { + $(".pageTest #result #rateQuoteButton .rating").text(""); + } + $(".pageTest #result #rateQuoteButton").removeClass("hidden"); + //check local pb AccountButton.loading(true); let dontShowCrown = false; @@ -1857,6 +1871,7 @@ export async function finish(difficultyFailed = false) { }); }); } else { + $(".pageTest #result #rateQuoteButton").addClass("hidden"); try { firebase.analytics().logEvent("testCompletedNoLogin", completedEvent); } catch (e) { diff --git a/src/js/test/test-ui.js b/src/js/test/test-ui.js index e5c56731c..918a61415 100644 --- a/src/js/test/test-ui.js +++ b/src/js/test/test-ui.js @@ -18,6 +18,7 @@ import * as TestStats from "./test-stats"; import * as Misc from "./misc"; import * as TestUI from "./test-ui"; import * as ChallengeController from "./challenge-controller"; +import * as RateQuotePopup from "./rate-quote-popup"; export let currentWordElementIndex = 0; export let resultVisible = false; @@ -934,6 +935,10 @@ $(".pageTest #copyWordsListButton").click(async (event) => { } }); +$(".pageTest #rateQuoteButton").click(async (event) => { + RateQuotePopup.show(TestLogic.randomQuote); +}); + $(".pageTest #toggleBurstHeatmap").click(async (event) => { UpdateConfig.setBurstHeatmap(!Config.burstHeatmap); }); diff --git a/src/sass/style.scss b/src/sass/style.scss index 2e369676d..e536b6126 100644 --- a/src/sass/style.scss +++ b/src/sass/style.scss @@ -713,6 +713,114 @@ label.checkbox { } } +#rateQuotePopupWrapper { + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.75); + position: fixed; + left: 0; + top: 0; + z-index: 1000; + display: grid; + justify-content: center; + align-items: center; + padding: 5rem 0; + + #rateQuotePopup { + color: var(--sub-color); + background: var(--bg-color); + border-radius: var(--roundness); + padding: 2rem; + display: grid; + gap: 2rem; + width: 1000px; + + display: grid; + grid-template-areas: "ratingStats ratingStats submitButton" "spacer spacer spacer" "quote quote quote"; + grid-template-columns: auto 1fr; + + color: var(--text-color); + + .spacer { + grid-area: spacer; + grid-column: 1/4; + width: 100%; + height: 0.1rem; + border-radius: var(--roundness); + background: var(--sub-color); + opacity: 0.25; + } + + .submitButton { + font-size: 2rem; + grid-area: submitButton; + color: var(--sub-color); + &:hover { + color: var(--text-color); + } + } + + .top { + color: var(--sub-color); + font-size: 0.8rem; + } + + .ratingStats { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; + grid-area: ratingStats; + .top { + font-size: 1rem; + } + .val { + font-size: 2rem; + } + } + + .quote { + display: grid; + grid-area: quote; + gap: 1rem; + grid-template-areas: + "text text text" + "id length source"; + grid-template-columns: 1fr 1fr 3fr; + .text { + grid-area: text; + } + .id { + grid-area: id; + } + .length { + grid-area: length; + } + .source { + grid-area: source; + } + } + + .stars { + display: grid; + color: var(--sub-color); + font-size: 2rem; + grid-template-columns: auto auto auto auto auto; + justify-content: flex-start; + align-items: center; + cursor: pointer; + } + .star { + transition: 0.125s; + } + i { + pointer-events: none; + } + .star.active { + color: var(--text-color); + } + } +} + #simplePopupWrapper { width: 100%; height: 100%; @@ -2129,6 +2237,7 @@ key { color: var(--sub-color); font-size: 1rem; line-height: 1rem; + margin-bottom: 0.25rem; } .bottom { @@ -2146,6 +2255,10 @@ key { margin-left: 0.2rem; } } + + &.source #rateQuoteButton { + padding: 0 0.5rem; + } } // .infoAndTags { diff --git a/static/index.html b/static/index.html index aeaf9e750..67035e6aa 100644 --- a/static/index.html +++ b/static/index.html @@ -137,6 +137,75 @@ + +