From 9ca1ad38b47493bf1f122859acf1694ab0cd3cd3 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Tue, 24 Mar 2026 17:25:02 +0100 Subject: [PATCH 1/3] refactor(quotes): use tanstack (@fehmer) --- frontend/src/ts/collections/quote-ratings.ts | 94 +++++++++++++++++++ .../ts/commandline/lists/quote-favorites.ts | 10 +- frontend/src/ts/components/core/DevTools.tsx | 2 +- .../ts/components/layout/footer/Footer.tsx | 12 +++ frontend/src/ts/elements/last-10-average.ts | 4 +- frontend/src/ts/elements/modes-notice.ts | 4 +- frontend/src/ts/event-handlers/test.ts | 12 ++- frontend/src/ts/modals/share-test-settings.ts | 4 +- frontend/src/ts/states/test.ts | 4 + frontend/src/ts/test/pace-caret.ts | 3 +- frontend/src/ts/test/result.ts | 4 +- frontend/src/ts/test/test-logic.ts | 17 ++-- frontend/src/ts/test/test-ui.ts | 3 +- frontend/src/ts/test/test-words.ts | 6 -- frontend/src/ts/test/timer-progress.ts | 3 +- frontend/src/ts/test/words-generator.ts | 24 ++--- 16 files changed, 158 insertions(+), 48 deletions(-) create mode 100644 frontend/src/ts/collections/quote-ratings.ts diff --git a/frontend/src/ts/collections/quote-ratings.ts b/frontend/src/ts/collections/quote-ratings.ts new file mode 100644 index 000000000000..8584c80498e9 --- /dev/null +++ b/frontend/src/ts/collections/quote-ratings.ts @@ -0,0 +1,94 @@ +import { Language } from "@monkeytype/schemas/languages"; +import { QuoteRating } from "@monkeytype/schemas/quotes"; +import { + parseLoadSubsetOptions, + queryCollectionOptions, +} from "@tanstack/query-db-collection"; +import { createCollection, eq, useLiveQuery } from "@tanstack/solid-db"; +import { Accessor } from "solid-js"; +import { queryClient } from "../queries"; +import { baseKey } from "../queries/utils/keys"; +import Ape from "../ape"; + +export type QuoteUserRating = { + id: number; + language: Language; + rating: number; //0..5 +}; +const queryKeys = { + root: () => [...baseKey("quoteRatings")], + user: () => [...baseKey("userQuoteRatings", { isUserSpecific: true })], +}; + +export const quoteRatingsCollection = createCollection( + queryCollectionOptions({ + staleTime: Infinity, + queryKey: queryKeys.root(), + syncMode: "on-demand", // Enable predicate push-down + queryFn: async ({ meta }) => { + if (meta?.loadSubsetOptions === undefined) { + throw new Error("missing where clause in quoteRatingsCollection"); + } + const { where } = meta.loadSubsetOptions; + const parsed = parseLoadSubsetOptions({ where }); + + const language = parsed.filters.find( + (it) => it.field?.[0] === "language", + )?.value; + const quoteId = parsed.filters.find( + (it) => it.field?.[0] === "quoteId", + )?.value; + + const response = await Ape.quotes.getRating({ + query: { language, quoteId }, + }); + if (response.status !== 200) { + throw new Error( + "Error fetching quote ratings:" + response.body.message, + ); + } + + const existingData: QuoteRating[] = + queryClient.getQueryData(queryKeys.root()) ?? []; + + if (response.body.data !== null) { + existingData.push(response.body.data); + } + + return existingData; + }, + + queryClient, + getKey: (it) => it._id, + }), +); + +// oxlint-disable-next-line typescript/explicit-function-return-type +export function useQuoteRatingsLiveQuery( + filterAccessor: Accessor<{ + language: Language; + id: number; + } | null>, +) { + return useLiveQuery((q) => { + const filter = filterAccessor(); + if (filter === null) return undefined; + return q + .from({ r: quoteRatingsCollection }) + .where(({ r }) => eq(r.language, filter.language)) + .where(({ r }) => eq(r.quoteId, filter.id)); + }); +} + +export const userQuoteRatingsCollection = createCollection( + queryCollectionOptions({ + staleTime: Infinity, + queryKey: queryKeys.root(), + queryClient, + getKey: (it) => it.language + it.id, + queryFn: async () => { + //filled from the user query + return [] as QuoteUserRating[]; + }, + }), +); diff --git a/frontend/src/ts/commandline/lists/quote-favorites.ts b/frontend/src/ts/commandline/lists/quote-favorites.ts index ecfe6bf8876b..e4b24ca94b47 100644 --- a/frontend/src/ts/commandline/lists/quote-favorites.ts +++ b/frontend/src/ts/commandline/lists/quote-favorites.ts @@ -6,8 +6,8 @@ import { } from "../../states/notifications"; import { isAuthenticated } from "../../firebase"; import { showLoaderBar, hideLoaderBar } from "../../states/loader-bar"; -import * as TestWords from "../../test/test-words"; import { Command } from "../types"; +import { getCurrentQuote } from "../../states/test"; const commands: Command[] = [ { @@ -15,7 +15,7 @@ const commands: Command[] = [ display: "Add current quote to favorite", icon: "fa-heart", available: (): boolean => { - const quote = TestWords.currentQuote; + const quote = getCurrentQuote(); return ( isAuthenticated() && quote !== null && @@ -27,7 +27,7 @@ const commands: Command[] = [ try { showLoaderBar(); await QuotesController.setQuoteFavorite( - TestWords.currentQuote as Quote, + getCurrentQuote() as Quote, true, ); hideLoaderBar(); @@ -43,7 +43,7 @@ const commands: Command[] = [ display: "Remove current quote from favorite", icon: "fa-heart-broken", available: (): boolean => { - const quote = TestWords.currentQuote; + const quote = getCurrentQuote(); return ( isAuthenticated() && quote !== null && @@ -55,7 +55,7 @@ const commands: Command[] = [ try { showLoaderBar(); await QuotesController.setQuoteFavorite( - TestWords.currentQuote as Quote, + getCurrentQuote() as Quote, false, ); hideLoaderBar(); diff --git a/frontend/src/ts/components/core/DevTools.tsx b/frontend/src/ts/components/core/DevTools.tsx index 85eb49e91b86..9ccfc4b3de54 100644 --- a/frontend/src/ts/components/core/DevTools.tsx +++ b/frontend/src/ts/components/core/DevTools.tsx @@ -19,7 +19,7 @@ if (import.meta.env.DEV) { default: () => { onMount(() => { m.attachDevtoolsOverlay({ - defaultOpen: true, + defaultOpen: false, noPadding: true, }); }); diff --git a/frontend/src/ts/components/layout/footer/Footer.tsx b/frontend/src/ts/components/layout/footer/Footer.tsx index 12b2f0265924..786ff62b37bb 100644 --- a/frontend/src/ts/components/layout/footer/Footer.tsx +++ b/frontend/src/ts/components/layout/footer/Footer.tsx @@ -1,20 +1,32 @@ import { JSXElement } from "solid-js"; +import { useQuoteRatingsLiveQuery } from "../../../collections/quote-ratings"; import { getFocus, getIsScreenshotting } from "../../../states/core"; import { showModal } from "../../../states/modals"; +import { getCurrentQuote } from "../../../states/test"; import { cn } from "../../../utils/cn"; +import AsyncContent from "../../common/AsyncContent"; import { Button } from "../../common/Button"; import { Keytips } from "./Keytips"; import { ThemeIndicator } from "./ThemeIndicator"; import { VersionButton } from "./VersionButton"; export function Footer(): JSXElement { + const ratingQuery = useQuoteRatingsLiveQuery(() => getCurrentQuote()); + return (